// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Symbols;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
///
/// The compilation object is an immutable representation of a single invocation of the
/// compiler. Although immutable, a compilation is also on-demand, and will realize and cache
/// data as necessary. A compilation can produce a new compilation from existing compilation
/// with the application of small deltas. In many cases, it is more efficient than creating a
/// new compilation from scratch, as the new compilation can reuse information from the old
/// compilation.
///
public sealed partial class CSharpCompilation : Compilation
{
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// Changes to the public interface of this class should remain synchronized with the VB
// version. Do not make any changes to the public interface without making the corresponding
// change to the VB version.
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
internal static readonly ParallelOptions DefaultParallelOptions = new ParallelOptions();
private readonly CSharpCompilationOptions _options;
private readonly Lazy _globalImports;
private readonly Lazy _previousSubmissionImports;
private readonly Lazy _globalNamespaceAlias; // alias symbol used to resolve "global::".
private readonly Lazy _scriptClass;
// All imports (using directives and extern aliases) in syntax trees in this compilation.
// NOTE: We need to de-dup since the Imports objects that populate the list may be GC'd
// and re-created.
private ConcurrentSet _lazyImportInfos;
// Cache the CLS diagnostics for the whole compilation so they aren't computed repeatedly.
// NOTE: Presently, we do not cache the per-tree diagnostics.
private ImmutableArray _lazyClsComplianceDiagnostics;
private Conversions _conversions;
internal Conversions Conversions
{
get
{
if (_conversions == null)
{
Interlocked.CompareExchange(ref _conversions, new BuckStopsHereBinder(this).Conversions, null);
}
return _conversions;
}
}
///
/// Manages anonymous types declared in this compilation. Unifies types that are structurally equivalent.
///
private readonly AnonymousTypeManager _anonymousTypeManager;
private NamespaceSymbol _lazyGlobalNamespace;
internal readonly BuiltInOperators builtInOperators;
///
/// The for this compilation. Do not access directly, use Assembly property
/// instead. This field is lazily initialized by ReferenceManager, ReferenceManager.CacheLockObject must be locked
/// while ReferenceManager "calculates" the value and assigns it, several threads must not perform duplicate
/// "calculation" simultaneously.
///
private SourceAssemblySymbol _lazyAssemblySymbol;
///
/// Holds onto data related to reference binding.
/// The manager is shared among multiple compilations that we expect to have the same result of reference binding.
/// In most cases this can be determined without performing the binding. If the compilation however contains a circular
/// metadata reference (a metadata reference that refers back to the compilation) we need to avoid sharing of the binding results.
/// We do so by creating a new reference manager for such compilation.
///
private ReferenceManager _referenceManager;
private readonly SyntaxAndDeclarationManager _syntaxAndDeclarations;
///
/// Contains the main method of this assembly, if there is one.
///
private EntryPoint _lazyEntryPoint;
///
/// The set of trees for which a has been added to the queue.
///
private HashSet _lazyCompilationUnitCompletedTrees;
public override string Language
{
get
{
return LanguageNames.CSharp;
}
}
public override bool IsCaseSensitive
{
get
{
return true;
}
}
///
/// The options the compilation was created with.
///
public new CSharpCompilationOptions Options
{
get
{
return _options;
}
}
internal AnonymousTypeManager AnonymousTypeManager
{
get
{
return _anonymousTypeManager;
}
}
internal override CommonAnonymousTypeManager CommonAnonymousTypeManager
{
get
{
return AnonymousTypeManager;
}
}
///
/// True when the compiler is run in "strict" mode, in which it enforces the language specification
/// in some cases even at the expense of full compatibility. Such differences typically arise when
/// earlier versions of the compiler failed to enforce the full language specification.
///
internal bool FeatureStrictEnabled => Feature("strict") != null;
///
/// The language version that was used to parse the syntax trees of this compilation.
///
public LanguageVersion LanguageVersion
{
get;
}
public override INamedTypeSymbol CreateErrorTypeSymbol(INamespaceOrTypeSymbol container, string name, int arity)
{
return new ExtendedErrorTypeSymbol((NamespaceOrTypeSymbol)container, name, arity, null);
}
#region Constructors and Factories
private static readonly CSharpCompilationOptions s_defaultOptions = new CSharpCompilationOptions(OutputKind.ConsoleApplication);
private static readonly CSharpCompilationOptions s_defaultSubmissionOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithReferencesSupersedeLowerVersions(true);
///
/// Creates a new compilation from scratch. Methods such as AddSyntaxTrees or AddReferences
/// on the returned object will allow to continue building up the Compilation incrementally.
///
/// Simple assembly name.
/// The syntax trees with the source code for the new compilation.
/// The references for the new compilation.
/// The compiler options to use.
/// A new compilation.
public static CSharpCompilation Create(
string assemblyName,
IEnumerable syntaxTrees = null,
IEnumerable references = null,
CSharpCompilationOptions options = null)
{
return Create(
assemblyName,
options ?? s_defaultOptions,
syntaxTrees,
references,
previousSubmission: null,
returnType: null,
hostObjectType: null,
isSubmission: false);
}
///
/// Creates a new compilation that can be used in scripting.
///
public static CSharpCompilation CreateScriptCompilation(
string assemblyName,
SyntaxTree syntaxTree = null,
IEnumerable references = null,
CSharpCompilationOptions options = null,
CSharpCompilation previousScriptCompilation = null,
Type returnType = null,
Type globalsType = null)
{
CheckSubmissionOptions(options);
ValidateScriptCompilationParameters(previousScriptCompilation, returnType, ref globalsType);
return Create(
assemblyName,
options?.WithReferencesSupersedeLowerVersions(true) ?? s_defaultSubmissionOptions,
(syntaxTree != null) ? new[] { syntaxTree } : SpecializedCollections.EmptyEnumerable(),
references,
previousScriptCompilation,
returnType,
globalsType,
isSubmission: true);
}
private static CSharpCompilation Create(
string assemblyName,
CSharpCompilationOptions options,
IEnumerable syntaxTrees,
IEnumerable references,
CSharpCompilation previousSubmission,
Type returnType,
Type hostObjectType,
bool isSubmission)
{
Debug.Assert(options != null);
Debug.Assert(!isSubmission || options.ReferencesSupersedeLowerVersions);
CheckAssemblyName(assemblyName);
var validatedReferences = ValidateReferences(references);
var compilation = new CSharpCompilation(
assemblyName,
options,
validatedReferences,
previousSubmission,
returnType,
hostObjectType,
isSubmission,
referenceManager: null,
reuseReferenceManager: false,
syntaxAndDeclarations: new SyntaxAndDeclarationManager(
ImmutableArray.Empty,
options.ScriptClassName,
options.SourceReferenceResolver,
CSharp.MessageProvider.Instance,
isSubmission,
state: null));
if (syntaxTrees != null)
{
compilation = compilation.AddSyntaxTrees(syntaxTrees);
}
Debug.Assert((object)compilation._lazyAssemblySymbol == null);
return compilation;
}
private CSharpCompilation(
string assemblyName,
CSharpCompilationOptions options,
ImmutableArray references,
CSharpCompilation previousSubmission,
Type submissionReturnType,
Type hostObjectType,
bool isSubmission,
ReferenceManager referenceManager,
bool reuseReferenceManager,
SyntaxAndDeclarationManager syntaxAndDeclarations,
AsyncQueue eventQueue = null)
: base(assemblyName, references, SyntaxTreeCommonFeatures(syntaxAndDeclarations.ExternalSyntaxTrees), isSubmission, eventQueue)
{
_wellKnownMemberSignatureComparer = new WellKnownMembersSignatureComparer(this);
_options = options;
this.builtInOperators = new BuiltInOperators(this);
_scriptClass = new Lazy(BindScriptClass);
_globalImports = new Lazy(BindGlobalImports);
_previousSubmissionImports = new Lazy(ExpandPreviousSubmissionImports);
_globalNamespaceAlias = new Lazy(CreateGlobalNamespaceAlias);
_anonymousTypeManager = new AnonymousTypeManager(this);
this.LanguageVersion = CommonLanguageVersion(syntaxAndDeclarations.ExternalSyntaxTrees);
if (isSubmission)
{
Debug.Assert(previousSubmission == null || previousSubmission.HostObjectType == hostObjectType);
this.ScriptCompilationInfo = new CSharpScriptCompilationInfo(previousSubmission, submissionReturnType, hostObjectType);
}
else
{
Debug.Assert(previousSubmission == null && submissionReturnType == null && hostObjectType == null);
}
if (reuseReferenceManager)
{
referenceManager.AssertCanReuseForCompilation(this);
_referenceManager = referenceManager;
}
else
{
_referenceManager = new ReferenceManager(
MakeSourceAssemblySimpleName(),
this.Options.AssemblyIdentityComparer,
observedMetadata: referenceManager?.ObservedMetadata);
}
_syntaxAndDeclarations = syntaxAndDeclarations;
Debug.Assert((object)_lazyAssemblySymbol == null);
if (EventQueue != null) EventQueue.Enqueue(new CompilationStartedEvent(this));
}
internal override void ValidateDebugEntryPoint(IMethodSymbol debugEntryPoint, DiagnosticBag diagnostics)
{
Debug.Assert(debugEntryPoint != null);
// Debug entry point has to be a method definition from this compilation.
var methodSymbol = debugEntryPoint as MethodSymbol;
if (methodSymbol?.DeclaringCompilation != this || !methodSymbol.IsDefinition)
{
diagnostics.Add(ErrorCode.ERR_DebugEntryPointNotSourceMethodDefinition, Location.None);
}
}
private static LanguageVersion CommonLanguageVersion(ImmutableArray syntaxTrees)
{
LanguageVersion? result = null;
foreach (var tree in syntaxTrees)
{
var version = ((CSharpParseOptions)tree.Options).LanguageVersion;
if (result == null)
{
result = version;
}
else if (result != version)
{
throw new ArgumentException(CodeAnalysisResources.InconsistentLanguageVersions, nameof(syntaxTrees));
}
}
return result ?? CSharpParseOptions.Default.LanguageVersion;
}
///
/// Create a duplicate of this compilation with different symbol instances.
///
public new CSharpCompilation Clone()
{
return new CSharpCompilation(
this.AssemblyName,
_options,
this.ExternalReferences,
this.PreviousSubmission,
this.SubmissionReturnType,
this.HostObjectType,
this.IsSubmission,
_referenceManager,
reuseReferenceManager: true,
syntaxAndDeclarations: _syntaxAndDeclarations);
}
private CSharpCompilation Update(
ReferenceManager referenceManager,
bool reuseReferenceManager,
SyntaxAndDeclarationManager syntaxAndDeclarations)
{
return new CSharpCompilation(
this.AssemblyName,
_options,
this.ExternalReferences,
this.PreviousSubmission,
this.SubmissionReturnType,
this.HostObjectType,
this.IsSubmission,
referenceManager,
reuseReferenceManager,
syntaxAndDeclarations);
}
///
/// Creates a new compilation with the specified name.
///
public new CSharpCompilation WithAssemblyName(string assemblyName)
{
CheckAssemblyName(assemblyName);
// Can't reuse references since the source assembly name changed and the referenced symbols might
// have internals-visible-to relationship with this compilation or they might had a circular reference
// to this compilation.
return new CSharpCompilation(
assemblyName,
_options,
this.ExternalReferences,
this.PreviousSubmission,
this.SubmissionReturnType,
this.HostObjectType,
this.IsSubmission,
_referenceManager,
reuseReferenceManager: assemblyName == this.AssemblyName,
syntaxAndDeclarations: _syntaxAndDeclarations);
}
///
/// Creates a new compilation with the specified references.
///
///
/// The new will query the given for the underlying
/// metadata as soon as the are needed.
///
/// The new compilation uses whatever metadata is currently being provided by the .
/// E.g. if the current compilation references a metadata file that has changed since the creation of the compilation
/// the new compilation is going to use the updated version, while the current compilation will be using the previous (it doesn't change).
///
public new CSharpCompilation WithReferences(IEnumerable references)
{
// References might have changed, don't reuse reference manager.
// Don't even reuse observed metadata - let the manager query for the metadata again.
return new CSharpCompilation(
this.AssemblyName,
_options,
ValidateReferences(references),
this.PreviousSubmission,
this.SubmissionReturnType,
this.HostObjectType,
this.IsSubmission,
referenceManager: null,
reuseReferenceManager: false,
syntaxAndDeclarations: _syntaxAndDeclarations);
}
///
/// Creates a new compilation with the specified references.
///
public new CSharpCompilation WithReferences(params MetadataReference[] references)
{
return this.WithReferences((IEnumerable)references);
}
///
/// Creates a new compilation with the specified compilation options.
///
public CSharpCompilation WithOptions(CSharpCompilationOptions options)
{
var oldOptions = this.Options;
bool reuseReferenceManager = oldOptions.CanReuseCompilationReferenceManager(options);
bool reuseSyntaxAndDeclarationManager = oldOptions.ScriptClassName == options.ScriptClassName &&
oldOptions.SourceReferenceResolver == options.SourceReferenceResolver;
return new CSharpCompilation(
this.AssemblyName,
options,
this.ExternalReferences,
this.PreviousSubmission,
this.SubmissionReturnType,
this.HostObjectType,
this.IsSubmission,
_referenceManager,
reuseReferenceManager,
reuseSyntaxAndDeclarationManager ?
_syntaxAndDeclarations :
new SyntaxAndDeclarationManager(
_syntaxAndDeclarations.ExternalSyntaxTrees,
options.ScriptClassName,
options.SourceReferenceResolver,
_syntaxAndDeclarations.MessageProvider,
_syntaxAndDeclarations.IsSubmission,
state: null));
}
///
/// Returns a new compilation with the given compilation set as the previous submission.
///
public CSharpCompilation WithScriptCompilationInfo(CSharpScriptCompilationInfo info)
{
if (info == ScriptCompilationInfo)
{
return this;
}
// Reference binding doesn't depend on previous submission so we can reuse it.
return new CSharpCompilation(
this.AssemblyName,
_options,
this.ExternalReferences,
info?.PreviousScriptCompilation,
info?.ReturnType,
info?.GlobalsType,
info != null,
_referenceManager,
reuseReferenceManager: true,
syntaxAndDeclarations: _syntaxAndDeclarations);
}
///
/// Returns a new compilation with a given event queue.
///
internal override Compilation WithEventQueue(AsyncQueue eventQueue)
{
return new CSharpCompilation(
this.AssemblyName,
_options,
this.ExternalReferences,
this.PreviousSubmission,
this.SubmissionReturnType,
this.HostObjectType,
this.IsSubmission,
_referenceManager,
reuseReferenceManager: true,
syntaxAndDeclarations: _syntaxAndDeclarations,
eventQueue: eventQueue);
}
#endregion
#region Submission
public new CSharpScriptCompilationInfo ScriptCompilationInfo { get; }
internal override ScriptCompilationInfo CommonScriptCompilationInfo => ScriptCompilationInfo;
internal CSharpCompilation PreviousSubmission => ScriptCompilationInfo?.PreviousScriptCompilation;
internal override bool HasSubmissionResult()
{
Debug.Assert(IsSubmission);
// A submission may be empty or comprised of a single script file.
var tree = _syntaxAndDeclarations.ExternalSyntaxTrees.SingleOrDefault();
if (tree == null)
{
return false;
}
var root = tree.GetCompilationUnitRoot();
if (root.HasErrors)
{
return false;
}
// Are there any top-level return statments?
if (root.DescendantNodes(n => n is GlobalStatementSyntax || n is StatementSyntax || n is CompilationUnitSyntax).Any(n => n.IsKind(SyntaxKind.ReturnStatement)))
{
return true;
}
// Is there a trailing expression?
var lastGlobalStatement = (GlobalStatementSyntax)root.Members.LastOrDefault(m => m.IsKind(SyntaxKind.GlobalStatement));
if (lastGlobalStatement != null)
{
var statement = lastGlobalStatement.Statement;
if (statement.IsKind(SyntaxKind.ExpressionStatement))
{
var expressionStatement = (ExpressionStatementSyntax)statement;
if (expressionStatement.SemicolonToken.IsMissing)
{
var model = GetSemanticModel(tree);
var expression = expressionStatement.Expression;
var info = model.GetTypeInfo(expression);
return info.ConvertedType?.SpecialType != SpecialType.System_Void;
}
}
}
return false;
}
#endregion
#region Syntax Trees (maintain an ordered list)
///
/// The syntax trees (parsed from source code) that this compilation was created with.
///
public new ImmutableArray SyntaxTrees
{
get { return _syntaxAndDeclarations.GetLazyState().SyntaxTrees; }
}
///
/// Returns true if this compilation contains the specified tree. False otherwise.
///
public new bool ContainsSyntaxTree(SyntaxTree syntaxTree)
{
var cstree = syntaxTree as SyntaxTree;
return cstree != null && _syntaxAndDeclarations.GetLazyState().RootNamespaces.ContainsKey(cstree);
}
///
/// Creates a new compilation with additional syntax trees.
///
public new CSharpCompilation AddSyntaxTrees(params SyntaxTree[] trees)
{
return AddSyntaxTrees((IEnumerable)trees);
}
///
/// Creates a new compilation with additional syntax trees.
///
public new CSharpCompilation AddSyntaxTrees(IEnumerable trees)
{
if (trees == null)
{
throw new ArgumentNullException(nameof(trees));
}
if (trees.IsEmpty())
{
return this;
}
// This HashSet is needed so that we don't allow adding the same tree twice
// with a single call to AddSyntaxTrees. Rather than using a separate HashSet,
// ReplaceSyntaxTrees can just check against ExternalSyntaxTrees, because we
// only allow replacing a single tree at a time.
var externalSyntaxTrees = PooledHashSet.GetInstance();
var syntaxAndDeclarations = _syntaxAndDeclarations;
externalSyntaxTrees.AddAll(syntaxAndDeclarations.ExternalSyntaxTrees);
bool reuseReferenceManager = true;
int i = 0;
foreach (var tree in trees.Cast())
{
if (tree == null)
{
throw new ArgumentNullException($"{nameof(trees)}[{i}]");
}
if (!tree.HasCompilationUnitRoot)
{
throw new ArgumentException(CSharpResources.TreeMustHaveARootNodeWith, $"{nameof(trees)}[{i}]");
}
if (externalSyntaxTrees.Contains(tree))
{
throw new ArgumentException(CSharpResources.SyntaxTreeAlreadyPresent, $"{nameof(trees)}[{i}]");
}
if (this.IsSubmission && tree.Options.Kind == SourceCodeKind.Regular)
{
throw new ArgumentException(CSharpResources.SubmissionCanOnlyInclude, $"{nameof(trees)}[{i}]");
}
externalSyntaxTrees.Add(tree);
reuseReferenceManager &= !tree.HasReferenceOrLoadDirectives;
i++;
}
externalSyntaxTrees.Free();
if (this.IsSubmission && i > 1)
{
throw new ArgumentException(CSharpResources.SubmissionCanHaveAtMostOne, nameof(trees));
}
syntaxAndDeclarations = syntaxAndDeclarations.AddSyntaxTrees(trees);
return Update(_referenceManager, reuseReferenceManager, syntaxAndDeclarations);
}
///
/// Creates a new compilation without the specified syntax trees. Preserves metadata info for use with trees
/// added later.
///
public new CSharpCompilation RemoveSyntaxTrees(params SyntaxTree[] trees)
{
return RemoveSyntaxTrees((IEnumerable)trees);
}
///
/// Creates a new compilation without the specified syntax trees. Preserves metadata info for use with trees
/// added later.
///
public new CSharpCompilation RemoveSyntaxTrees(IEnumerable trees)
{
if (trees == null)
{
throw new ArgumentNullException(nameof(trees));
}
if (trees.IsEmpty())
{
return this;
}
var removeSet = PooledHashSet.GetInstance();
// This HashSet is needed so that we don't allow adding the same tree twice
// with a single call to AddSyntaxTrees. Rather than using a separate HashSet,
// ReplaceSyntaxTrees can just check against ExternalSyntaxTrees, because we
// only allow replacing a single tree at a time.
var externalSyntaxTrees = PooledHashSet.GetInstance();
var syntaxAndDeclarations = _syntaxAndDeclarations;
externalSyntaxTrees.AddAll(syntaxAndDeclarations.ExternalSyntaxTrees);
bool reuseReferenceManager = true;
int i = 0;
foreach (var tree in trees.Cast())
{
if (!externalSyntaxTrees.Contains(tree))
{
// Check to make sure this is not a #load'ed tree.
var loadedSyntaxTreeMap = syntaxAndDeclarations.GetLazyState().LoadedSyntaxTreeMap;
if (SyntaxAndDeclarationManager.IsLoadedSyntaxTree(tree, loadedSyntaxTreeMap))
{
throw new ArgumentException(string.Format(CSharpResources.SyntaxTreeFromLoadNoRemoveReplace, tree), $"{nameof(trees)}[{i}]");
}
throw new ArgumentException(string.Format(CSharpResources.SyntaxTreeNotFoundTo, tree), $"{nameof(trees)}[{i}]");
}
removeSet.Add(tree);
reuseReferenceManager &= !tree.HasReferenceOrLoadDirectives;
i++;
}
externalSyntaxTrees.Free();
syntaxAndDeclarations = syntaxAndDeclarations.RemoveSyntaxTrees(removeSet);
removeSet.Free();
return Update(_referenceManager, reuseReferenceManager, syntaxAndDeclarations);
}
///
/// Creates a new compilation without any syntax trees. Preserves metadata info
/// from this compilation for use with trees added later.
///
public new CSharpCompilation RemoveAllSyntaxTrees()
{
var syntaxAndDeclarations = _syntaxAndDeclarations;
return Update(
_referenceManager,
reuseReferenceManager: !syntaxAndDeclarations.MayHaveReferenceDirectives(),
syntaxAndDeclarations: syntaxAndDeclarations.WithExternalSyntaxTrees(ImmutableArray.Empty));
}
///
/// Creates a new compilation without the old tree but with the new tree.
///
public new CSharpCompilation ReplaceSyntaxTree(SyntaxTree oldTree, SyntaxTree newTree)
{
// this is just to force a cast exception
oldTree = (CSharpSyntaxTree)oldTree;
newTree = (CSharpSyntaxTree)newTree;
if (oldTree == null)
{
throw new ArgumentNullException(nameof(oldTree));
}
if (newTree == null)
{
return this.RemoveSyntaxTrees(oldTree);
}
else if (newTree == oldTree)
{
return this;
}
if (!newTree.HasCompilationUnitRoot)
{
throw new ArgumentException(CSharpResources.TreeMustHaveARootNodeWith, nameof(newTree));
}
var syntaxAndDeclarations = _syntaxAndDeclarations;
var externalSyntaxTrees = syntaxAndDeclarations.ExternalSyntaxTrees;
if (!externalSyntaxTrees.Contains(oldTree))
{
// Check to see if this is a #load'ed tree.
var loadedSyntaxTreeMap = syntaxAndDeclarations.GetLazyState().LoadedSyntaxTreeMap;
if (SyntaxAndDeclarationManager.IsLoadedSyntaxTree(oldTree, loadedSyntaxTreeMap))
{
throw new ArgumentException(string.Format(CSharpResources.SyntaxTreeFromLoadNoRemoveReplace, oldTree), nameof(oldTree));
}
throw new ArgumentException(string.Format(CSharpResources.SyntaxTreeNotFoundTo, oldTree), nameof(oldTree));
}
if (externalSyntaxTrees.Contains(newTree))
{
throw new ArgumentException(CSharpResources.SyntaxTreeAlreadyPresent, nameof(newTree));
}
// TODO(tomat): Consider comparing #r's of the old and the new tree. If they are exactly the same we could still reuse.
// This could be a perf win when editing a script file in the IDE. The services create a new compilation every keystroke
// that replaces the tree with a new one.
var reuseReferenceManager = !oldTree.HasReferenceOrLoadDirectives() && !newTree.HasReferenceOrLoadDirectives();
syntaxAndDeclarations = syntaxAndDeclarations.ReplaceSyntaxTree(oldTree, newTree);
return Update(_referenceManager, reuseReferenceManager, syntaxAndDeclarations);
}
internal override int GetSyntaxTreeOrdinal(SyntaxTree tree)
{
Debug.Assert(this.ContainsSyntaxTree(tree));
return _syntaxAndDeclarations.GetLazyState().OrdinalMap[tree];
}
#endregion
#region References
internal override CommonReferenceManager CommonGetBoundReferenceManager()
{
return GetBoundReferenceManager();
}
internal new ReferenceManager GetBoundReferenceManager()
{
if ((object)_lazyAssemblySymbol == null)
{
_referenceManager.CreateSourceAssemblyForCompilation(this);
Debug.Assert((object)_lazyAssemblySymbol != null);
}
// referenceManager can only be accessed after we initialized the lazyAssemblySymbol.
// In fact, initialization of the assembly symbol might change the reference manager.
return _referenceManager;
}
// for testing only:
internal bool ReferenceManagerEquals(CSharpCompilation other)
{
return ReferenceEquals(_referenceManager, other._referenceManager);
}
public override ImmutableArray DirectiveReferences
{
get
{
return GetBoundReferenceManager().DirectiveReferences;
}
}
internal override IDictionary, MetadataReference> ReferenceDirectiveMap
{
get
{
return GetBoundReferenceManager().ReferenceDirectiveMap;
}
}
// for testing purposes
internal IEnumerable ExternAliases
{
get
{
return GetBoundReferenceManager().ExternAliases;
}
}
///
/// Gets the or for a metadata reference used to create this compilation.
///
/// or corresponding to the given reference or null if there is none.
///
/// Uses object identity when comparing two references.
///
internal new Symbol GetAssemblyOrModuleSymbol(MetadataReference reference)
{
if (reference == null)
{
throw new ArgumentNullException(nameof(reference));
}
if (reference.Properties.Kind == MetadataImageKind.Assembly)
{
return GetBoundReferenceManager().GetReferencedAssemblySymbol(reference);
}
else
{
Debug.Assert(reference.Properties.Kind == MetadataImageKind.Module);
int index = GetBoundReferenceManager().GetReferencedModuleIndex(reference);
return index < 0 ? null : this.Assembly.Modules[index];
}
}
public override IEnumerable ReferencedAssemblyNames
{
get
{
return Assembly.Modules.SelectMany(module => module.GetReferencedAssemblies());
}
}
///
/// All reference directives used in this compilation.
///
internal override IEnumerable ReferenceDirectives
{
get { return this.Declarations.ReferenceDirectives; }
}
///
/// Returns a metadata reference that a given #r resolves to.
///
/// #r directive.
/// Metadata reference the specified directive resolves to, or null if the doesn't match any #r directive in the compilation.
public MetadataReference GetDirectiveReference(ReferenceDirectiveTriviaSyntax directive)
{
MetadataReference reference;
return ReferenceDirectiveMap.TryGetValue(ValueTuple.Create(directive.SyntaxTree.FilePath, directive.File.ValueText), out reference) ? reference : null;
}
///
/// Creates a new compilation with additional metadata references.
///
public new CSharpCompilation AddReferences(params MetadataReference[] references)
{
return (CSharpCompilation)base.AddReferences(references);
}
///
/// Creates a new compilation with additional metadata references.
///
public new CSharpCompilation AddReferences(IEnumerable references)
{
return (CSharpCompilation)base.AddReferences(references);
}
///
/// Creates a new compilation without the specified metadata references.
///
public new CSharpCompilation RemoveReferences(params MetadataReference[] references)
{
return (CSharpCompilation)base.RemoveReferences(references);
}
///
/// Creates a new compilation without the specified metadata references.
///
public new CSharpCompilation RemoveReferences(IEnumerable references)
{
return (CSharpCompilation)base.RemoveReferences(references);
}
///
/// Creates a new compilation without any metadata references
///
public new CSharpCompilation RemoveAllReferences()
{
return (CSharpCompilation)base.RemoveAllReferences();
}
///
/// Creates a new compilation with an old metadata reference replaced with a new metadata reference.
///
public new CSharpCompilation ReplaceReference(MetadataReference oldReference, MetadataReference newReference)
{
return (CSharpCompilation)base.ReplaceReference(oldReference, newReference);
}
public override CompilationReference ToMetadataReference(ImmutableArray aliases = default(ImmutableArray), bool embedInteropTypes = false)
{
return new CSharpCompilationReference(this, aliases, embedInteropTypes);
}
///
/// Get all modules in this compilation, including the source module, added modules, and all
/// modules of referenced assemblies that do not come from an assembly with an extern alias.
/// Metadata imported from aliased assemblies is not visible at the source level except through
/// the use of an extern alias directive. So exclude them from this list which is used to construct
/// the global namespace.
///
private void GetAllUnaliasedModules(ArrayBuilder modules)
{
// NOTE: This includes referenced modules - they count as modules of the compilation assembly.
modules.AddRange(Assembly.Modules);
var referenceManager = GetBoundReferenceManager();
for (int i = 0; i < referenceManager.ReferencedAssemblies.Length; i++)
{
if (referenceManager.DeclarationsAccessibleWithoutAlias(i))
{
modules.AddRange(referenceManager.ReferencedAssemblies[i].Modules);
}
}
}
///
/// Return a list of assembly symbols than can be accessed without using an alias.
/// For example:
/// 1) /r:A.dll /r:B.dll -> A, B
/// 2) /r:Foo=A.dll /r:B.dll -> B
/// 3) /r:Foo=A.dll /r:A.dll -> A
///
internal void GetUnaliasedReferencedAssemblies(ArrayBuilder assemblies)
{
var referenceManager = GetBoundReferenceManager();
for (int i = 0; i < referenceManager.ReferencedAssemblies.Length; i++)
{
if (referenceManager.DeclarationsAccessibleWithoutAlias(i))
{
assemblies.Add(referenceManager.ReferencedAssemblies[i]);
}
}
}
///
/// Gets the that corresponds to the assembly symbol.
///
public new MetadataReference GetMetadataReference(IAssemblySymbol assemblySymbol)
{
return base.GetMetadataReference(assemblySymbol);
}
#endregion
#region Symbols
///
/// The AssemblySymbol that represents the assembly being created.
///
internal SourceAssemblySymbol SourceAssembly
{
get
{
GetBoundReferenceManager();
return _lazyAssemblySymbol;
}
}
///
/// The AssemblySymbol that represents the assembly being created.
///
internal new AssemblySymbol Assembly
{
get
{
return SourceAssembly;
}
}
///
/// Get a ModuleSymbol that refers to the module being created by compiling all of the code.
/// By getting the GlobalNamespace property of that module, all of the namespaces and types
/// defined in source code can be obtained.
///
internal new ModuleSymbol SourceModule
{
get
{
return Assembly.Modules[0];
}
}
///
/// Gets the root namespace that contains all namespaces and types defined in source code or in
/// referenced metadata, merged into a single namespace hierarchy.
///
internal new NamespaceSymbol GlobalNamespace
{
get
{
if ((object)_lazyGlobalNamespace == null)
{
// Get the root namespace from each module, and merge them all together
// Get all modules in this compilation, ones referenced directly by the compilation
// as well as those referenced by all referenced assemblies.
var modules = ArrayBuilder.GetInstance();
GetAllUnaliasedModules(modules);
var result = MergedNamespaceSymbol.Create(
new NamespaceExtent(this),
null,
modules.SelectDistinct(m => m.GlobalNamespace));
modules.Free();
Interlocked.CompareExchange(ref _lazyGlobalNamespace, result, null);
}
return _lazyGlobalNamespace;
}
}
///
/// Given for the specified module or assembly namespace, gets the corresponding compilation
/// namespace (merged namespace representation for all namespace declarations and references
/// with contributions for the namespaceSymbol). Can return null if no corresponding
/// namespace can be bound in this compilation with the same name.
///
internal new NamespaceSymbol GetCompilationNamespace(INamespaceSymbol namespaceSymbol)
{
if (namespaceSymbol is NamespaceSymbol &&
namespaceSymbol.NamespaceKind == NamespaceKind.Compilation &&
namespaceSymbol.ContainingCompilation == this)
{
return (NamespaceSymbol)namespaceSymbol;
}
var containingNamespace = namespaceSymbol.ContainingNamespace;
if (containingNamespace == null)
{
return this.GlobalNamespace;
}
var current = GetCompilationNamespace(containingNamespace);
if ((object)current != null)
{
return current.GetNestedNamespace(namespaceSymbol.Name);
}
return null;
}
private ConcurrentDictionary _externAliasTargets;
internal bool GetExternAliasTarget(string aliasName, out NamespaceSymbol @namespace)
{
if (_externAliasTargets == null)
{
Interlocked.CompareExchange(ref _externAliasTargets, new ConcurrentDictionary(), null);
}
else if (_externAliasTargets.TryGetValue(aliasName, out @namespace))
{
return !(@namespace is MissingNamespaceSymbol);
}
ArrayBuilder builder = null;
var referenceManager = GetBoundReferenceManager();
for (int i = 0; i < referenceManager.ReferencedAssemblies.Length; i++)
{
if (referenceManager.AliasesOfReferencedAssemblies[i].Contains(aliasName))
{
builder = builder ?? ArrayBuilder.GetInstance();
builder.Add(referenceManager.ReferencedAssemblies[i].GlobalNamespace);
}
}
bool foundNamespace = builder != null;
// We want to cache failures as well as successes so that subsequent incorrect extern aliases with the
// same alias will have the same target.
@namespace = foundNamespace
? MergedNamespaceSymbol.Create(new NamespaceExtent(this), namespacesToMerge: builder.ToImmutableAndFree(), containingNamespace: null, nameOpt: null)
: new MissingNamespaceSymbol(new MissingModuleSymbol(new MissingAssemblySymbol(new AssemblyIdentity(System.Guid.NewGuid().ToString())), ordinal: -1));
// Use GetOrAdd in case another thread beat us to the punch (i.e. should return the same object for the same alias, every time).
@namespace = _externAliasTargets.GetOrAdd(aliasName, @namespace);
Debug.Assert(foundNamespace == !(@namespace is MissingNamespaceSymbol));
return foundNamespace;
}
///
/// A symbol representing the implicit Script class. This is null if the class is not
/// defined in the compilation.
///
internal new NamedTypeSymbol ScriptClass
{
get { return _scriptClass.Value; }
}
///
/// Resolves a symbol that represents script container (Script class). Uses the
/// full name of the container class stored in to find the symbol.
///
/// The Script class symbol or null if it is not defined.
private ImplicitNamedTypeSymbol BindScriptClass()
{
if (_options.ScriptClassName == null || !_options.ScriptClassName.IsValidClrTypeName())
{
return null;
}
var namespaceOrType = this.Assembly.GlobalNamespace.GetNamespaceOrTypeByQualifiedName(_options.ScriptClassName.Split('.')).AsSingleton();
return namespaceOrType as ImplicitNamedTypeSymbol;
}
internal bool IsSubmissionSyntaxTree(SyntaxTree tree)
{
Debug.Assert(tree != null);
Debug.Assert(!this.IsSubmission || _syntaxAndDeclarations.ExternalSyntaxTrees.Length <= 1);
return this.IsSubmission && tree == _syntaxAndDeclarations.ExternalSyntaxTrees.SingleOrDefault();
}
///
/// Global imports (including those from previous submissions, if there are any).
///
internal Imports GlobalImports => _globalImports.Value;
private Imports BindGlobalImports() => Imports.FromGlobalUsings(this);
///
/// Imports declared by this submission (null if this isn't one).
///
internal Imports GetSubmissionImports()
{
Debug.Assert(this.IsSubmission);
Debug.Assert(_syntaxAndDeclarations.ExternalSyntaxTrees.Length <= 1);
// A submission may be empty or comprised of a single script file.
var tree = _syntaxAndDeclarations.ExternalSyntaxTrees.SingleOrDefault();
if (tree == null)
{
return Imports.Empty;
}
var binder = GetBinderFactory(tree).GetImportsBinder((CSharpSyntaxNode)tree.GetRoot());
return binder.GetImports(basesBeingResolved: null);
}
///
/// Imports from all previous submissions.
///
internal Imports GetPreviousSubmissionImports() => _previousSubmissionImports.Value;
private Imports ExpandPreviousSubmissionImports()
{
Debug.Assert(this.IsSubmission);
var previous = this.PreviousSubmission;
if (previous == null)
{
return Imports.Empty;
}
return Imports.ExpandPreviousSubmissionImports(previous.GetPreviousSubmissionImports(), this).Concat(
Imports.ExpandPreviousSubmissionImports(previous.GetSubmissionImports(), this));
}
internal AliasSymbol GlobalNamespaceAlias
{
get
{
return _globalNamespaceAlias.Value;
}
}
///
/// Get the symbol for the predefined type from the COR Library referenced by this compilation.
///
internal new NamedTypeSymbol GetSpecialType(SpecialType specialType)
{
if (specialType <= SpecialType.None || specialType > SpecialType.Count)
{
throw new ArgumentOutOfRangeException(nameof(specialType));
}
var result = Assembly.GetSpecialType(specialType);
Debug.Assert(result.SpecialType == specialType);
return result;
}
///
/// Get the symbol for the predefined type member from the COR Library referenced by this compilation.
///
internal Symbol GetSpecialTypeMember(SpecialMember specialMember)
{
return Assembly.GetSpecialTypeMember(specialMember);
}
internal TypeSymbol GetTypeByReflectionType(Type type, DiagnosticBag diagnostics)
{
var result = Assembly.GetTypeByReflectionType(type, includeReferences: true);
if ((object)result == null)
{
var errorType = new ExtendedErrorTypeSymbol(this, type.Name, 0, CreateReflectionTypeNotFoundError(type));
diagnostics.Add(errorType.ErrorInfo, NoLocation.Singleton);
result = errorType;
}
return result;
}
private static CSDiagnosticInfo CreateReflectionTypeNotFoundError(Type type)
{
// The type or namespace name '{0}' could not be found in the global namespace (are you missing an assembly reference?)
return new CSDiagnosticInfo(
ErrorCode.ERR_GlobalSingleTypeNameNotFound,
new object[] { type.AssemblyQualifiedName },
ImmutableArray.Empty,
ImmutableArray.Empty
);
}
// The type of host object model if available.
private TypeSymbol _lazyHostObjectTypeSymbol;
internal TypeSymbol GetHostObjectTypeSymbol()
{
if (HostObjectType != null && (object)_lazyHostObjectTypeSymbol == null)
{
TypeSymbol symbol = Assembly.GetTypeByReflectionType(HostObjectType, includeReferences: true);
if ((object)symbol == null)
{
MetadataTypeName mdName = MetadataTypeName.FromNamespaceAndTypeName(HostObjectType.Namespace ?? String.Empty,
HostObjectType.Name,
useCLSCompliantNameArityEncoding: true);
symbol = new MissingMetadataTypeSymbol.TopLevelWithCustomErrorInfo(
new MissingAssemblySymbol(AssemblyIdentity.FromAssemblyDefinition(HostObjectType.GetTypeInfo().Assembly)).Modules[0],
ref mdName,
CreateReflectionTypeNotFoundError(HostObjectType),
SpecialType.None);
}
Interlocked.CompareExchange(ref _lazyHostObjectTypeSymbol, symbol, null);
}
return _lazyHostObjectTypeSymbol;
}
internal SynthesizedInteractiveInitializerMethod GetSubmissionInitializer()
{
return (IsSubmission && (object)ScriptClass != null) ?
ScriptClass.GetScriptInitializer() :
null;
}
///
/// Gets the type within the compilation's assembly and all referenced assemblies (other than
/// those that can only be referenced via an extern alias) using its canonical CLR metadata name.
///
internal new NamedTypeSymbol GetTypeByMetadataName(string fullyQualifiedMetadataName)
{
return this.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName, includeReferences: true, isWellKnownType: false);
}
///
/// The TypeSymbol for the type 'dynamic' in this Compilation.
///
internal new TypeSymbol DynamicType
{
get
{
return AssemblySymbol.DynamicType;
}
}
///
/// The NamedTypeSymbol for the .NET System.Object type, which could have a TypeKind of
/// Error if there was no COR Library in this Compilation.
///
internal new NamedTypeSymbol ObjectType
{
get
{
return this.Assembly.ObjectType;
}
}
internal bool DeclaresTheObjectClass
{
get
{
return SourceAssembly.DeclaresTheObjectClass;
}
}
internal new MethodSymbol GetEntryPoint(CancellationToken cancellationToken)
{
EntryPoint entryPoint = GetEntryPointAndDiagnostics(cancellationToken);
return entryPoint == null ? null : entryPoint.MethodSymbol;
}
internal EntryPoint GetEntryPointAndDiagnostics(CancellationToken cancellationToken)
{
if (!this.Options.OutputKind.IsApplication() && ((object)this.ScriptClass == null))
{
return null;
}
if (this.Options.MainTypeName != null && !this.Options.MainTypeName.IsValidClrTypeName())
{
Debug.Assert(!this.Options.Errors.IsDefaultOrEmpty);
return new EntryPoint(null, ImmutableArray.Empty);
}
if (_lazyEntryPoint == null)
{
ImmutableArray diagnostics;
var entryPoint = FindEntryPoint(cancellationToken, out diagnostics);
Interlocked.CompareExchange(ref _lazyEntryPoint, new EntryPoint(entryPoint, diagnostics), null);
}
return _lazyEntryPoint;
}
private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out ImmutableArray sealedDiagnostics)
{
var diagnostics = DiagnosticBag.GetInstance();
var entryPointCandidates = ArrayBuilder.GetInstance();
try
{
NamedTypeSymbol mainType;
string mainTypeName = this.Options.MainTypeName;
NamespaceSymbol globalNamespace = this.SourceModule.GlobalNamespace;
if (mainTypeName != null)
{
// Global code is the entry point, ignore all other Mains.
var scriptClass = this.ScriptClass;
if (scriptClass != null)
{
// CONSIDER: we could use the symbol instead of just the name.
diagnostics.Add(ErrorCode.WRN_MainIgnored, NoLocation.Singleton, mainTypeName);
return scriptClass.GetScriptEntryPoint();
}
var mainTypeOrNamespace = globalNamespace.GetNamespaceOrTypeByQualifiedName(mainTypeName.Split('.')).OfMinimalArity();
if ((object)mainTypeOrNamespace == null)
{
diagnostics.Add(ErrorCode.ERR_MainClassNotFound, NoLocation.Singleton, mainTypeName);
return null;
}
mainType = mainTypeOrNamespace as NamedTypeSymbol;
if ((object)mainType == null || mainType.IsGenericType || (mainType.TypeKind != TypeKind.Class && mainType.TypeKind != TypeKind.Struct))
{
diagnostics.Add(ErrorCode.ERR_MainClassNotClass, mainTypeOrNamespace.Locations.First(), mainTypeOrNamespace);
return null;
}
EntryPointCandidateFinder.FindCandidatesInSingleType(mainType, entryPointCandidates, cancellationToken);
}
else
{
mainType = null;
EntryPointCandidateFinder.FindCandidatesInNamespace(globalNamespace, entryPointCandidates, cancellationToken);
// Global code is the entry point, ignore all other Mains.
var scriptClass = this.ScriptClass;
if (scriptClass != null)
{
foreach (var main in entryPointCandidates)
{
diagnostics.Add(ErrorCode.WRN_MainIgnored, main.Locations.First(), main);
}
return scriptClass.GetScriptEntryPoint();
}
}
DiagnosticBag warnings = DiagnosticBag.GetInstance();
var viableEntryPoints = ArrayBuilder.GetInstance();
foreach (var candidate in entryPointCandidates)
{
if (!candidate.HasEntryPointSignature())
{
// a single error for partial methods:
warnings.Add(ErrorCode.WRN_InvalidMainSig, candidate.Locations.First(), candidate);
continue;
}
if (candidate.IsGenericMethod || candidate.ContainingType.IsGenericType)
{
// a single error for partial methods:
warnings.Add(ErrorCode.WRN_MainCantBeGeneric, candidate.Locations.First(), candidate);
continue;
}
if (candidate.IsAsync)
{
diagnostics.Add(ErrorCode.ERR_MainCantBeAsync, candidate.Locations.First(), candidate);
}
viableEntryPoints.Add(candidate);
}
if ((object)mainType == null || viableEntryPoints.Count == 0)
{
diagnostics.AddRange(warnings);
}
warnings.Free();
MethodSymbol entryPoint = null;
if (viableEntryPoints.Count == 0)
{
if ((object)mainType == null)
{
diagnostics.Add(ErrorCode.ERR_NoEntryPoint, NoLocation.Singleton);
}
else
{
diagnostics.Add(ErrorCode.ERR_NoMainInClass, mainType.Locations.First(), mainType);
}
}
else if (viableEntryPoints.Count > 1)
{
viableEntryPoints.Sort(LexicalOrderSymbolComparer.Instance);
var info = new CSDiagnosticInfo(
ErrorCode.ERR_MultipleEntryPoints,
args: SpecializedCollections.EmptyArray