|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Threading;
using Microsoft.Cci;
using Microsoft.CodeAnalysis;
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.Operations;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Symbols;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CSharp.Binder;
namespace Microsoft.CodeAnalysis.CSharp
{
/// <summary>
/// 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.
/// </summary>
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.
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
private readonly CSharpCompilationOptions _options;
private UsingsFromOptionsAndDiagnostics? _lazyUsingsFromOptions;
private ImmutableArray<NamespaceOrTypeAndUsingDirective> _lazyGlobalImports;
private Imports? _lazyPreviousSubmissionImports;
private AliasSymbol? _lazyGlobalNamespaceAlias; // alias symbol used to resolve "global::".
private NamedTypeSymbol? _lazyScriptClass = ErrorTypeSymbol.UnknownResultType;
// The type of host object model if available.
private TypeSymbol? _lazyHostObjectTypeSymbol;
/// <summary>
/// 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.
/// Values are the sets of dependencies for corresponding directives.
/// </summary>
private ConcurrentDictionary<ImportInfo, ImmutableArray<AssemblySymbol>>? _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<Diagnostic> _lazyClsComplianceDiagnostics;
private ImmutableArray<AssemblySymbol> _lazyClsComplianceDependencies;
private Conversions? _conversions;
/// <summary>
/// A conversions object that ignores nullability.
/// </summary>
internal Conversions Conversions
{
get
{
if (_conversions == null)
{
Interlocked.CompareExchange(ref _conversions, new BuckStopsHereBinder(this, associatedFileIdentifier: null).Conversions, null);
}
return _conversions;
}
}
/// <summary>
/// Manages anonymous types declared in this compilation. Unifies types that are structurally equivalent.
/// </summary>
private AnonymousTypeManager? _lazyAnonymousTypeManager;
private NamespaceSymbol? _lazyGlobalNamespace;
private BuiltInOperators? _lazyBuiltInOperators;
/// <summary>
/// The <see cref="SourceAssemblySymbol"/> 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.
/// </summary>
private SourceAssemblySymbol? _lazyAssemblySymbol;
/// <summary>
/// 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.
/// </summary>
private ReferenceManager _referenceManager;
private readonly SyntaxAndDeclarationManager _syntaxAndDeclarations;
/// <summary>
/// Contains the main method of this assembly, if there is one.
/// </summary>
private EntryPoint? _lazyEntryPoint;
/// <summary>
/// Emit nullable attributes for only those members that are visible outside the assembly
/// (public, protected, and if any [InternalsVisibleTo] attributes, internal members).
/// If false, attributes are emitted for all members regardless of visibility.
/// </summary>
private ThreeState _lazyEmitNullablePublicOnly;
/// <summary>
/// The set of trees for which a <see cref="CompilationUnitCompletedEvent"/> has been added to the queue.
/// </summary>
private HashSet<SyntaxTree>? _lazyCompilationUnitCompletedTrees;
/// <summary>
/// The set of trees for which enough analysis was performed in order to record usage of using directives.
/// Once all trees are processed the value is set to null.
/// </summary>
private ImmutableHashSet<SyntaxTree>? _usageOfUsingsRecordedInTrees = ImmutableHashSet<SyntaxTree>.Empty;
internal ImmutableHashSet<SyntaxTree>? UsageOfUsingsRecordedInTrees => Volatile.Read(ref _usageOfUsingsRecordedInTrees);
/// <summary>
/// Cache of T to Nullable<T>.
/// </summary>
private ConcurrentCache<TypeSymbol, NamedTypeSymbol>? _lazyTypeToNullableVersion;
/// <summary>Lazily caches SyntaxTrees by their mapped path. Used to look up the syntax tree referenced by an interceptor (temporary compat behavior).</summary>
/// <remarks>Must be removed prior to interceptors stable release.</remarks>
private ImmutableSegmentedDictionary<string, OneOrMany<SyntaxTree>> _mappedPathToSyntaxTree;
/// <summary>Lazily caches SyntaxTrees by their path. Used to look up the syntax tree referenced by an interceptor.</summary>
/// <remarks>Must be removed prior to interceptors stable release.</remarks>
private ImmutableSegmentedDictionary<string, OneOrMany<SyntaxTree>> _pathToSyntaxTree;
/// <summary>Lazily caches SyntaxTrees by their xxHash128 checksum. Used to look up the syntax tree referenced by an interceptor.</summary>
private ImmutableSegmentedDictionary<ReadOnlyMemory<byte>, OneOrMany<SyntaxTree>> _contentHashToSyntaxTree;
public override string Language
{
get
{
return LanguageNames.CSharp;
}
}
public override bool IsCaseSensitive
{
get
{
return true;
}
}
/// <summary>
/// The options the compilation was created with.
/// </summary>
public new CSharpCompilationOptions Options
{
get
{
return _options;
}
}
internal BuiltInOperators BuiltInOperators
{
get
{
return InterlockedOperations.Initialize(ref _lazyBuiltInOperators, static self => new BuiltInOperators(self), this);
}
}
internal AnonymousTypeManager AnonymousTypeManager
{
get
{
return InterlockedOperations.Initialize(ref _lazyAnonymousTypeManager, static self => new AnonymousTypeManager(self), this);
}
}
internal override CommonAnonymousTypeManager CommonAnonymousTypeManager
{
get
{
return AnonymousTypeManager;
}
}
/// <summary>
/// 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.
/// </summary>
internal bool FeatureStrictEnabled => Feature("strict") != null;
/// <summary>
/// True when the "peverify-compat" feature flag is set or the language version is below C# 7.2.
/// With this flag we will avoid certain patterns known not be compatible with PEVerify.
/// The code may be less efficient and may deviate from spec in corner cases.
/// The flag is only to be used if PEVerify pass is extremely important.
/// </summary>
internal bool IsPeVerifyCompatEnabled => LanguageVersion < LanguageVersion.CSharp7_2 || Feature("peverify-compat") != null;
/// <summary>
/// True when the "disable-length-based-switch" feature flag is set.
/// When this flag is set, the compiler will not emit length-based switch for string dispatches.
/// </summary>
internal bool FeatureDisableLengthBasedSwitch => Feature("disable-length-based-switch") != null;
/// <summary>
/// Returns true if nullable analysis is enabled in the text span represented by the syntax node.
/// </summary>
/// <remarks>
/// This overload is used for member symbols during binding, or for cases other
/// than symbols such as attribute arguments and parameter defaults.
/// </remarks>
internal bool IsNullableAnalysisEnabledIn(SyntaxNode syntax)
{
return IsNullableAnalysisEnabledIn((CSharpSyntaxTree)syntax.SyntaxTree, syntax.Span);
}
/// <summary>
/// Returns true if nullable analysis is enabled in the text span.
/// </summary>
/// <remarks>
/// This overload is used for member symbols during binding, or for cases other
/// than symbols such as attribute arguments and parameter defaults.
/// </remarks>
internal bool IsNullableAnalysisEnabledIn(CSharpSyntaxTree tree, TextSpan span)
{
return GetNullableAnalysisValue() ??
tree.IsNullableAnalysisEnabled(span) ??
(Options.NullableContextOptions & NullableContextOptions.Warnings) != 0;
}
/// <summary>
/// Returns true if nullable analysis is enabled for the method. For constructors, the
/// region considered may include other constructors and field and property initializers.
/// </summary>
/// <remarks>
/// This overload is intended for callers that rely on symbols rather than syntax. The overload
/// uses the cached value calculated during binding (from potentially several spans)
/// from <see cref="IsNullableAnalysisEnabledIn(CSharpSyntaxTree, TextSpan)"/>.
/// </remarks>
internal bool IsNullableAnalysisEnabledIn(MethodSymbol method)
{
return GetNullableAnalysisValue() ??
method.IsNullableAnalysisEnabled();
}
/// <summary>
/// Returns true if nullable analysis is enabled for all methods regardless
/// of the actual nullable context.
/// If this property returns true but IsNullableAnalysisEnabled returns false,
/// any nullable analysis should be enabled but results should be ignored.
/// </summary>
/// <remarks>
/// For DEBUG builds, we treat nullable analysis as enabled for all methods
/// unless explicitly disabled, so that analysis is run, even though results may
/// be ignored, to increase the chance of catching nullable regressions
/// (e.g. https://github.com/dotnet/roslyn/issues/40136).
/// </remarks>
internal bool IsNullableAnalysisEnabledAlways
{
get
{
var value = GetNullableAnalysisValue();
#if DEBUG
return value != false;
#else
return value == true;
#endif
}
}
/// <summary>
/// Returns Feature("run-nullable-analysis") as a bool? value:
/// true for "always"; false for "never"; and null otherwise.
/// </summary>
private bool? GetNullableAnalysisValue()
{
return Feature("run-nullable-analysis") switch
{
"always" => true,
"never" => false,
_ => null,
};
}
/// <summary>
/// The language version that was used to parse the syntax trees of this compilation.
/// </summary>
public LanguageVersion LanguageVersion
{
get;
}
protected override INamedTypeSymbol CommonCreateErrorTypeSymbol(INamespaceOrTypeSymbol? container, string name, int arity)
{
return new ExtendedErrorTypeSymbol(
container.EnsureCSharpSymbolOrNull(nameof(container)),
name, arity, errorInfo: null).GetPublicSymbol();
}
protected override INamespaceSymbol CommonCreateErrorNamespaceSymbol(INamespaceSymbol container, string name)
{
return new MissingNamespaceSymbol(
container.EnsureCSharpSymbolOrNull(nameof(container)),
name).GetPublicSymbol();
}
protected override IPreprocessingSymbol CommonCreatePreprocessingSymbol(string name)
{
return new Symbols.PublicModel.PreprocessingSymbol(name);
}
#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);
/// <summary>
/// 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.
/// </summary>
/// <param name="assemblyName">Simple assembly name.</param>
/// <param name="syntaxTrees">The syntax trees with the source code for the new compilation.</param>
/// <param name="references">The references for the new compilation.</param>
/// <param name="options">The compiler options to use.</param>
/// <returns>A new compilation.</returns>
public static CSharpCompilation Create(
string? assemblyName,
IEnumerable<SyntaxTree>? syntaxTrees = null,
IEnumerable<MetadataReference>? references = null,
CSharpCompilationOptions? options = null)
{
return Create(
assemblyName,
options ?? s_defaultOptions,
syntaxTrees,
references,
previousSubmission: null,
returnType: null,
hostObjectType: null,
isSubmission: false);
}
/// <summary>
/// Creates a new compilation that can be used in scripting.
/// </summary>
public static CSharpCompilation CreateScriptCompilation(
string assemblyName,
SyntaxTree? syntaxTree = null,
IEnumerable<MetadataReference>? 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<SyntaxTree>(),
references,
previousScriptCompilation,
returnType,
globalsType,
isSubmission: true);
}
private static CSharpCompilation Create(
string? assemblyName,
CSharpCompilationOptions options,
IEnumerable<SyntaxTree>? syntaxTrees,
IEnumerable<MetadataReference>? references,
CSharpCompilation? previousSubmission,
Type? returnType,
Type? hostObjectType,
bool isSubmission)
{
RoslynDebug.Assert(options != null);
Debug.Assert(!isSubmission || options.ReferencesSupersedeLowerVersions);
var validatedReferences = ValidateReferences<CSharpCompilationReference>(references);
// We can't reuse the whole Reference Manager entirely (reuseReferenceManager = false)
// because the set of references of this submission differs from the previous one.
// The submission inherits references of the previous submission, adds the previous submission reference
// and may add more references passed explicitly or via #r.
//
// TODO: Consider reusing some results of the assembly binding to improve perf
// since most of the binding work is similar.
// https://github.com/dotnet/roslyn/issues/43397
var compilation = new CSharpCompilation(
assemblyName,
options,
validatedReferences,
previousSubmission,
returnType,
hostObjectType,
isSubmission,
referenceManager: null,
reuseReferenceManager: false,
syntaxAndDeclarations: new SyntaxAndDeclarationManager(
ImmutableArray<SyntaxTree>.Empty,
options.ScriptClassName,
options.SourceReferenceResolver,
CSharp.MessageProvider.Instance,
isSubmission,
state: null),
semanticModelProvider: null);
if (syntaxTrees != null)
{
compilation = compilation.AddSyntaxTrees(syntaxTrees);
}
Debug.Assert(compilation._lazyAssemblySymbol is null);
return compilation;
}
private CSharpCompilation(
string? assemblyName,
CSharpCompilationOptions options,
ImmutableArray<MetadataReference> references,
CSharpCompilation? previousSubmission,
Type? submissionReturnType,
Type? hostObjectType,
bool isSubmission,
ReferenceManager? referenceManager,
bool reuseReferenceManager,
SyntaxAndDeclarationManager syntaxAndDeclarations,
SemanticModelProvider? semanticModelProvider,
AsyncQueue<CompilationEvent>? eventQueue = null)
: this(assemblyName, options, references, previousSubmission, submissionReturnType, hostObjectType, isSubmission, referenceManager, reuseReferenceManager, syntaxAndDeclarations, SyntaxTreeCommonFeatures(syntaxAndDeclarations.ExternalSyntaxTrees), semanticModelProvider, eventQueue)
{
}
private CSharpCompilation(
string? assemblyName,
CSharpCompilationOptions options,
ImmutableArray<MetadataReference> references,
CSharpCompilation? previousSubmission,
Type? submissionReturnType,
Type? hostObjectType,
bool isSubmission,
ReferenceManager? referenceManager,
bool reuseReferenceManager,
SyntaxAndDeclarationManager syntaxAndDeclarations,
IReadOnlyDictionary<string, string> features,
SemanticModelProvider? semanticModelProvider,
AsyncQueue<CompilationEvent>? eventQueue = null)
: base(assemblyName, references, features, isSubmission, semanticModelProvider, eventQueue)
{
_options = options;
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)
{
if (referenceManager is null)
{
throw new ArgumentNullException(nameof(referenceManager));
}
referenceManager.AssertCanReuseForCompilation(this);
_referenceManager = referenceManager;
}
else
{
_referenceManager = new ReferenceManager(
MakeSourceAssemblySimpleName(),
this.Options.AssemblyIdentityComparer,
observedMetadata: referenceManager?.ObservedMetadata);
}
_syntaxAndDeclarations = syntaxAndDeclarations;
Debug.Assert(_lazyAssemblySymbol is null);
if (EventQueue != null) EventQueue.TryEnqueue(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 Symbols.PublicModel.MethodSymbol)?.UnderlyingMethodSymbol;
if (methodSymbol?.DeclaringCompilation != this || !methodSymbol.IsDefinition)
{
diagnostics.Add(ErrorCode.ERR_DebugEntryPointNotSourceMethodDefinition, Location.None);
}
}
private static LanguageVersion CommonLanguageVersion(ImmutableArray<SyntaxTree> 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 ?? LanguageVersion.Default.MapSpecifiedToEffectiveVersion();
}
/// <summary>
/// Create a duplicate of this compilation with different symbol instances.
/// </summary>
public new CSharpCompilation Clone()
{
return new CSharpCompilation(
this.AssemblyName,
_options,
this.ExternalReferences,
this.PreviousSubmission,
this.SubmissionReturnType,
this.HostObjectType,
this.IsSubmission,
_referenceManager,
reuseReferenceManager: true,
_syntaxAndDeclarations,
this.SemanticModelProvider);
}
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,
this.SemanticModelProvider);
}
/// <summary>
/// Creates a new compilation with the specified name.
/// </summary>
public new CSharpCompilation WithAssemblyName(string? 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,
this.SemanticModelProvider);
}
/// <summary>
/// Creates a new compilation with the specified references.
/// </summary>
/// <remarks>
/// The new <see cref="CSharpCompilation"/> will query the given <see cref="MetadataReference"/> for the underlying
/// metadata as soon as the are needed.
///
/// The new compilation uses whatever metadata is currently being provided by the <see cref="MetadataReference"/>.
/// 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).
/// </remarks>
public new CSharpCompilation WithReferences(IEnumerable<MetadataReference>? 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<CSharpCompilationReference>(references),
this.PreviousSubmission,
this.SubmissionReturnType,
this.HostObjectType,
this.IsSubmission,
referenceManager: null,
reuseReferenceManager: false,
_syntaxAndDeclarations,
this.SemanticModelProvider);
}
/// <summary>
/// Creates a new compilation with the specified references.
/// </summary>
public new CSharpCompilation WithReferences(params MetadataReference[] references)
{
return this.WithReferences((IEnumerable<MetadataReference>)references);
}
/// <summary>
/// Creates a new compilation with the specified compilation options.
/// </summary>
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),
this.SemanticModelProvider);
}
/// <summary>
/// Returns a new compilation with the given compilation set as the previous submission.
/// </summary>
public CSharpCompilation WithScriptCompilationInfo(CSharpScriptCompilationInfo? info)
{
if (info == ScriptCompilationInfo)
{
return this;
}
// Metadata references are inherited from the previous submission,
// so we can only reuse the manager if we can guarantee that these references are the same.
// Check if the previous script compilation doesn't change.
// TODO: Consider comparing the metadata references if they have been bound already.
// https://github.com/dotnet/roslyn/issues/43397
bool reuseReferenceManager = ReferenceEquals(ScriptCompilationInfo?.PreviousScriptCompilation, info?.PreviousScriptCompilation);
return new CSharpCompilation(
this.AssemblyName,
_options,
this.ExternalReferences,
info?.PreviousScriptCompilation,
info?.ReturnTypeOpt,
info?.GlobalsType,
isSubmission: info != null,
_referenceManager,
reuseReferenceManager,
_syntaxAndDeclarations,
this.SemanticModelProvider);
}
/// <summary>
/// Returns a new compilation with the given semantic model provider.
/// </summary>
internal override Compilation WithSemanticModelProvider(SemanticModelProvider? semanticModelProvider)
{
if (this.SemanticModelProvider == semanticModelProvider)
{
return this;
}
return new CSharpCompilation(
this.AssemblyName,
_options,
this.ExternalReferences,
this.PreviousSubmission,
this.SubmissionReturnType,
this.HostObjectType,
this.IsSubmission,
_referenceManager,
reuseReferenceManager: true,
_syntaxAndDeclarations,
semanticModelProvider);
}
/// <summary>
/// Returns a new compilation with a given event queue.
/// </summary>
internal override Compilation WithEventQueue(AsyncQueue<CompilationEvent>? eventQueue)
{
return new CSharpCompilation(
this.AssemblyName,
_options,
this.ExternalReferences,
this.PreviousSubmission,
this.SubmissionReturnType,
this.HostObjectType,
this.IsSubmission,
_referenceManager,
reuseReferenceManager: true,
_syntaxAndDeclarations,
this.SemanticModelProvider,
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 statements?
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)
/// <summary>
/// The syntax trees (parsed from source code) that this compilation was created with.
/// </summary>
public new ImmutableArray<SyntaxTree> SyntaxTrees
{
get { return _syntaxAndDeclarations.GetLazyState().SyntaxTrees; }
}
/// <summary>
/// Returns true if this compilation contains the specified tree. False otherwise.
/// </summary>
public new bool ContainsSyntaxTree(SyntaxTree? syntaxTree)
{
return syntaxTree != null && _syntaxAndDeclarations.GetLazyState().RootNamespaces.ContainsKey(syntaxTree);
}
/// <summary>
/// Creates a new compilation with additional syntax trees.
/// </summary>
public new CSharpCompilation AddSyntaxTrees(params SyntaxTree[] trees)
{
return AddSyntaxTrees((IEnumerable<SyntaxTree>)trees);
}
/// <summary>
/// Creates a new compilation with additional syntax trees.
/// </summary>
public new CSharpCompilation AddSyntaxTrees(IEnumerable<SyntaxTree> 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<SyntaxTree>.GetInstance();
var syntaxAndDeclarations = _syntaxAndDeclarations;
externalSyntaxTrees.AddAll(syntaxAndDeclarations.ExternalSyntaxTrees);
bool reuseReferenceManager = true;
int i = 0;
foreach (var tree in trees.Cast<CSharpSyntaxTree>())
{
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);
}
/// <summary>
/// Creates a new compilation without the specified syntax trees. Preserves metadata info for use with trees
/// added later.
/// </summary>
public new CSharpCompilation RemoveSyntaxTrees(params SyntaxTree[] trees)
{
return RemoveSyntaxTrees((IEnumerable<SyntaxTree>)trees);
}
/// <summary>
/// Creates a new compilation without the specified syntax trees. Preserves metadata info for use with trees
/// added later.
/// </summary>
public new CSharpCompilation RemoveSyntaxTrees(IEnumerable<SyntaxTree> trees)
{
if (trees == null)
{
throw new ArgumentNullException(nameof(trees));
}
if (trees.IsEmpty())
{
return this;
}
var removeSet = PooledHashSet<SyntaxTree>.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<SyntaxTree>.GetInstance();
var syntaxAndDeclarations = _syntaxAndDeclarations;
externalSyntaxTrees.AddAll(syntaxAndDeclarations.ExternalSyntaxTrees);
bool reuseReferenceManager = true;
int i = 0;
foreach (var tree in trees.Cast<CSharpSyntaxTree>())
{
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(CSharpResources.SyntaxTreeFromLoadNoRemoveReplace, $"{nameof(trees)}[{i}]");
}
throw new ArgumentException(CSharpResources.SyntaxTreeNotFoundToRemove, $"{nameof(trees)}[{i}]");
}
removeSet.Add(tree);
reuseReferenceManager &= !tree.HasReferenceOrLoadDirectives;
i++;
}
externalSyntaxTrees.Free();
syntaxAndDeclarations = syntaxAndDeclarations.RemoveSyntaxTrees(removeSet);
removeSet.Free();
return Update(_referenceManager, reuseReferenceManager, syntaxAndDeclarations);
}
/// <summary>
/// Creates a new compilation without any syntax trees. Preserves metadata info
/// from this compilation for use with trees added later.
/// </summary>
public new CSharpCompilation RemoveAllSyntaxTrees()
{
var syntaxAndDeclarations = _syntaxAndDeclarations;
return Update(
_referenceManager,
reuseReferenceManager: !syntaxAndDeclarations.MayHaveReferenceDirectives(),
syntaxAndDeclarations: syntaxAndDeclarations.WithExternalSyntaxTrees(ImmutableArray<SyntaxTree>.Empty));
}
/// <summary>
/// Creates a new compilation without the old tree but with the new tree.
/// </summary>
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(CSharpResources.SyntaxTreeFromLoadNoRemoveReplace, nameof(oldTree));
}
throw new ArgumentException(CSharpResources.SyntaxTreeNotFoundToRemove, 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.
// https://github.com/dotnet/roslyn/issues/43397
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));
try
{
return _syntaxAndDeclarations.GetLazyState().OrdinalMap[tree];
}
catch (KeyNotFoundException)
{
// Explicitly catching and re-throwing exception so we don't send the syntax
// tree (potentially containing private user information) to telemetry.
throw new KeyNotFoundException($"Syntax tree not found with file path: {tree.FilePath}");
}
}
internal OneOrMany<SyntaxTree> GetSyntaxTreesByMappedPath(string mappedPath)
{
// This method supports a "compat" behavior for interceptor file path resolution.
// It must be removed prior to stable release.
// We could consider storing this on SyntaxAndDeclarationManager instead, and updating it incrementally.
// However, this would make it more difficult for it to be "pay-for-play",
// i.e. only created in compilations where interceptors are used.
var mappedPathToSyntaxTree = _mappedPathToSyntaxTree;
if (mappedPathToSyntaxTree.IsDefault)
{
RoslynImmutableInterlocked.InterlockedInitialize(ref _mappedPathToSyntaxTree, computeMappedPathToSyntaxTree());
mappedPathToSyntaxTree = _mappedPathToSyntaxTree;
}
return mappedPathToSyntaxTree.TryGetValue(mappedPath, out var value) ? value : OneOrMany<SyntaxTree>.Empty;
ImmutableSegmentedDictionary<string, OneOrMany<SyntaxTree>> computeMappedPathToSyntaxTree()
{
var builder = ImmutableSegmentedDictionary.CreateBuilder<string, OneOrMany<SyntaxTree>>();
var resolver = Options.SourceReferenceResolver;
foreach (var tree in SyntaxTrees)
{
var path = resolver?.NormalizePath(tree.FilePath, baseFilePath: null) ?? tree.FilePath;
builder[path] = builder.ContainsKey(path) ? builder[path].Add(tree) : OneOrMany.Create(tree);
}
return builder.ToImmutable();
}
}
internal OneOrMany<SyntaxTree> GetSyntaxTreesByContentHash(ReadOnlyMemory<byte> contentHash)
{
Debug.Assert(contentHash.Length == InterceptableLocation1.ContentHashLength);
var contentHashToSyntaxTree = _contentHashToSyntaxTree;
if (contentHashToSyntaxTree.IsDefault)
{
RoslynImmutableInterlocked.InterlockedInitialize(ref _contentHashToSyntaxTree, computeHashToSyntaxTree());
contentHashToSyntaxTree = _contentHashToSyntaxTree;
}
return contentHashToSyntaxTree.TryGetValue(contentHash, out var value) ? value : OneOrMany<SyntaxTree>.Empty;
ImmutableSegmentedDictionary<ReadOnlyMemory<byte>, OneOrMany<SyntaxTree>> computeHashToSyntaxTree()
{
var builder = ImmutableSegmentedDictionary.CreateBuilder<ReadOnlyMemory<byte>, OneOrMany<SyntaxTree>>(ContentHashComparer.Instance);
foreach (var tree in SyntaxTrees)
{
var text = tree.GetText();
var hash = text.GetContentHash().AsMemory();
builder[hash] = builder.TryGetValue(hash, out var existing) ? existing.Add(tree) : OneOrMany.Create(tree);
}
return builder.ToImmutable();
}
}
internal OneOrMany<SyntaxTree> GetSyntaxTreesByPath(string path)
{
// We could consider storing this on SyntaxAndDeclarationManager instead, and updating it incrementally.
// However, this would make it more difficult for it to be "pay-for-play",
// i.e. only created in compilations where interceptors are used.
var pathToSyntaxTree = _pathToSyntaxTree;
if (pathToSyntaxTree.IsDefault)
{
RoslynImmutableInterlocked.InterlockedInitialize(ref _pathToSyntaxTree, computePathToSyntaxTree());
pathToSyntaxTree = _pathToSyntaxTree;
}
return pathToSyntaxTree.TryGetValue(path, out var value) ? value : OneOrMany<SyntaxTree>.Empty;
ImmutableSegmentedDictionary<string, OneOrMany<SyntaxTree>> computePathToSyntaxTree()
{
var builder = ImmutableSegmentedDictionary.CreateBuilder<string, OneOrMany<SyntaxTree>>();
foreach (var tree in SyntaxTrees)
{
var path = FileUtilities.GetNormalizedPathOrOriginalPath(tree.FilePath, basePath: null);
builder[path] = builder.ContainsKey(path) ? builder[path].Add(tree) : OneOrMany.Create(tree);
}
return builder.ToImmutable();
}
}
#endregion
#region References
internal override CommonReferenceManager CommonGetBoundReferenceManager()
{
return GetBoundReferenceManager();
}
internal new ReferenceManager GetBoundReferenceManager()
{
if (_lazyAssemblySymbol is null)
{
_referenceManager.CreateSourceAssemblyForCompilation(this);
Debug.Assert(_lazyAssemblySymbol is object);
}
// 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<MetadataReference> DirectiveReferences
{
get
{
return GetBoundReferenceManager().DirectiveReferences;
}
}
internal override IDictionary<(string path, string content), MetadataReference> ReferenceDirectiveMap
=> GetBoundReferenceManager().ReferenceDirectiveMap;
// for testing purposes
internal IEnumerable<string> ExternAliases
{
get
{
return GetBoundReferenceManager().ExternAliases;
}
}
/// <summary>
/// Gets the <see cref="AssemblySymbol"/> or <see cref="ModuleSymbol"/> for a metadata reference used to create this compilation.
/// </summary>
/// <returns><see cref="AssemblySymbol"/> or <see cref="ModuleSymbol"/> corresponding to the given reference or null if there is none.</returns>
/// <remarks>
/// Uses object identity when comparing two references.
/// </remarks>
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];
}
}
internal override TSymbol? GetSymbolInternal<TSymbol>(ISymbol? symbol) where TSymbol : class
{
return (TSymbol?)(object?)symbol.GetSymbol<Symbol>();
}
public override IEnumerable<AssemblyIdentity> ReferencedAssemblyNames
{
get
{
return Assembly.Modules.SelectMany(module => module.GetReferencedAssemblies());
}
}
/// <summary>
/// All reference directives used in this compilation.
/// </summary>
internal override IEnumerable<ReferenceDirective> ReferenceDirectives
{
get { return this.Declarations.ReferenceDirectives; }
}
/// <summary>
/// Returns a metadata reference that a given #r resolves to.
/// </summary>
/// <param name="directive">#r directive.</param>
/// <returns>Metadata reference the specified directive resolves to, or null if the <paramref name="directive"/> doesn't match any #r directive in the compilation.</returns>
public MetadataReference? GetDirectiveReference(ReferenceDirectiveTriviaSyntax directive)
{
RoslynDebug.Assert(directive.SyntaxTree.FilePath is object);
MetadataReference? reference;
return ReferenceDirectiveMap.TryGetValue((directive.SyntaxTree.FilePath, directive.File.ValueText), out reference) ? reference : null;
}
/// <summary>
/// Creates a new compilation with additional metadata references.
/// </summary>
public new CSharpCompilation AddReferences(params MetadataReference[] references)
{
return (CSharpCompilation)base.AddReferences(references);
}
/// <summary>
/// Creates a new compilation with additional metadata references.
/// </summary>
public new CSharpCompilation AddReferences(IEnumerable<MetadataReference> references)
{
return (CSharpCompilation)base.AddReferences(references);
}
/// <summary>
/// Creates a new compilation without the specified metadata references.
/// </summary>
public new CSharpCompilation RemoveReferences(params MetadataReference[] references)
{
return (CSharpCompilation)base.RemoveReferences(references);
}
/// <summary>
/// Creates a new compilation without the specified metadata references.
/// </summary>
public new CSharpCompilation RemoveReferences(IEnumerable<MetadataReference> references)
{
return (CSharpCompilation)base.RemoveReferences(references);
}
/// <summary>
/// Creates a new compilation without any metadata references
/// </summary>
public new CSharpCompilation RemoveAllReferences()
{
return (CSharpCompilation)base.RemoveAllReferences();
}
/// <summary>
/// Creates a new compilation with an old metadata reference replaced with a new metadata reference.
/// </summary>
public new CSharpCompilation ReplaceReference(MetadataReference oldReference, MetadataReference newReference)
{
return (CSharpCompilation)base.ReplaceReference(oldReference, newReference);
}
public override CompilationReference ToMetadataReference(ImmutableArray<string> aliases = default, bool embedInteropTypes = false)
{
return new CSharpCompilationReference(this, aliases, embedInteropTypes);
}
/// <summary>
/// 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.
/// </summary>
private void GetAllUnaliasedModules(ArrayBuilder<ModuleSymbol> 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);
}
}
}
/// <summary>
/// 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:Goo=A.dll /r:B.dll -> B
/// 3) /r:Goo=A.dll /r:A.dll -> A
/// </summary>
internal void GetUnaliasedReferencedAssemblies(ArrayBuilder<AssemblySymbol> assemblies)
{
var referenceManager = GetBoundReferenceManager();
int length = referenceManager.ReferencedAssemblies.Length;
assemblies.EnsureCapacity(assemblies.Count + length);
for (int i = 0; i < length; i++)
{
if (referenceManager.DeclarationsAccessibleWithoutAlias(i))
{
assemblies.Add(referenceManager.ReferencedAssemblies[i]);
}
}
}
/// <summary>
/// Gets the <see cref="MetadataReference"/> that corresponds to the assembly symbol.
/// </summary>
public new MetadataReference? GetMetadataReference(IAssemblySymbol assemblySymbol)
{
return base.GetMetadataReference(assemblySymbol);
}
private protected override MetadataReference? CommonGetMetadataReference(IAssemblySymbol assemblySymbol)
{
if (assemblySymbol is Symbols.PublicModel.AssemblySymbol { UnderlyingAssemblySymbol: var underlyingSymbol })
{
return GetMetadataReference(underlyingSymbol);
}
return null;
}
internal MetadataReference? GetMetadataReference(AssemblySymbol? assemblySymbol)
{
return GetBoundReferenceManager().GetMetadataReference(assemblySymbol);
}
#endregion
#region Symbols
/// <summary>
/// The AssemblySymbol that represents the assembly being created.
/// </summary>
internal SourceAssemblySymbol SourceAssembly
{
get
{
GetBoundReferenceManager();
RoslynDebug.Assert(_lazyAssemblySymbol is object);
return _lazyAssemblySymbol;
}
}
/// <summary>
/// The AssemblySymbol that represents the assembly being created.
/// </summary>
internal new AssemblySymbol Assembly
{
get
{
return SourceAssembly;
}
}
/// <summary>
/// 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.
/// </summary>
internal new ModuleSymbol SourceModule
{
get
{
return Assembly.Modules[0];
}
}
/// <summary>
/// Gets the root namespace that contains all namespaces and types defined in source code or in
/// referenced metadata, merged into a single namespace hierarchy.
/// </summary>
internal new NamespaceSymbol GlobalNamespace
{
get
{
if (_lazyGlobalNamespace is 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<ModuleSymbol>.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;
}
}
/// <summary>
/// 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.
/// </summary>
internal new NamespaceSymbol? GetCompilationNamespace(INamespaceSymbol namespaceSymbol)
{
if (namespaceSymbol is Symbols.PublicModel.NamespaceSymbol n &&
namespaceSymbol.NamespaceKind == NamespaceKind.Compilation &&
namespaceSymbol.ContainingCompilation == this)
{
return n.UnderlyingNamespaceSymbol;
}
var containingNamespace = namespaceSymbol.ContainingNamespace;
if (containingNamespace == null)
{
return this.GlobalNamespace;
}
var current = GetCompilationNamespace(containingNamespace);
if (current is object)
{
return current.GetNestedNamespace(namespaceSymbol.Name);
}
return null;
}
internal NamespaceSymbol? GetCompilationNamespace(NamespaceSymbol namespaceSymbol)
{
if (namespaceSymbol.NamespaceKind == NamespaceKind.Compilation &&
namespaceSymbol.ContainingCompilation == this)
{
return namespaceSymbol;
}
var containingNamespace = namespaceSymbol.ContainingNamespace;
if (containingNamespace == null)
{
return this.GlobalNamespace;
}
var current = GetCompilationNamespace(containingNamespace);
if (current is object)
{
return current.GetNestedNamespace(namespaceSymbol.Name);
}
return null;
}
private ConcurrentDictionary<string, NamespaceSymbol>? _externAliasTargets;
internal bool GetExternAliasTarget(string aliasName, out NamespaceSymbol @namespace)
{
if (_externAliasTargets == null)
{
Interlocked.CompareExchange(ref _externAliasTargets, new ConcurrentDictionary<string, NamespaceSymbol>(), null);
}
else if (_externAliasTargets.TryGetValue(aliasName, out var cached))
{
@namespace = cached;
return !(@namespace is MissingNamespaceSymbol);
}
ArrayBuilder<NamespaceSymbol>? builder = null;
var referenceManager = GetBoundReferenceManager();
for (int i = 0; i < referenceManager.ReferencedAssemblies.Length; i++)
{
if (referenceManager.AliasesOfReferencedAssemblies[i].Contains(aliasName))
{
builder = builder ?? ArrayBuilder<NamespaceSymbol>.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;
}
/// <summary>
/// A symbol representing the implicit Script class. This is null if the class is not
/// defined in the compilation.
/// </summary>
internal new NamedTypeSymbol? ScriptClass
{
get
{
if (ReferenceEquals(_lazyScriptClass, ErrorTypeSymbol.UnknownResultType))
{
Interlocked.CompareExchange(ref _lazyScriptClass, BindScriptClass()!, ErrorTypeSymbol.UnknownResultType);
}
return _lazyScriptClass;
}
}
/// <summary>
/// Resolves a symbol that represents script container (Script class). Uses the
/// full name of the container class stored in <see cref="CompilationOptions.ScriptClassName"/> to find the symbol.
/// </summary>
/// <returns>The Script class symbol or null if it is not defined.</returns>
private ImplicitNamedTypeSymbol? BindScriptClass()
{
return (ImplicitNamedTypeSymbol?)CommonBindScriptClass().GetSymbol();
}
internal bool IsSubmissionSyntaxTree(SyntaxTree tree)
{
Debug.Assert(tree != null);
Debug.Assert(!this.IsSubmission || _syntaxAndDeclarations.ExternalSyntaxTrees.Length <= 1);
return this.IsSubmission && tree == _syntaxAndDeclarations.ExternalSyntaxTrees.SingleOrDefault();
}
/// <summary>
/// Global imports (including those from previous submissions, if there are any).
/// </summary>
internal ImmutableArray<NamespaceOrTypeAndUsingDirective> GlobalImports
=> InterlockedOperations.Initialize(ref _lazyGlobalImports, static self => self.BindGlobalImports(), arg: this);
private ImmutableArray<NamespaceOrTypeAndUsingDirective> BindGlobalImports()
{
var usingsFromoptions = UsingsFromOptions;
var previousSubmission = PreviousSubmission;
var previousSubmissionImports = previousSubmission is object ? Imports.ExpandPreviousSubmissionImports(previousSubmission.GlobalImports, this) : ImmutableArray<NamespaceOrTypeAndUsingDirective>.Empty;
if (usingsFromoptions.UsingNamespacesOrTypes.IsEmpty)
{
return previousSubmissionImports;
}
else if (previousSubmissionImports.IsEmpty)
{
return usingsFromoptions.UsingNamespacesOrTypes;
}
var boundUsings = ArrayBuilder<NamespaceOrTypeAndUsingDirective>.GetInstance();
var uniqueUsings = PooledHashSet<NamespaceOrTypeSymbol>.GetInstance();
boundUsings.AddRange(usingsFromoptions.UsingNamespacesOrTypes);
uniqueUsings.AddAll(usingsFromoptions.UsingNamespacesOrTypes.Select(static unt => unt.NamespaceOrType));
foreach (var previousUsing in previousSubmissionImports)
{
if (uniqueUsings.Add(previousUsing.NamespaceOrType))
{
boundUsings.Add(previousUsing);
}
}
uniqueUsings.Free();
return boundUsings.ToImmutableAndFree();
}
/// <summary>
/// Global imports not including those from previous submissions.
/// </summary>
private UsingsFromOptionsAndDiagnostics UsingsFromOptions
=> InterlockedOperations.Initialize(ref _lazyUsingsFromOptions, static self => self.BindUsingsFromOptions(), this);
private UsingsFromOptionsAndDiagnostics BindUsingsFromOptions() => UsingsFromOptionsAndDiagnostics.FromOptions(this);
/// <summary>
/// Imports declared by this submission (null if this isn't one).
/// </summary>
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;
}
return ((SourceNamespaceSymbol)SourceModule.GlobalNamespace).GetImports((CSharpSyntaxNode)tree.GetRoot(), basesBeingResolved: null);
}
/// <summary>
/// Imports from all previous submissions.
/// </summary>
internal Imports GetPreviousSubmissionImports()
=> InterlockedOperations.Initialize(ref _lazyPreviousSubmissionImports, static self => self.ExpandPreviousSubmissionImports(), this);
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 InterlockedOperations.Initialize(ref _lazyGlobalNamespaceAlias, static self => self.CreateGlobalNamespaceAlias(), this);
}
}
/// <summary>
/// Get the symbol for the predefined type from the COR Library referenced by this compilation.
/// </summary>
internal NamedTypeSymbol GetSpecialType(ExtendedSpecialType specialType)
{
if ((int)specialType <= (int)SpecialType.None || (int)specialType >= (int)InternalSpecialType.NextAvailable)
{
throw new ArgumentOutOfRangeException(nameof(specialType), $"Unexpected SpecialType: '{(int)specialType}'.");
}
NamedTypeSymbol result;
if (IsTypeMissing(specialType))
{
MetadataTypeName emittedName = MetadataTypeName.FromFullName(specialType.GetMetadataName(), useCLSCompliantNameArityEncoding: true);
result = new MissingMetadataTypeSymbol.TopLevel(Assembly.CorLibrary.Modules[0], ref emittedName, specialType);
}
else
{
result = Assembly.GetSpecialType(specialType);
}
Debug.Assert(result.ExtendedSpecialType == specialType);
return result;
}
private ConcurrentCache<TypeSymbol, NamedTypeSymbol> TypeToNullableVersion
{
get
{
return InterlockedOperations.Initialize(ref _lazyTypeToNullableVersion, static () => new ConcurrentCache<TypeSymbol, NamedTypeSymbol>(size: 100));
}
}
/// <summary>
/// Given a provided <paramref name="typeArgument"/>, gives back <see cref="Nullable{T}"/> constructed with that
/// argument. This function is only intended to be used for very common instantiations produced heavily during
/// binding. Specifically, the nullable versions of enums, and the nullable versions of core built-ins. So
/// many of these are created that it's worthwhile to cache, keeping overall garbage low, while not ballooning
/// the size of the cache itself.
/// </summary>
internal NamedTypeSymbol GetOrCreateNullableType(TypeSymbol typeArgument)
{
#if DEBUG
if (!isSupportedType(typeArgument))
Debug.Fail($"Unsupported type argument: {typeArgument.ToDisplayString()}");
#endif
var typeToNullableVersion = TypeToNullableVersion;
if (!typeToNullableVersion.TryGetValue(typeArgument, out var constructedNullableInstance))
{
constructedNullableInstance = this.GetSpecialType(SpecialType.System_Nullable_T).Construct(typeArgument);
typeToNullableVersion.TryAdd(typeArgument, constructedNullableInstance);
}
return constructedNullableInstance;
#if DEBUG
static bool isSupportedType(TypeSymbol typeArgument)
{
if (typeArgument.IsEnumType())
return true;
switch (typeArgument.SpecialType)
{
case SpecialType.System_SByte:
case SpecialType.System_Byte:
case SpecialType.System_Int16:
case SpecialType.System_UInt16:
case SpecialType.System_Int32:
case SpecialType.System_UInt32:
case SpecialType.System_Int64:
case SpecialType.System_UInt64:
case SpecialType.System_Char:
case SpecialType.System_Single:
case SpecialType.System_Double:
case SpecialType.System_Decimal:
case SpecialType.System_Boolean:
return true;
}
if (typeArgument.IsNativeIntegerType)
return true;
return false;
}
#endif
}
/// <summary>
/// Get the symbol for the predefined type member from the COR Library referenced by this compilation.
/// </summary>
internal Symbol GetSpecialTypeMember(SpecialMember specialMember)
{
return Assembly.GetSpecialTypeMember(specialMember);
}
internal override ISymbolInternal CommonGetSpecialTypeMember(SpecialMember specialMember)
{
return GetSpecialTypeMember(specialMember);
}
internal TypeSymbol GetTypeByReflectionType(Type type, BindingDiagnosticBag diagnostics)
{
var result = Assembly.GetTypeByReflectionType(type);
if (result is 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<Symbol>.Empty,
ImmutableArray<Location>.Empty
);
}
protected override ITypeSymbol? CommonScriptGlobalsType
=> GetHostObjectTypeSymbol()?.GetPublicSymbol();
internal TypeSymbol? GetHostObjectTypeSymbol()
{
if (HostObjectType != null && _lazyHostObjectTypeSymbol is null)
{
TypeSymbol? symbol = Assembly.GetTypeByReflectionType(HostObjectType);
if (symbol is null)
{
MetadataTypeName mdName = MetadataTypeName.FromNamespaceAndTypeName(HostObjectType.Namespace ?? String.Empty,
HostObjectType.Name,
useCLSCompliantNameArityEncoding: true);
symbol = new MissingMetadataTypeSymbol.TopLevel(
new MissingAssemblySymbol(AssemblyIdentity.FromAssemblyDefinition(HostObjectType.GetTypeInfo().Assembly)).Modules[0],
ref mdName,
SpecialType.None,
CreateReflectionTypeNotFoundError(HostObjectType));
}
Interlocked.CompareExchange(ref _lazyHostObjectTypeSymbol, symbol, null);
}
return _lazyHostObjectTypeSymbol;
}
internal SynthesizedInteractiveInitializerMethod? GetSubmissionInitializer()
{
return (IsSubmission && ScriptClass is object) ?
ScriptClass.GetScriptInitializer() :
null;
}
/// <summary>
/// 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.
/// </summary>
internal new NamedTypeSymbol? GetTypeByMetadataName(string fullyQualifiedMetadataName)
{
var result = this.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName, includeReferences: true, isWellKnownType: false, conflicts: out var _);
Debug.Assert(result?.IsErrorType() != true);
return result;
}
/// <summary>
/// The TypeSymbol for the type 'dynamic' in this Compilation.
/// </summary>
internal new TypeSymbol DynamicType
{
get
{
return AssemblySymbol.DynamicType;
}
}
/// <summary>
/// The NamedTypeSymbol for the .NET System.Object type, which could have a TypeKind of
/// Error if there was no COR Library in this Compilation.
/// </summary>
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.MethodSymbol;
}
internal EntryPoint GetEntryPointAndDiagnostics(CancellationToken cancellationToken)
{
if (_lazyEntryPoint == null)
{
EntryPoint? entryPoint;
var simpleProgramEntryPointSymbol = SynthesizedSimpleProgramEntryPointSymbol.GetSimpleProgramEntryPoint(this);
if (!this.Options.OutputKind.IsApplication() && (this.ScriptClass is null))
{
if (simpleProgramEntryPointSymbol is object)
{
var diagnostics = BindingDiagnosticBag.GetInstance();
diagnostics.Add(ErrorCode.ERR_SimpleProgramNotAnExecutable, simpleProgramEntryPointSymbol.ReturnTypeSyntax.Location);
entryPoint = new EntryPoint(null, diagnostics.ToReadOnlyAndFree());
}
else
{
entryPoint = EntryPoint.None;
}
}
else
{
entryPoint = null;
if (this.Options.MainTypeName != null && !this.Options.MainTypeName.IsValidClrTypeName())
{
Debug.Assert(!this.Options.Errors.IsDefaultOrEmpty);
entryPoint = EntryPoint.None;
}
if (entryPoint is null)
{
ReadOnlyBindingDiagnostic<AssemblySymbol> diagnostics;
var entryPointMethod = FindEntryPoint(simpleProgramEntryPointSymbol, cancellationToken, out diagnostics);
entryPoint = new EntryPoint(entryPointMethod, diagnostics);
}
if (this.Options.MainTypeName != null && simpleProgramEntryPointSymbol is object)
{
var diagnostics = DiagnosticBag.GetInstance();
diagnostics.Add(ErrorCode.ERR_SimpleProgramDisallowsMainType, NoLocation.Singleton);
entryPoint = new EntryPoint(entryPoint.MethodSymbol,
new ReadOnlyBindingDiagnostic<AssemblySymbol>(
entryPoint.Diagnostics.Diagnostics.Concat(diagnostics.ToReadOnlyAndFree()), entryPoint.Diagnostics.Dependencies));
}
}
Interlocked.CompareExchange(ref _lazyEntryPoint, entryPoint, null);
}
return _lazyEntryPoint;
}
private MethodSymbol? FindEntryPoint(MethodSymbol? simpleProgramEntryPointSymbol, CancellationToken cancellationToken, out ReadOnlyBindingDiagnostic<AssemblySymbol> sealedDiagnostics)
{
var diagnostics = BindingDiagnosticBag.GetInstance();
RoslynDebug.Assert(diagnostics.DiagnosticBag is object);
var entryPointCandidates = ArrayBuilder<MethodSymbol>.GetInstance();
try
{
NamedTypeSymbol? mainType;
string? mainTypeName = this.Options.MainTypeName;
NamespaceSymbol globalNamespace = this.SourceModule.GlobalNamespace;
var scriptClass = this.ScriptClass;
if (mainTypeName != null)
{
// Global code is the entry point, ignore all other Mains.
if (scriptClass is object)
{
// 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 (mainTypeOrNamespace is null)
{
diagnostics.Add(ErrorCode.ERR_MainClassNotFound, NoLocation.Singleton, mainTypeName);
return null;
}
mainType = mainTypeOrNamespace as NamedTypeSymbol;
if (mainType is null || mainType.IsGenericType || (mainType.TypeKind != TypeKind.Class && mainType.TypeKind != TypeKind.Struct && !mainType.IsInterface))
{
diagnostics.Add(ErrorCode.ERR_MainClassNotClass, mainTypeOrNamespace.GetFirstLocation(), mainTypeOrNamespace);
return null;
}
AddEntryPointCandidates(entryPointCandidates, mainType.GetMembersUnordered());
}
else
{
mainType = null;
AddEntryPointCandidates(
entryPointCandidates,
this.GetSymbolsWithNameCore(WellKnownMemberNames.EntryPointMethodName, SymbolFilter.Member, cancellationToken));
// Global code is the entry point, ignore all other Mains.
if (scriptClass is object || simpleProgramEntryPointSymbol is object)
{
foreach (var main in entryPointCandidates)
{
if (main is not SynthesizedSimpleProgramEntryPointSymbol)
{
diagnostics.Add(ErrorCode.WRN_MainIgnored, main.GetFirstLocation(), main);
}
}
if (scriptClass is object)
{
return scriptClass.GetScriptEntryPoint();
}
RoslynDebug.Assert(simpleProgramEntryPointSymbol is object);
entryPointCandidates.Clear();
entryPointCandidates.Add(simpleProgramEntryPointSymbol);
}
}
// Validity and diagnostics are also tracked because they must be conditionally handled
// if there are not any "traditional" entrypoints found.
var taskEntryPoints = ArrayBuilder<(bool IsValid, MethodSymbol Candidate, BindingDiagnosticBag SpecificDiagnostics)>.GetInstance();
// These diagnostics (warning only) are added to the compilation only if
// there were not any main methods found.
var noMainFoundDiagnostics = BindingDiagnosticBag.GetInstance(diagnostics);
RoslynDebug.Assert(noMainFoundDiagnostics.DiagnosticBag is object);
bool checkValid(MethodSymbol candidate, bool isCandidate, BindingDiagnosticBag specificDiagnostics)
{
if (!isCandidate)
{
noMainFoundDiagnostics.Add(ErrorCode.WRN_InvalidMainSig, candidate.GetFirstLocation(), candidate);
noMainFoundDiagnostics.AddRange(specificDiagnostics);
return false;
}
if (candidate.IsGenericMethod || candidate.ContainingType.IsGenericType)
{
// a single error for partial methods:
noMainFoundDiagnostics.Add(ErrorCode.WRN_MainCantBeGeneric, candidate.GetFirstLocation(), candidate);
return false;
}
return true;
}
var viableEntryPoints = ArrayBuilder<MethodSymbol>.GetInstance();
foreach (var candidate in entryPointCandidates)
{
var perCandidateBag = BindingDiagnosticBag.GetInstance(diagnostics);
var (IsCandidate, IsTaskLike) = HasEntryPointSignature(candidate, perCandidateBag);
if (IsTaskLike)
{
taskEntryPoints.Add((IsCandidate, candidate, perCandidateBag));
}
else
{
if (checkValid(candidate, IsCandidate, perCandidateBag))
{
if (candidate.IsAsync)
{
diagnostics.Add(ErrorCode.ERR_NonTaskMainCantBeAsync, candidate.GetFirstLocation());
}
else
{
diagnostics.AddRange(perCandidateBag);
viableEntryPoints.Add(candidate);
}
}
perCandidateBag.Free();
}
}
if (viableEntryPoints.Count == 0)
{
foreach (var (IsValid, Candidate, SpecificDiagnostics) in taskEntryPoints)
{
if (checkValid(Candidate, IsValid, SpecificDiagnostics) &&
CheckFeatureAvailability(Candidate.ExtractReturnTypeSyntax(), MessageID.IDS_FeatureAsyncMain, diagnostics))
{
diagnostics.AddRange(SpecificDiagnostics);
viableEntryPoints.Add(Candidate);
}
}
}
else if (LanguageVersion >= MessageID.IDS_FeatureAsyncMain.RequiredVersion() && taskEntryPoints.Count > 0)
{
var taskCandidates = taskEntryPoints.SelectAsArray(s => (Symbol)s.Candidate);
var taskLocations = taskCandidates.SelectAsArray(s => s.GetFirstLocation());
foreach (var candidate in taskCandidates)
{
// Method '{0}' will not be used as an entry point because a synchronous entry point '{1}' was found.
var info = new CSDiagnosticInfo(
ErrorCode.WRN_SyncAndAsyncEntryPoints,
args: new object[] { candidate, viableEntryPoints[0] },
symbols: taskCandidates,
additionalLocations: taskLocations);
diagnostics.Add(new CSDiagnostic(info, candidate.GetFirstLocation()));
}
}
foreach (var (_, _, SpecificDiagnostics) in taskEntryPoints)
{
SpecificDiagnostics.Free();
}
if (viableEntryPoints.Count == 0)
{
diagnostics.AddRange(noMainFoundDiagnostics);
}
else if (mainType is null)
{
// Filters out diagnostics so that only InvalidMainSig and MainCant'BeGeneric are left.
// The reason that Error diagnostics can end up in `noMainFoundDiagnostics` is when
// HasEntryPointSignature yields some Error Diagnostics when people implement Task or Task<T> incorrectly.
//
// We can't add those Errors to the general diagnostics bag because it would break previously-working programs.
// The fact that these warnings are not added when csc is invoked with /main is possibly a bug, and is tracked at
// https://github.com/dotnet/roslyn/issues/18964
foreach (var diagnostic in noMainFoundDiagnostics.DiagnosticBag.AsEnumerable())
{
if (diagnostic.Code == (int)ErrorCode.WRN_InvalidMainSig || diagnostic.Code == (int)ErrorCode.WRN_MainCantBeGeneric)
{
diagnostics.Add(diagnostic);
}
}
diagnostics.AddDependencies(noMainFoundDiagnostics);
}
MethodSymbol? entryPoint = null;
if (viableEntryPoints.Count == 0)
{
if (mainType is null)
{
diagnostics.Add(ErrorCode.ERR_NoEntryPoint, NoLocation.Singleton);
}
else
{
diagnostics.Add(ErrorCode.ERR_NoMainInClass, mainType.GetFirstLocation(), mainType);
}
}
else
{
foreach (var viableEntryPoint in viableEntryPoints)
{
if (viableEntryPoint.GetUnmanagedCallersOnlyAttributeData(forceComplete: true) is { } data)
{
Debug.Assert(!ReferenceEquals(data, UnmanagedCallersOnlyAttributeData.Uninitialized));
Debug.Assert(!ReferenceEquals(data, UnmanagedCallersOnlyAttributeData.AttributePresentDataNotBound));
diagnostics.Add(ErrorCode.ERR_EntryPointCannotBeUnmanagedCallersOnly, viableEntryPoint.GetFirstLocation());
}
}
if (viableEntryPoints.Count > 1)
{
viableEntryPoints.Sort(LexicalOrderSymbolComparer.Instance);
var info = new CSDiagnosticInfo(
ErrorCode.ERR_MultipleEntryPoints,
args: Array.Empty<object>(),
symbols: viableEntryPoints.OfType<Symbol>().AsImmutable(),
additionalLocations: viableEntryPoints.Select(m => m.GetFirstLocation()).OfType<Location>().AsImmutable());
diagnostics.Add(new CSDiagnostic(info, viableEntryPoints.First().GetFirstLocation()));
}
else
{
entryPoint = viableEntryPoints[0];
}
}
taskEntryPoints.Free();
viableEntryPoints.Free();
noMainFoundDiagnostics.Free();
return entryPoint;
}
finally
{
entryPointCandidates.Free();
sealedDiagnostics = diagnostics.ToReadOnlyAndFree();
}
}
private static void AddEntryPointCandidates(
ArrayBuilder<MethodSymbol> entryPointCandidates, IEnumerable<Symbol> members)
{
foreach (var member in members)
{
if (member is MethodSymbol method &&
method.IsEntryPointCandidate)
{
entryPointCandidates.Add(method);
}
}
}
internal bool ReturnsAwaitableToVoidOrInt(MethodSymbol method, BindingDiagnosticBag diagnostics)
{
// Common case optimization
if (method.ReturnType.IsVoidType() || method.ReturnType.SpecialType == SpecialType.System_Int32)
{
return false;
}
if (!(method.ReturnType is NamedTypeSymbol namedType))
{
return false;
}
// Early bail so we only ever check things that are System.Threading.Tasks.Task(<T>)
if (!(TypeSymbol.Equals(namedType.ConstructedFrom, GetWellKnownType(WellKnownType.System_Threading_Tasks_Task), TypeCompareKind.ConsiderEverything2) ||
TypeSymbol.Equals(namedType.ConstructedFrom, GetWellKnownType(WellKnownType.System_Threading_Tasks_Task_T), TypeCompareKind.ConsiderEverything2)))
{
return false;
}
var syntax = method.ExtractReturnTypeSyntax();
var dumbInstance = new BoundLiteral(syntax, ConstantValue.Null, namedType);
var binder = GetBinder(syntax);
BoundExpression? result;
var success = binder.GetAwaitableExpressionInfo(dumbInstance, out result, syntax, diagnostics);
RoslynDebug.Assert(!namedType.IsDynamic());
return success &&
(result!.Type!.IsVoidType() || result.Type!.SpecialType == SpecialType.System_Int32);
}
/// <summary>
/// Checks if the method has an entry point compatible signature, i.e.
/// - the return type is either void, int, or returns a <see cref="System.Threading.Tasks.Task" />,
/// or <see cref="System.Threading.Tasks.Task{T}" /> where the return type of GetAwaiter().GetResult()
/// is either void or int.
/// - has either no parameter or a single parameter of type string[]
/// </summary>
internal (bool IsCandidate, bool IsTaskLike) HasEntryPointSignature(MethodSymbol method, BindingDiagnosticBag bag)
{
if (method.IsVararg)
{
return (false, false);
}
TypeSymbol returnType = method.ReturnType;
bool returnsTaskOrTaskOfInt = false;
if (returnType.SpecialType != SpecialType.System_Int32 && !returnType.IsVoidType())
{
// Never look for ReturnsAwaitableToVoidOrInt on int32 or void
returnsTaskOrTaskOfInt = ReturnsAwaitableToVoidOrInt(method, bag);
if (!returnsTaskOrTaskOfInt)
{
return (false, false);
}
}
if (method.RefKind != RefKind.None)
{
return (false, returnsTaskOrTaskOfInt);
}
if (method.Parameters.Length == 0)
{
return (true, returnsTaskOrTaskOfInt);
}
if (method.Parameters.Length > 1)
{
return (false, returnsTaskOrTaskOfInt);
}
if (!method.ParameterRefKinds.IsDefault)
{
return (false, returnsTaskOrTaskOfInt);
}
var firstType = method.Parameters[0].TypeWithAnnotations;
if (firstType.TypeKind != TypeKind.Array)
{
return (false, returnsTaskOrTaskOfInt);
}
var array = (ArrayTypeSymbol)firstType.Type;
return (array.IsSZArray && array.ElementType.SpecialType == SpecialType.System_String, returnsTaskOrTaskOfInt);
}
internal override bool IsUnreferencedAssemblyIdentityDiagnosticCode(int code)
=> code == (int)ErrorCode.ERR_NoTypeDef;
internal class EntryPoint
{
public readonly MethodSymbol? MethodSymbol;
public readonly ReadOnlyBindingDiagnostic<AssemblySymbol> Diagnostics;
public static readonly EntryPoint None = new EntryPoint(null, ReadOnlyBindingDiagnostic<AssemblySymbol>.Empty);
public EntryPoint(MethodSymbol? methodSymbol, ReadOnlyBindingDiagnostic<AssemblySymbol> diagnostics)
{
this.MethodSymbol = methodSymbol;
this.Diagnostics = diagnostics;
}
}
internal bool MightContainNoPiaLocalTypes()
{
return SourceAssembly.MightContainNoPiaLocalTypes();
}
// NOTE(cyrusn): There is a bit of a discoverability problem with this method and the same
// named method in SyntaxTreeSemanticModel. Technically, i believe these are the appropriate
// locations for these methods. This method has no dependencies on anything but the
// compilation, while the other method needs a bindings object to determine what bound node
// an expression syntax binds to. Perhaps when we document these methods we should explain
// where a user can find the other.
/// <summary>
/// Classifies a conversion from <paramref name="source"/> to <paramref name="destination"/>.
/// </summary>
/// <param name="source">Source type of value to be converted</param>
/// <param name="destination">Destination type of value to be converted</param>
/// <returns>A <see cref="Conversion"/> that classifies the conversion from the
/// <paramref name="source"/> type to the <paramref name="destination"/> type.</returns>
public Conversion ClassifyConversion(ITypeSymbol source, ITypeSymbol destination)
{
// https://github.com/dotnet/roslyn/issues/60397 : Add an API with ability to specify isChecked?
// Note that it is possible for there to be both an implicit user-defined conversion
// and an explicit built-in conversion from source to destination. In that scenario
// this method returns the implicit conversion.
if ((object)source == null)
{
throw new ArgumentNullException(nameof(source));
}
if ((object)destination == null)
{
throw new ArgumentNullException(nameof(destination));
}
TypeSymbol? cssource = source.EnsureCSharpSymbolOrNull(nameof(source));
TypeSymbol? csdest = destination.EnsureCSharpSymbolOrNull(nameof(destination));
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
return Conversions.ClassifyConversionFromType(cssource, csdest, isChecked: false, ref discardedUseSiteInfo);
}
/// <summary>
/// Classifies a conversion from <paramref name="source"/> to <paramref name="destination"/> according
/// to this compilation's programming language.
/// </summary>
/// <param name="source">Source type of value to be converted</param>
/// <param name="destination">Destination type of value to be converted</param>
/// <returns>A <see cref="CommonConversion"/> that classifies the conversion from the
/// <paramref name="source"/> type to the <paramref name="destination"/> type.</returns>
public override CommonConversion ClassifyCommonConversion(ITypeSymbol source, ITypeSymbol destination)
{
// https://github.com/dotnet/roslyn/issues/60397 : Add an API with ability to specify isChecked?
return ClassifyConversion(source, destination).ToCommonConversion();
}
internal override IConvertibleConversion ClassifyConvertibleConversion(IOperation source, ITypeSymbol? destination, out ConstantValue? constantValue)
{
constantValue = null;
if (destination is null)
{
return Conversion.NoConversion;
}
ITypeSymbol? sourceType = source.Type;
ConstantValue? sourceConstantValue = source.GetConstantValue();
if (sourceType is null)
{
if (sourceConstantValue is { IsNull: true } && destination.IsReferenceType)
{
constantValue = sourceConstantValue;
return Conversion.NullLiteral;
}
return Conversion.NoConversion;
}
Conversion result = ClassifyConversion(sourceType, destination);
if (result.IsReference && sourceConstantValue is { IsNull: true })
{
constantValue = sourceConstantValue;
}
return result;
}
/// <summary>
/// Returns a new ArrayTypeSymbol representing an array type tied to the base types of the
/// COR Library in this Compilation.
/// </summary>
internal ArrayTypeSymbol CreateArrayTypeSymbol(TypeSymbol elementType, int rank = 1, NullableAnnotation elementNullableAnnotation = NullableAnnotation.Oblivious)
{
if ((object)elementType == null)
{
throw new ArgumentNullException(nameof(elementType));
}
if (rank < 1)
{
throw new ArgumentException(nameof(rank));
}
return ArrayTypeSymbol.CreateCSharpArray(this.Assembly, TypeWithAnnotations.Create(elementType, elementNullableAnnotation), rank);
}
/// <summary>
/// Returns a new PointerTypeSymbol representing a pointer type tied to a type in this Compilation.
/// </summary>
internal PointerTypeSymbol CreatePointerTypeSymbol(TypeSymbol elementType, NullableAnnotation elementNullableAnnotation = NullableAnnotation.Oblivious)
{
if ((object)elementType == null)
{
throw new ArgumentNullException(nameof(elementType));
}
return new PointerTypeSymbol(TypeWithAnnotations.Create(elementType, elementNullableAnnotation));
}
private protected override bool IsSymbolAccessibleWithinCore(
ISymbol symbol,
ISymbol within,
ITypeSymbol? throughType)
{
Symbol? symbol0 = symbol.EnsureCSharpSymbolOrNull(nameof(symbol));
Symbol? within0 = within.EnsureCSharpSymbolOrNull(nameof(within));
TypeSymbol? throughType0 = throughType.EnsureCSharpSymbolOrNull(nameof(throughType));
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
return
within0.Kind == SymbolKind.Assembly ?
AccessCheck.IsSymbolAccessible(symbol0, (AssemblySymbol)within0, ref discardedUseSiteInfo) :
AccessCheck.IsSymbolAccessible(symbol0, (NamedTypeSymbol)within0, ref discardedUseSiteInfo, throughType0);
}
[Obsolete("Compilation.IsSymbolAccessibleWithin is not designed for use within the compilers", true)]
internal new bool IsSymbolAccessibleWithin(
ISymbol symbol,
ISymbol within,
ITypeSymbol? throughType = null)
{
throw new NotImplementedException();
}
private ConcurrentSet<MethodSymbol>? _moduleInitializerMethods;
internal void AddModuleInitializerMethod(MethodSymbol method)
{
Debug.Assert(!_declarationDiagnosticsFrozen);
LazyInitializer.EnsureInitialized(ref _moduleInitializerMethods).Add(method);
}
internal bool InterceptorsDiscoveryComplete;
// NB: the 'Many' case for these dictionary values means there are duplicates. An error is reported for this after binding.
private ConcurrentDictionary<(string FilePath, int Position), OneOrMany<(Location AttributeLocation, MethodSymbol Interceptor)>>? _interceptions;
internal void AddInterception(string filePath, int position, Location attributeLocation, MethodSymbol interceptor)
{
Debug.Assert(!_declarationDiagnosticsFrozen);
Debug.Assert(!InterceptorsDiscoveryComplete);
var dictionary = LazyInitializer.EnsureInitialized(ref _interceptions);
dictionary.AddOrUpdate((filePath, position),
addValueFactory: static (key, newValue) => OneOrMany.Create(newValue),
updateValueFactory: static (key, existingValues, newValue) =>
{
// AddInterception can be called when attributes are decoded on a symbol, which can happen for the same symbol concurrently.
// If something else has already added the interceptor denoted by a given `[InterceptsLocation]`, we want to drop it.
// Since the collection is almost always length 1, a simple foreach is adequate for detecting this.
foreach (var (attributeLocation, interceptor) in existingValues)
{
if (attributeLocation == newValue.AttributeLocation && interceptor.Equals(newValue.Interceptor, TypeCompareKind.ConsiderEverything))
{
return existingValues;
}
}
return existingValues.Add(newValue);
},
// Explicit tuple element names are needed here so that the names unify when this is an extension method call (netstandard2.0).
factoryArgument: (AttributeLocation: attributeLocation, Interceptor: interceptor));
}
internal (Location AttributeLocation, MethodSymbol Interceptor)? TryGetInterceptor(SimpleNameSyntax? node)
{
if (node is null)
{
return null;
}
((SourceModuleSymbol)SourceModule).DiscoverInterceptorsIfNeeded();
if (_interceptions is null)
{
return null;
}
var key = (node.SyntaxTree.FilePath, node.Position);
if (_interceptions.TryGetValue(key, out var interceptionsAtAGivenLocation) && interceptionsAtAGivenLocation is [var oneInterception])
{
return oneInterception;
}
return null;
}
#endregion
#region Binding
public new SemanticModel GetSemanticModel(SyntaxTree syntaxTree, bool ignoreAccessibility)
#pragma warning disable RSEXPERIMENTAL001 // Internal usage of experimental API
=> GetSemanticModel(syntaxTree, ignoreAccessibility ? SemanticModelOptions.IgnoreAccessibility : SemanticModelOptions.None);
#pragma warning restore RSEXPERIMENTAL001
/// <summary>
/// Gets a new SyntaxTreeSemanticModel for the specified syntax tree.
/// </summary>
[Experimental(RoslynExperiments.NullableDisabledSemanticModel, UrlFormat = RoslynExperiments.NullableDisabledSemanticModel_Url)]
public new SemanticModel GetSemanticModel(SyntaxTree syntaxTree, SemanticModelOptions options)
{
if (syntaxTree == null)
{
throw new ArgumentNullException(nameof(syntaxTree));
}
if (!_syntaxAndDeclarations.GetLazyState().RootNamespaces.ContainsKey(syntaxTree))
{
throw new ArgumentException(CSharpResources.SyntaxTreeNotFound, nameof(syntaxTree));
}
SemanticModel? model = null;
if (SemanticModelProvider != null)
{
model = SemanticModelProvider.GetSemanticModel(syntaxTree, this, options);
Debug.Assert(model != null);
}
return model ?? CreateSemanticModel(syntaxTree, options);
}
#pragma warning disable RSEXPERIMENTAL001 // Internal usage of experimental API
internal override SemanticModel CreateSemanticModel(SyntaxTree syntaxTree, SemanticModelOptions options)
=> new SyntaxTreeSemanticModel(this, syntaxTree, options);
#pragma warning restore RSEXPERIMENTAL001
// When building symbols from the declaration table (lazily), or inside a type, or when
// compiling a method body, we may not have a BinderContext in hand for the enclosing
// scopes. Therefore, we build them when needed (and cache them) using a ContextBuilder.
// Since a ContextBuilder is only a cache, and the identity of the ContextBuilders and
// BinderContexts have no semantic meaning, we can reuse them or rebuild them, whichever is
// most convenient. We store them using weak references so that GC pressure will cause them
// to be recycled.
private WeakReference<BinderFactory>[]? _binderFactories;
private WeakReference<BinderFactory>[]? _ignoreAccessibilityBinderFactories;
internal BinderFactory GetBinderFactory(SyntaxTree syntaxTree, bool ignoreAccessibility = false)
{
if (ignoreAccessibility && SynthesizedSimpleProgramEntryPointSymbol.GetSimpleProgramEntryPoint(this) is object)
{
return GetBinderFactory(syntaxTree, ignoreAccessibility: true, ref _ignoreAccessibilityBinderFactories);
}
return GetBinderFactory(syntaxTree, ignoreAccessibility: false, ref _binderFactories);
}
private BinderFactory GetBinderFactory(SyntaxTree syntaxTree, bool ignoreAccessibility, ref WeakReference<BinderFactory>[]? cachedBinderFactories)
{
Debug.Assert(System.Runtime.CompilerServices.Unsafe.AreSame(ref cachedBinderFactories, ref ignoreAccessibility ? ref _ignoreAccessibilityBinderFactories : ref _binderFactories));
var treeNum = GetSyntaxTreeOrdinal(syntaxTree);
WeakReference<BinderFactory>[]? binderFactories = cachedBinderFactories;
if (binderFactories == null)
{
binderFactories = new WeakReference<BinderFactory>[this.SyntaxTrees.Length];
binderFactories = Interlocked.CompareExchange(ref cachedBinderFactories, binderFactories, null) ?? binderFactories;
}
BinderFactory? previousFactory;
var previousWeakReference = binderFactories[treeNum];
if (previousWeakReference != null && previousWeakReference.TryGetTarget(out previousFactory))
{
return previousFactory;
}
return AddNewFactory(syntaxTree, ignoreAccessibility, ref binderFactories[treeNum]);
}
private BinderFactory AddNewFactory(SyntaxTree syntaxTree, bool ignoreAccessibility, [NotNull] ref WeakReference<BinderFactory>? slot)
{
var newFactory = new BinderFactory(this, syntaxTree, ignoreAccessibility);
var newWeakReference = new WeakReference<BinderFactory>(newFactory);
while (true)
{
BinderFactory? previousFactory;
WeakReference<BinderFactory>? previousWeakReference = slot;
if (previousWeakReference != null && previousWeakReference.TryGetTarget(out previousFactory))
{
Debug.Assert(slot is object);
return previousFactory;
}
if (Interlocked.CompareExchange(ref slot!, newWeakReference, previousWeakReference) == previousWeakReference)
{
return newFactory;
}
}
}
internal Binder GetBinder(CSharpSyntaxNode syntax)
{
return GetBinderFactory(syntax.SyntaxTree).GetBinder(syntax);
}
private AliasSymbol CreateGlobalNamespaceAlias()
{
return AliasSymbol.CreateGlobalNamespaceAlias(this.GlobalNamespace);
}
private void CompleteTree(SyntaxTree tree)
{
if (_lazyCompilationUnitCompletedTrees == null) Interlocked.CompareExchange(ref _lazyCompilationUnitCompletedTrees, new HashSet<SyntaxTree>(), null);
lock (_lazyCompilationUnitCompletedTrees)
{
if (_lazyCompilationUnitCompletedTrees.Add(tree))
{
// signal the end of the compilation unit
EventQueue?.TryEnqueue(new CompilationUnitCompletedEvent(this, tree));
if (_lazyCompilationUnitCompletedTrees.Count == this.SyntaxTrees.Length)
{
// if that was the last tree, signal the end of compilation
CompleteCompilationEventQueue_NoLock();
}
}
}
}
internal override void ReportUnusedImports(DiagnosticBag diagnostics, CancellationToken cancellationToken)
{
Debug.Assert(diagnostics is { });
var bag = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false);
Debug.Assert(bag.DiagnosticBag is { });
ReportUnusedImports(filterTree: null, bag, cancellationToken);
diagnostics.AddRange(bag.DiagnosticBag);
bag.Free();
}
private void ReportUnusedImports(SyntaxTree? filterTree, BindingDiagnosticBag diagnostics, CancellationToken cancellationToken)
{
if (_lazyImportInfos != null && (filterTree is null || ReportUnusedImportsInTree(filterTree)))
{
PooledHashSet<NamespaceSymbol>? externAliasesToCheck = null;
if (diagnostics.DependenciesBag is object)
{
externAliasesToCheck = PooledHashSet<NamespaceSymbol>.GetInstance();
}
foreach (var pair in _lazyImportInfos)
{
cancellationToken.ThrowIfCancellationRequested();
ImportInfo info = pair.Key;
SyntaxTree infoTree = info.Tree;
if ((filterTree == null || filterTree == infoTree) && ReportUnusedImportsInTree(infoTree))
{
TextSpan infoSpan = info.Span;
if (!this.IsImportDirectiveUsed(infoTree, infoSpan.Start))
{
ErrorCode code = info.Kind == SyntaxKind.ExternAliasDirective
? ErrorCode.HDN_UnusedExternAlias
: ErrorCode.HDN_UnusedUsingDirective;
diagnostics.Add(code, infoTree.GetLocation(infoSpan));
}
else if (diagnostics.DependenciesBag is object)
{
RoslynDebug.Assert(externAliasesToCheck is object);
ImmutableArray<AssemblySymbol> dependencies = pair.Value;
if (!dependencies.IsDefaultOrEmpty)
{
diagnostics.AddDependencies(dependencies);
}
else if (info.Kind == SyntaxKind.ExternAliasDirective)
{
// Record targets of used extern aliases
var node = info.Tree.GetRoot(cancellationToken).FindToken(info.Span.Start, findInsideTrivia: false).
Parent!.FirstAncestorOrSelf<ExternAliasDirectiveSyntax>();
if (node is object && GetExternAliasTarget(node.Identifier.ValueText, out NamespaceSymbol target))
{
externAliasesToCheck.Add(target);
}
}
}
}
}
if (externAliasesToCheck is object)
{
RoslynDebug.Assert(diagnostics.DependenciesBag is object);
// We could do this check after we have built the transitive closure
// in GetCompleteSetOfUsedAssemblies.completeTheSetOfUsedAssemblies. However,
// the level of accuracy is probably not worth the complexity this would add.
var bindingDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: false, withDependencies: true);
RoslynDebug.Assert(bindingDiagnostics.DependenciesBag is object);
foreach (var aliasedNamespace in externAliasesToCheck)
{
bindingDiagnostics.Clear();
bindingDiagnostics.AddAssembliesUsedByNamespaceReference(aliasedNamespace);
// See if any of the references with the alias are registered as used. We can get in a situation when none of them are.
// For example, when the alias was used in a doc comment, but nothing was found within it. We would get only a warning
// in this case and no assembly marked as used.
if (_lazyUsedAssemblyReferences?.IsEmpty == false || diagnostics.DependenciesBag.Count != 0)
{
foreach (var assembly in bindingDiagnostics.DependenciesBag)
{
if (_lazyUsedAssemblyReferences?.Contains(assembly) == true ||
diagnostics.DependenciesBag.Contains(assembly))
{
bindingDiagnostics.DependenciesBag.Clear();
break;
}
}
}
diagnostics.AddDependencies(bindingDiagnostics);
}
bindingDiagnostics.Free();
externAliasesToCheck.Free();
}
}
CompleteTrees(filterTree);
}
internal override void CompleteTrees(SyntaxTree? filterTree)
{
// By definition, a tree is complete when all of its compiler diagnostics have been reported.
// Since unused imports are the last thing we compute and report, a tree is complete when
// the unused imports have been reported.
if (EventQueue != null)
{
if (filterTree != null)
{
CompleteTree(filterTree);
}
else
{
foreach (var tree in this.SyntaxTrees)
{
CompleteTree(tree);
}
}
}
if (filterTree is null)
{
_usageOfUsingsRecordedInTrees = null;
}
}
internal void RecordImport(UsingDirectiveSyntax syntax)
{
RecordImportInternal(syntax);
}
internal void RecordImport(ExternAliasDirectiveSyntax syntax)
{
RecordImportInternal(syntax);
}
private void RecordImportInternal(CSharpSyntaxNode syntax)
{
// Note: the suppression will be unnecessary once LazyInitializer is properly annotated
LazyInitializer.EnsureInitialized(ref _lazyImportInfos)!.
TryAdd(new ImportInfo(syntax.SyntaxTree, syntax.Kind(), syntax.Span), default);
}
internal void RecordImportDependencies(UsingDirectiveSyntax syntax, ImmutableArray<AssemblySymbol> dependencies)
{
RoslynDebug.Assert(_lazyImportInfos is object);
_lazyImportInfos.TryUpdate(new ImportInfo(syntax.SyntaxTree, syntax.Kind(), syntax.Span), dependencies, default);
}
private readonly struct ImportInfo : IEquatable<ImportInfo>
{
public readonly SyntaxTree Tree;
public readonly SyntaxKind Kind;
public readonly TextSpan Span;
public ImportInfo(SyntaxTree tree, SyntaxKind kind, TextSpan span)
{
this.Tree = tree;
this.Kind = kind;
this.Span = span;
}
public override bool Equals(object? obj)
{
return (obj is ImportInfo) && Equals((ImportInfo)obj);
}
public bool Equals(ImportInfo other)
{
return
other.Kind == this.Kind &&
other.Tree == this.Tree &&
other.Span == this.Span;
}
public override int GetHashCode()
{
return Hash.Combine(Tree, Span.Start);
}
}
#endregion
#region Diagnostics
internal override CommonMessageProvider MessageProvider
{
get { return _syntaxAndDeclarations.MessageProvider; }
}
/// <summary>
/// The bag in which semantic analysis should deposit its diagnostics.
/// </summary>
internal DiagnosticBag DeclarationDiagnostics
{
get
{
// We should only be placing diagnostics in this bag until
// we are done gathering declaration diagnostics. Assert that is
// the case. But since we have bugs (see https://github.com/dotnet/roslyn/issues/846)
// we disable the assertion until they are fixed.
Debug.Assert(!_declarationDiagnosticsFrozen || true);
if (_lazyDeclarationDiagnostics == null)
{
var diagnostics = new DiagnosticBag();
Interlocked.CompareExchange(ref _lazyDeclarationDiagnostics, diagnostics, null);
}
return _lazyDeclarationDiagnostics;
}
}
private DiagnosticBag? _lazyDeclarationDiagnostics;
private bool _declarationDiagnosticsFrozen;
/// <summary>
/// A bag in which diagnostics that should be reported after code gen can be deposited.
/// </summary>
internal DiagnosticBag AdditionalCodegenWarnings
{
get
{
return _additionalCodegenWarnings;
}
}
private readonly DiagnosticBag _additionalCodegenWarnings = new DiagnosticBag();
internal DeclarationTable Declarations
{
get
{
return _syntaxAndDeclarations.GetLazyState().DeclarationTable;
}
}
internal MergedNamespaceDeclaration MergedRootDeclaration
{
get
{
return Declarations.GetMergedRoot(this);
}
}
/// <summary>
/// Gets the diagnostics produced during the parsing stage of a compilation. There are no diagnostics for declarations or accessor or
/// method bodies, for example.
/// </summary>
public override ImmutableArray<Diagnostic> GetParseDiagnostics(CancellationToken cancellationToken = default)
{
return GetDiagnostics(CompilationStage.Parse, false, symbolFilter: null, cancellationToken);
}
/// <summary>
/// Gets the diagnostics produced during symbol declaration headers. There are no diagnostics for accessor or
/// method bodies, for example.
/// </summary>
public override ImmutableArray<Diagnostic> GetDeclarationDiagnostics(CancellationToken cancellationToken = default)
{
return GetDiagnostics(CompilationStage.Declare, false, symbolFilter: null, cancellationToken);
}
/// <summary>
/// Gets the diagnostics produced during the analysis of method bodies and field initializers.
/// </summary>
public override ImmutableArray<Diagnostic> GetMethodBodyDiagnostics(CancellationToken cancellationToken = default)
{
return GetDiagnostics(CompilationStage.Compile, false, symbolFilter: null, cancellationToken);
}
/// <summary>
/// Gets the all the diagnostics for the compilation, including syntax, declaration, and binding. Does not
/// include any diagnostics that might be produced during emit.
/// </summary>
public override ImmutableArray<Diagnostic> GetDiagnostics(CancellationToken cancellationToken = default)
{
return GetDiagnostics(DefaultDiagnosticsStage, true, symbolFilter: null, cancellationToken);
}
internal ImmutableArray<Diagnostic> GetDiagnostics(CompilationStage stage, bool includeEarlierStages, Predicate<ISymbolInternal>? symbolFilter, CancellationToken cancellationToken)
{
var diagnostics = DiagnosticBag.GetInstance();
GetDiagnostics(stage, includeEarlierStages, diagnostics, symbolFilter, cancellationToken);
return diagnostics.ToReadOnlyAndFree();
}
internal override void GetDiagnostics(CompilationStage stage, bool includeEarlierStages, DiagnosticBag diagnostics, CancellationToken cancellationToken = default)
=> GetDiagnostics(stage, includeEarlierStages, diagnostics, symbolFilter: null, cancellationToken);
internal void GetDiagnostics(CompilationStage stage, bool includeEarlierStages, DiagnosticBag diagnostics, Predicate<ISymbolInternal>? symbolFilter, CancellationToken cancellationToken)
{
var builder = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false);
Debug.Assert(builder.DiagnosticBag is { });
GetDiagnosticsWithoutSeverityFiltering(stage, includeEarlierStages, builder, symbolFilter, cancellationToken);
// Before returning diagnostics, we filter warnings
// to honor the compiler options (e.g., /nowarn, /warnaserror and /warn) and the pragmas.
FilterAndAppendDiagnostics(diagnostics, builder.DiagnosticBag, cancellationToken);
builder.Free();
}
private void GetDiagnosticsWithoutSeverityFiltering(CompilationStage stage, bool includeEarlierStages, BindingDiagnosticBag builder, Predicate<Symbol>? symbolFilter, CancellationToken cancellationToken)
{
RoslynDebug.Assert(builder.DiagnosticBag is object);
if (stage == CompilationStage.Parse || (stage > CompilationStage.Parse && includeEarlierStages))
{
var syntaxTrees = this.SyntaxTrees;
if (this.Options.ConcurrentBuild)
{
RoslynParallel.For(
0,
syntaxTrees.Length,
UICultureUtilities.WithCurrentUICulture<int>(i =>
{
var syntaxTree = syntaxTrees[i];
AppendLoadDirectiveDiagnostics(builder.DiagnosticBag, _syntaxAndDeclarations, syntaxTree);
builder.AddRange(syntaxTree.GetDiagnostics(cancellationToken));
}),
cancellationToken);
}
else
{
foreach (var syntaxTree in syntaxTrees)
{
cancellationToken.ThrowIfCancellationRequested();
AppendLoadDirectiveDiagnostics(builder.DiagnosticBag, _syntaxAndDeclarations, syntaxTree);
cancellationToken.ThrowIfCancellationRequested();
builder.AddRange(syntaxTree.GetDiagnostics(cancellationToken));
}
}
var parseOptionsReported = new HashSet<ParseOptions>();
foreach (var syntaxTree in syntaxTrees)
{
cancellationToken.ThrowIfCancellationRequested();
if (!syntaxTree.Options.Errors.IsDefaultOrEmpty && parseOptionsReported.Add(syntaxTree.Options))
{
var location = syntaxTree.GetLocation(TextSpan.FromBounds(0, 0));
foreach (var error in syntaxTree.Options.Errors)
{
builder.Add(error.WithLocation(location));
}
}
}
}
if (stage == CompilationStage.Declare || stage > CompilationStage.Declare && includeEarlierStages)
{
CheckAssemblyName(builder.DiagnosticBag);
builder.AddRange(Options.Errors);
if (Options.NullableContextOptions != NullableContextOptions.Disable && LanguageVersion < MessageID.IDS_FeatureNullableReferenceTypes.RequiredVersion() &&
_syntaxAndDeclarations.ExternalSyntaxTrees.Any())
{
builder.Add(new CSDiagnostic(new CSDiagnosticInfo(ErrorCode.ERR_NullableOptionNotAvailable,
nameof(Options.NullableContextOptions), Options.NullableContextOptions, LanguageVersion.ToDisplayString(),
new CSharpRequiredLanguageVersion(MessageID.IDS_FeatureNullableReferenceTypes.RequiredVersion())), Location.None));
}
cancellationToken.ThrowIfCancellationRequested();
// the set of diagnostics related to establishing references.
builder.AddRange(GetBoundReferenceManager().Diagnostics);
cancellationToken.ThrowIfCancellationRequested();
builder.AddRange(GetSourceDeclarationDiagnostics(symbolFilter: symbolFilter, cancellationToken: cancellationToken), allowMismatchInDependencyAccumulation: true);
if (EventQueue != null && SyntaxTrees.Length == 0)
{
EnsureCompilationEventQueueCompleted();
}
}
cancellationToken.ThrowIfCancellationRequested();
if (stage == CompilationStage.Compile || stage > CompilationStage.Compile && includeEarlierStages)
{
var methodBodyDiagnostics = builder.AccumulatesDependencies ? BindingDiagnosticBag.GetConcurrentInstance() : BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false);
RoslynDebug.Assert(methodBodyDiagnostics.DiagnosticBag is object);
GetDiagnosticsForAllMethodBodies(methodBodyDiagnostics, doLowering: false, cancellationToken);
builder.AddRangeAndFree(methodBodyDiagnostics);
}
}
private static void AppendLoadDirectiveDiagnostics(DiagnosticBag builder, SyntaxAndDeclarationManager syntaxAndDeclarations, SyntaxTree syntaxTree, Func<IEnumerable<Diagnostic>, IEnumerable<Diagnostic>>? locationFilterOpt = null)
{
ImmutableArray<LoadDirective> loadDirectives;
if (syntaxAndDeclarations.GetLazyState().LoadDirectiveMap.TryGetValue(syntaxTree, out loadDirectives))
{
Debug.Assert(!loadDirectives.IsEmpty);
foreach (var directive in loadDirectives)
{
IEnumerable<Diagnostic> diagnostics = directive.Diagnostics;
if (locationFilterOpt != null)
{
diagnostics = locationFilterOpt(diagnostics);
}
builder.AddRange(diagnostics);
}
}
}
// Do the steps in compilation to get the method body diagnostics, but don't actually generate
// IL or emit an assembly.
private void GetDiagnosticsForAllMethodBodies(BindingDiagnosticBag diagnostics, bool doLowering, CancellationToken cancellationToken)
{
RoslynDebug.Assert(diagnostics.DiagnosticBag is object);
MethodCompiler.CompileMethodBodies(
compilation: this,
moduleBeingBuiltOpt: doLowering ? (PEModuleBuilder?)CreateModuleBuilder(
emitOptions: EmitOptions.Default,
debugEntryPoint: null,
manifestResources: null,
sourceLinkStream: null,
embeddedTexts: null,
testData: null,
diagnostics: diagnostics.DiagnosticBag,
cancellationToken: cancellationToken)
: null,
emittingPdb: false,
hasDeclarationErrors: false,
emitMethodBodies: false,
diagnostics: diagnostics,
filterOpt: null,
cancellationToken: cancellationToken);
DocumentationCommentCompiler.WriteDocumentationCommentXml(this, null, null, diagnostics, cancellationToken);
this.ReportUnusedImports(filterTree: null, diagnostics, cancellationToken);
}
private static bool IsDefinedOrImplementedInSourceTree(Symbol symbol, SyntaxTree tree, TextSpan? span)
{
if (symbol.IsDefinedInSourceTree(tree, span))
{
return true;
}
if (symbol.Kind == SymbolKind.Method && symbol.IsImplicitlyDeclared && ((MethodSymbol)symbol).MethodKind == MethodKind.Constructor)
{
// Include implicitly declared constructor if containing type is included
return IsDefinedOrImplementedInSourceTree(symbol.ContainingType, tree, span);
}
return false;
}
private ImmutableArray<Diagnostic> GetDiagnosticsForMethodBodiesInTree(SyntaxTree tree, TextSpan? span, CancellationToken cancellationToken)
{
var bindingDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false);
Debug.Assert(bindingDiagnostics.DiagnosticBag is { });
// Report unused directives only if computing diagnostics for the entire tree.
// Otherwise we cannot determine if a particular directive is used outside of the given sub-span within the tree.
bool reportUnusedUsings = (!span.HasValue || span.Value == tree.GetRoot(cancellationToken).FullSpan) && ReportUnusedImportsInTree(tree);
bool recordUsageOfUsingsInAllTrees = false;
if (reportUnusedUsings && UsageOfUsingsRecordedInTrees is not null)
{
foreach (var singleDeclaration in ((SourceNamespaceSymbol)SourceModule.GlobalNamespace).MergedDeclaration.Declarations)
{
if (singleDeclaration.SyntaxReference.SyntaxTree == tree)
{
if (singleDeclaration.HasGlobalUsings)
{
// Global Using directives can be used in any tree. Make sure we collect usage information from all of them.
recordUsageOfUsingsInAllTrees = true;
}
break;
}
}
}
if (recordUsageOfUsingsInAllTrees && UsageOfUsingsRecordedInTrees?.IsEmpty == true)
{
Debug.Assert(reportUnusedUsings);
// Simply compile the world
compileMethodBodiesAndDocComments(filterTree: null, filterSpan: null, bindingDiagnostics, cancellationToken);
_usageOfUsingsRecordedInTrees = null;
}
else
{
// Always compile the target tree
compileMethodBodiesAndDocComments(filterTree: tree, filterSpan: span, bindingDiagnostics, cancellationToken);
if (reportUnusedUsings)
{
registeredUsageOfUsingsInTree(tree);
}
// Compile other trees if we need to, but discard diagnostics from them.
if (recordUsageOfUsingsInAllTrees)
{
Debug.Assert(reportUnusedUsings);
var discarded = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false);
Debug.Assert(discarded.DiagnosticBag is object);
foreach (var otherTree in SyntaxTrees)
{
var trackingSet = UsageOfUsingsRecordedInTrees;
if (trackingSet is null)
{
break;
}
if (!trackingSet.Contains(otherTree))
{
compileMethodBodiesAndDocComments(filterTree: otherTree, filterSpan: null, discarded, cancellationToken);
registeredUsageOfUsingsInTree(otherTree);
discarded.DiagnosticBag.Clear();
}
}
discarded.Free();
}
}
if (reportUnusedUsings)
{
ReportUnusedImports(tree, bindingDiagnostics, cancellationToken);
}
return bindingDiagnostics.ToReadOnlyAndFree().Diagnostics;
void compileMethodBodiesAndDocComments(SyntaxTree? filterTree, TextSpan? filterSpan, BindingDiagnosticBag bindingDiagnostics, CancellationToken cancellationToken)
{
MethodCompiler.CompileMethodBodies(
compilation: this,
moduleBeingBuiltOpt: null,
emittingPdb: false,
hasDeclarationErrors: false,
emitMethodBodies: false,
diagnostics: bindingDiagnostics,
filterOpt: filterTree is object ? (Predicate<Symbol>?)(s => IsDefinedOrImplementedInSourceTree(s, filterTree, filterSpan)) : (Predicate<Symbol>?)null,
cancellationToken: cancellationToken);
DocumentationCommentCompiler.WriteDocumentationCommentXml(this, null, null, bindingDiagnostics, cancellationToken, filterTree, filterSpan);
}
void registeredUsageOfUsingsInTree(SyntaxTree tree)
{
var current = UsageOfUsingsRecordedInTrees;
while (true)
{
if (current is null)
{
break;
}
var updated = current.Add(tree);
if ((object)updated == current)
{
break;
}
if (updated.Count == SyntaxTrees.Length)
{
_usageOfUsingsRecordedInTrees = null;
break;
}
var recent = Interlocked.CompareExchange(ref _usageOfUsingsRecordedInTrees, updated, current);
if (recent == (object)current)
{
break;
}
current = recent;
}
}
}
private ReadOnlyBindingDiagnostic<AssemblySymbol> GetSourceDeclarationDiagnostics(SyntaxTree? syntaxTree = null, TextSpan? filterSpanWithinTree = null, Func<IEnumerable<Diagnostic>, SyntaxTree, TextSpan?, IEnumerable<Diagnostic>>? locationFilterOpt = null, Predicate<Symbol>? symbolFilter = null, CancellationToken cancellationToken = default)
{
UsingsFromOptions.Complete(this, cancellationToken);
SourceLocation? location = null;
if (syntaxTree != null)
{
var root = syntaxTree.GetRoot(cancellationToken);
location = filterSpanWithinTree.HasValue ?
new SourceLocation(syntaxTree, filterSpanWithinTree.Value) :
new SourceLocation(root);
}
Assembly.ForceComplete(location, symbolFilter, cancellationToken);
if (syntaxTree is null && symbolFilter is null)
{
// Don't freeze the compilation if we're getting
// diagnostics for a single tree
_declarationDiagnosticsFrozen = true;
// Also freeze generated attribute flags.
// Symbols bound after getting the declaration
// diagnostics shouldn't need to modify the flags.
_needsGeneratedAttributes_IsFrozen = true;
}
var result = _lazyDeclarationDiagnostics?.AsEnumerable() ?? Enumerable.Empty<Diagnostic>();
if (locationFilterOpt != null)
{
RoslynDebug.Assert(syntaxTree != null);
result = locationFilterOpt(result, syntaxTree, filterSpanWithinTree);
}
// Do not check CLSCompliance if we are doing ENC.
if (symbolFilter == null)
{
// NOTE: Concatenate the CLS diagnostics *after* filtering by tree/span, because they're already filtered.
ReadOnlyBindingDiagnostic<AssemblySymbol> clsDiagnostics = GetClsComplianceDiagnostics(syntaxTree, filterSpanWithinTree, cancellationToken);
return new ReadOnlyBindingDiagnostic<AssemblySymbol>(result.AsImmutable().Concat(clsDiagnostics.Diagnostics), clsDiagnostics.Dependencies);
}
else
{
return new ReadOnlyBindingDiagnostic<AssemblySymbol>(result.AsImmutable(), ImmutableArray<AssemblySymbol>.Empty);
}
}
private ReadOnlyBindingDiagnostic<AssemblySymbol> GetClsComplianceDiagnostics(SyntaxTree? syntaxTree, TextSpan? filterSpanWithinTree, CancellationToken cancellationToken)
{
if (syntaxTree != null)
{
var builder = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false);
ClsComplianceChecker.CheckCompliance(this, builder, cancellationToken, syntaxTree, filterSpanWithinTree);
return builder.ToReadOnlyAndFree();
}
if (_lazyClsComplianceDiagnostics.IsDefault || _lazyClsComplianceDependencies.IsDefault)
{
var builder = BindingDiagnosticBag.GetInstance();
ClsComplianceChecker.CheckCompliance(this, builder, cancellationToken);
var result = builder.ToReadOnlyAndFree();
ImmutableInterlocked.InterlockedInitialize(ref _lazyClsComplianceDependencies, result.Dependencies);
ImmutableInterlocked.InterlockedInitialize(ref _lazyClsComplianceDiagnostics, result.Diagnostics);
}
Debug.Assert(!_lazyClsComplianceDependencies.IsDefault);
Debug.Assert(!_lazyClsComplianceDiagnostics.IsDefault);
return new ReadOnlyBindingDiagnostic<AssemblySymbol>(_lazyClsComplianceDiagnostics, _lazyClsComplianceDependencies);
}
private static IEnumerable<Diagnostic> FilterDiagnosticsByLocation(IEnumerable<Diagnostic> diagnostics, SyntaxTree tree, TextSpan? filterSpanWithinTree)
{
foreach (var diagnostic in diagnostics)
{
if (diagnostic.HasIntersectingLocation(tree, filterSpanWithinTree))
{
yield return diagnostic;
}
}
}
internal ImmutableArray<Diagnostic> GetDiagnosticsForSyntaxTree(
CompilationStage stage,
SyntaxTree syntaxTree,
TextSpan? filterSpanWithinTree,
bool includeEarlierStages,
CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
DiagnosticBag? builder = DiagnosticBag.GetInstance();
if (stage == CompilationStage.Parse || (stage > CompilationStage.Parse && includeEarlierStages))
{
AppendLoadDirectiveDiagnostics(builder, _syntaxAndDeclarations, syntaxTree,
diagnostics => FilterDiagnosticsByLocation(diagnostics, syntaxTree, filterSpanWithinTree));
var syntaxDiagnostics = syntaxTree.GetDiagnostics(cancellationToken);
syntaxDiagnostics = FilterDiagnosticsByLocation(syntaxDiagnostics, syntaxTree, filterSpanWithinTree);
builder.AddRange(syntaxDiagnostics);
}
cancellationToken.ThrowIfCancellationRequested();
if (stage == CompilationStage.Declare || (stage > CompilationStage.Declare && includeEarlierStages))
{
var declarationDiagnostics = GetSourceDeclarationDiagnostics(syntaxTree, filterSpanWithinTree, FilterDiagnosticsByLocation, symbolFilter: null, cancellationToken);
// re-enabling/fixing the below assert is tracked by https://github.com/dotnet/roslyn/issues/21020
// Debug.Assert(declarationDiagnostics.All(d => d.HasIntersectingLocation(syntaxTree, filterSpanWithinTree)));
builder.AddRange(declarationDiagnostics.Diagnostics);
}
cancellationToken.ThrowIfCancellationRequested();
if (stage == CompilationStage.Compile || (stage > CompilationStage.Compile && includeEarlierStages))
{
//remove some errors that don't have locations in the tree, like "no suitable main method."
//Members in trees other than the one being examined are not compiled. This includes field
//initializers which can result in 'field is never initialized' warnings for fields in partial
//types when the field is in a different source file than the one for which we're getting diagnostics.
//For that reason the bag must be also filtered by tree.
IEnumerable<Diagnostic> methodBodyDiagnostics = GetDiagnosticsForMethodBodiesInTree(syntaxTree, filterSpanWithinTree, cancellationToken);
// TODO: Enable the below commented assert and remove the filtering code in the next line.
// GetDiagnosticsForMethodBodiesInTree seems to be returning diagnostics with locations that don't satisfy the filter tree/span, this must be fixed.
// Debug.Assert(methodBodyDiagnostics.All(d => DiagnosticContainsLocation(d, syntaxTree, filterSpanWithinTree)));
methodBodyDiagnostics = FilterDiagnosticsByLocation(methodBodyDiagnostics, syntaxTree, filterSpanWithinTree);
builder.AddRange(methodBodyDiagnostics);
}
// Before returning diagnostics, we filter warnings
// to honor the compiler options (/nowarn, /warnaserror and /warn) and the pragmas.
var result = DiagnosticBag.GetInstance();
FilterAndAppendAndFreeDiagnostics(result, ref builder, cancellationToken);
return result.ToReadOnlyAndFree<Diagnostic>();
}
#endregion
#region Resources
protected override void AppendDefaultVersionResource(Stream resourceStream)
{
var sourceAssembly = SourceAssembly;
string fileVersion = sourceAssembly.FileVersion ?? sourceAssembly.Identity.Version.ToString();
Win32ResourceConversions.AppendVersionToResourceStream(resourceStream,
!this.Options.OutputKind.IsApplication(),
fileVersion: fileVersion,
originalFileName: this.SourceModule.Name,
internalName: this.SourceModule.Name,
productVersion: sourceAssembly.InformationalVersion ?? fileVersion,
fileDescription: sourceAssembly.Title ?? " ", //alink would give this a blank if nothing was supplied.
assemblyVersion: sourceAssembly.Identity.Version,
legalCopyright: sourceAssembly.Copyright ?? " ", //alink would give this a blank if nothing was supplied.
legalTrademarks: sourceAssembly.Trademark,
productName: sourceAssembly.Product,
comments: sourceAssembly.Description,
companyName: sourceAssembly.Company);
}
#endregion
#region Emit
internal override byte LinkerMajorVersion => 0x30;
internal override bool IsDelaySigned
{
get { return SourceAssembly.IsDelaySigned; }
}
internal override StrongNameKeys StrongNameKeys
{
get { return SourceAssembly.StrongNameKeys; }
}
internal override CommonPEModuleBuilder? CreateModuleBuilder(
EmitOptions emitOptions,
IMethodSymbol? debugEntryPoint,
Stream? sourceLinkStream,
IEnumerable<EmbeddedText>? embeddedTexts,
IEnumerable<ResourceDescription>? manifestResources,
CompilationTestData? testData,
DiagnosticBag diagnostics,
CancellationToken cancellationToken)
{
Debug.Assert(!IsSubmission || HasCodeToEmit() ||
(emitOptions == EmitOptions.Default && debugEntryPoint is null && sourceLinkStream is null && embeddedTexts is null && manifestResources is null && testData is null));
string? runtimeMDVersion = GetRuntimeMetadataVersion(emitOptions, diagnostics);
if (runtimeMDVersion == null)
{
return null;
}
var moduleProps = ConstructModuleSerializationProperties(emitOptions, runtimeMDVersion);
if (manifestResources == null)
{
manifestResources = SpecializedCollections.EmptyEnumerable<ResourceDescription>();
}
PEModuleBuilder moduleBeingBuilt;
if (_options.OutputKind.IsNetModule())
{
moduleBeingBuilt = new PENetModuleBuilder(
(SourceModuleSymbol)SourceModule,
emitOptions,
moduleProps,
manifestResources);
}
else
{
var kind = _options.OutputKind.IsValid() ? _options.OutputKind : OutputKind.DynamicallyLinkedLibrary;
moduleBeingBuilt = new PEAssemblyBuilder(
SourceAssembly,
emitOptions,
kind,
moduleProps,
manifestResources);
}
if (debugEntryPoint != null)
{
moduleBeingBuilt.SetDebugEntryPoint(debugEntryPoint.GetSymbol(), diagnostics);
}
moduleBeingBuilt.SourceLinkStreamOpt = sourceLinkStream;
if (embeddedTexts != null)
{
moduleBeingBuilt.EmbeddedTexts = embeddedTexts;
}
// testData is only passed when running tests.
if (testData != null)
{
moduleBeingBuilt.SetTestData(testData);
}
return moduleBeingBuilt;
}
internal override bool CompileMethods(
CommonPEModuleBuilder moduleBuilder,
bool emittingPdb,
DiagnosticBag diagnostics,
Predicate<ISymbolInternal>? filterOpt,
CancellationToken cancellationToken)
{
var emitMetadataOnly = moduleBuilder.EmitOptions.EmitMetadataOnly;
// The diagnostics should include syntax and declaration errors. We insert these before calling Emitter.Emit, so that the emitter
// does not attempt to emit if there are declaration errors (but we do insert all errors from method body binding...)
PooledHashSet<int>? excludeDiagnostics = null;
if (emitMetadataOnly)
{
excludeDiagnostics = PooledHashSet<int>.GetInstance();
excludeDiagnostics.Add((int)ErrorCode.ERR_ConcreteMissingBody);
}
bool hasDeclarationErrors = !FilterAndAppendDiagnostics(diagnostics, GetDiagnostics(CompilationStage.Declare, true, symbolFilter: filterOpt, cancellationToken), excludeDiagnostics, cancellationToken);
excludeDiagnostics?.Free();
// TODO (tomat): NoPIA:
// EmbeddedSymbolManager.MarkAllDeferredSymbolsAsReferenced(this)
var moduleBeingBuilt = (PEModuleBuilder)moduleBuilder;
if (emitMetadataOnly)
{
if (hasDeclarationErrors)
{
return false;
}
if (moduleBeingBuilt.SourceModule.HasBadAttributes)
{
// If there were errors but no declaration diagnostics, explicitly add a "Failed to emit module" error.
diagnostics.Add(ErrorCode.ERR_ModuleEmitFailure, NoLocation.Singleton, ((Cci.INamedEntity)moduleBeingBuilt).Name,
new LocalizableResourceString(nameof(CodeAnalysisResources.ModuleHasInvalidAttributes), CodeAnalysisResources.ResourceManager, typeof(CodeAnalysisResources)));
return false;
}
SynthesizedMetadataCompiler.ProcessSynthesizedMembers(this, moduleBeingBuilt, cancellationToken);
}
else
{
if ((emittingPdb || moduleBeingBuilt.EmitOptions.InstrumentationKinds.Contains(InstrumentationKind.TestCoverage)) &&
!CreateDebugDocuments(moduleBeingBuilt.DebugDocumentsBuilder, moduleBeingBuilt.EmbeddedTexts, diagnostics))
{
return false;
}
// Perform initial bind of method bodies in spite of earlier errors. This is the same
// behavior as when calling GetDiagnostics()
// Use a temporary bag so we don't have to refilter pre-existing diagnostics.
var methodBodyDiagnosticBag = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false);
Debug.Assert(methodBodyDiagnosticBag.DiagnosticBag is { });
Debug.Assert(moduleBeingBuilt is object);
MethodCompiler.CompileMethodBodies(
this,
moduleBeingBuilt,
emittingPdb,
hasDeclarationErrors,
emitMethodBodies: true,
diagnostics: methodBodyDiagnosticBag,
filterOpt: filterOpt,
cancellationToken: cancellationToken);
// We don't generate the module initializer for ENC scenarios, as the assembly is already loaded so edits to the module initializer would have no impact.
Debug.Assert(filterOpt == null || moduleBeingBuilt.IsEncDelta);
if (!hasDeclarationErrors && !CommonCompiler.HasUnsuppressableErrors(methodBodyDiagnosticBag.DiagnosticBag) && filterOpt == null)
{
GenerateModuleInitializer(moduleBeingBuilt, methodBodyDiagnosticBag.DiagnosticBag);
}
bool hasDuplicateFilePaths = CheckDuplicateFilePaths(diagnostics);
bool hasMethodBodyError = !FilterAndAppendDiagnostics(diagnostics, methodBodyDiagnosticBag.DiagnosticBag, cancellationToken);
methodBodyDiagnosticBag.Free();
if (hasDeclarationErrors || hasMethodBodyError || hasDuplicateFilePaths)
{
return false;
}
}
return true;
}
private protected override EmitBaseline MapToCompilation(CommonPEModuleBuilder moduleBeingBuilt)
=> EmitHelpers.MapToCompilation(this, (PEDeltaAssemblyBuilder)moduleBeingBuilt);
private class DuplicateFilePathsVisitor : CSharpSymbolVisitor
{
// note: the default HashSet<string> uses an ordinal comparison
private readonly PooledHashSet<string> _duplicatePaths = PooledHashSet<string>.GetInstance();
private readonly DiagnosticBag _diagnostics;
private bool _hasDuplicateFilePaths;
public DuplicateFilePathsVisitor(DiagnosticBag diagnostics)
{
_diagnostics = diagnostics;
}
public bool CheckDuplicateFilePathsAndFree(ImmutableArray<SyntaxTree> syntaxTrees, NamespaceSymbol globalNamespace)
{
var paths = PooledHashSet<string>.GetInstance();
foreach (var tree in syntaxTrees)
{
if (!paths.Add(tree.FilePath))
{
_duplicatePaths.Add(tree.FilePath);
}
}
paths.Free();
if (_duplicatePaths.Any())
{
VisitNamespace(globalNamespace);
}
_duplicatePaths.Free();
return _hasDuplicateFilePaths;
}
public override void VisitNamespace(NamespaceSymbol symbol)
{
foreach (var childSymbol in symbol.GetMembers())
{
switch (childSymbol)
{
case NamespaceSymbol @namespace:
VisitNamespace(@namespace);
break;
case NamedTypeSymbol namedType:
VisitNamedType(namedType);
break;
}
}
}
public override void VisitNamedType(NamedTypeSymbol symbol)
{
Debug.Assert(symbol.ContainingSymbol.Kind == SymbolKind.Namespace); // avoid unnecessary traversal of nested types
if (symbol.IsFileLocal)
{
var location = symbol.GetFirstLocation();
var filePath = location.SourceTree?.FilePath;
if (_duplicatePaths.Contains(filePath!))
{
_diagnostics.Add(ErrorCode.ERR_FileTypeNonUniquePath, location, symbol, filePath);
_hasDuplicateFilePaths = true;
}
}
}
}
/// <returns><see langword="true"/> if file types are present in files with duplicate file paths. Otherwise, <see langword="false" />.</returns>
private bool CheckDuplicateFilePaths(DiagnosticBag diagnostics)
{
var visitor = new DuplicateFilePathsVisitor(diagnostics);
return visitor.CheckDuplicateFilePathsAndFree(SyntaxTrees, GlobalNamespace);
}
/// <returns><see langword="true"/> if duplicate interceptions are present in the compilation. Otherwise, <see langword="false" />.</returns>
internal bool CheckDuplicateInterceptions(BindingDiagnosticBag diagnostics)
{
if (_interceptions is null)
{
return false;
}
bool anyDuplicates = false;
foreach ((_, OneOrMany<(Location, MethodSymbol)> interceptionsOfAGivenLocation) in _interceptions)
{
Debug.Assert(interceptionsOfAGivenLocation.Count != 0);
if (interceptionsOfAGivenLocation.Count == 1)
{
continue;
}
anyDuplicates = true;
foreach (var (attributeLocation, _) in interceptionsOfAGivenLocation)
{
diagnostics.Add(ErrorCode.ERR_DuplicateInterceptor, attributeLocation);
}
}
return anyDuplicates;
}
private void GenerateModuleInitializer(PEModuleBuilder moduleBeingBuilt, DiagnosticBag methodBodyDiagnosticBag)
{
Debug.Assert(_declarationDiagnosticsFrozen);
if (_moduleInitializerMethods is object)
{
var ilBuilder = new ILBuilder(moduleBeingBuilt, new LocalSlotManager(slotAllocator: null), OptimizationLevel.Release, areLocalsZeroed: false);
foreach (MethodSymbol method in _moduleInitializerMethods.OrderBy<MethodSymbol>(LexicalOrderSymbolComparer.Instance))
{
ilBuilder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0);
ilBuilder.EmitToken(
moduleBeingBuilt.Translate(method, methodBodyDiagnosticBag, needDeclaration: true),
CSharpSyntaxTree.Dummy.GetRoot(),
methodBodyDiagnosticBag);
}
ilBuilder.EmitRet(isVoid: true);
ilBuilder.Realize();
moduleBeingBuilt.RootModuleType.SetStaticConstructorBody(ilBuilder.RealizedIL);
}
}
internal override bool GenerateResources(
CommonPEModuleBuilder moduleBuilder,
Stream? win32Resources,
bool useRawWin32Resources,
DiagnosticBag diagnostics,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
// Use a temporary bag so we don't have to refilter pre-existing diagnostics.
DiagnosticBag? resourceDiagnostics = DiagnosticBag.GetInstance();
SetupWin32Resources(moduleBuilder, win32Resources, useRawWin32Resources, resourceDiagnostics);
ReportManifestResourceDuplicates(
moduleBuilder.ManifestResources,
SourceAssembly.Modules.Skip(1).Select(m => m.Name), //all modules except the first one
AddedModulesResourceNames(resourceDiagnostics),
resourceDiagnostics);
return FilterAndAppendAndFreeDiagnostics(diagnostics, ref resourceDiagnostics, cancellationToken);
}
internal override bool GenerateDocumentationComments(
Stream? xmlDocStream,
string? outputNameOverride,
DiagnosticBag diagnostics,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
// Use a temporary bag so we don't have to refilter pre-existing diagnostics.
var xmlDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false);
Debug.Assert(xmlDiagnostics.DiagnosticBag is { });
string? assemblyName = FileNameUtilities.ChangeExtension(outputNameOverride, extension: null);
DocumentationCommentCompiler.WriteDocumentationCommentXml(this, assemblyName, xmlDocStream, xmlDiagnostics, cancellationToken);
bool result = FilterAndAppendDiagnostics(diagnostics, xmlDiagnostics.DiagnosticBag, cancellationToken);
xmlDiagnostics.Free();
return result;
}
private IEnumerable<string> AddedModulesResourceNames(DiagnosticBag diagnostics)
{
ImmutableArray<ModuleSymbol> modules = SourceAssembly.Modules;
for (int i = 1; i < modules.Length; i++)
{
var m = (Symbols.Metadata.PE.PEModuleSymbol)modules[i];
ImmutableArray<EmbeddedResource> resources;
try
{
resources = m.Module.GetEmbeddedResourcesOrThrow();
}
catch (BadImageFormatException)
{
diagnostics.Add(new CSDiagnosticInfo(ErrorCode.ERR_BindToBogus, m), NoLocation.Singleton);
continue;
}
foreach (var resource in resources)
{
yield return resource.Name;
}
}
}
internal override EmitDifferenceResult EmitDifference(
EmitBaseline baseline,
IEnumerable<SemanticEdit> edits,
Func<ISymbol, bool> isAddedSymbol,
Stream metadataStream,
Stream ilStream,
Stream pdbStream,
CompilationTestData? testData,
CancellationToken cancellationToken)
{
return EmitHelpers.EmitDifference(
this,
baseline,
edits,
isAddedSymbol,
metadataStream,
ilStream,
pdbStream,
testData,
cancellationToken);
}
internal string? GetRuntimeMetadataVersion(EmitOptions emitOptions, DiagnosticBag diagnostics)
{
string? runtimeMDVersion = GetRuntimeMetadataVersion(emitOptions);
if (runtimeMDVersion != null)
{
return runtimeMDVersion;
}
DiagnosticBag? runtimeMDVersionDiagnostics = DiagnosticBag.GetInstance();
runtimeMDVersionDiagnostics.Add(ErrorCode.WRN_NoRuntimeMetadataVersion, NoLocation.Singleton);
if (!FilterAndAppendAndFreeDiagnostics(diagnostics, ref runtimeMDVersionDiagnostics, CancellationToken.None))
{
return null;
}
return string.Empty; //prevent emitter from crashing.
}
private string? GetRuntimeMetadataVersion(EmitOptions emitOptions)
{
var corAssembly = Assembly.CorLibrary as Symbols.Metadata.PE.PEAssemblySymbol;
if (corAssembly is object)
{
return corAssembly.Assembly.ManifestModule.MetadataVersion;
}
return emitOptions.RuntimeMetadataVersion;
}
internal override void AddDebugSourceDocumentsForChecksumDirectives(
DebugDocumentsBuilder documentsBuilder,
SyntaxTree tree,
DiagnosticBag diagnostics)
{
var checksumDirectives = tree.GetRoot().GetDirectives(d => d.Kind() == SyntaxKind.PragmaChecksumDirectiveTrivia &&
!d.ContainsDiagnostics);
foreach (var directive in checksumDirectives)
{
var checksumDirective = (PragmaChecksumDirectiveTriviaSyntax)directive;
var path = checksumDirective.File.ValueText;
var checksumText = checksumDirective.Bytes.ValueText;
var normalizedPath = documentsBuilder.NormalizeDebugDocumentPath(path, basePath: tree.FilePath);
var existingDoc = documentsBuilder.TryGetDebugDocumentForNormalizedPath(normalizedPath);
// duplicate checksum pragmas are valid as long as values match
// if we have seen this document already, check for matching values.
if (existingDoc != null)
{
// pragma matches a file path on an actual tree.
// Dev12 compiler just ignores the pragma in this case which means that
// checksum of the actual tree always wins and no warning is given.
// We will continue doing the same.
if (existingDoc.IsComputedChecksum)
{
continue;
}
var sourceInfo = existingDoc.GetSourceInfo();
if (ChecksumMatches(checksumText, sourceInfo.Checksum))
{
var guid = Guid.Parse(checksumDirective.Guid.ValueText);
if (guid == sourceInfo.ChecksumAlgorithmId)
{
// all parts match, nothing to do
continue;
}
}
// did not match to an existing document
// produce a warning and ignore the pragma
diagnostics.Add(ErrorCode.WRN_ConflictingChecksum, new SourceLocation(checksumDirective), path);
}
else
{
var newDocument = new Cci.DebugSourceDocument(
normalizedPath,
Cci.DebugSourceDocument.CorSymLanguageTypeCSharp,
MakeChecksumBytes(checksumText),
Guid.Parse(checksumDirective.Guid.ValueText));
documentsBuilder.AddDebugDocument(newDocument);
}
}
}
private static bool ChecksumMatches(string bytesText, ImmutableArray<byte> bytes)
{
if (bytesText.Length != bytes.Length * 2)
{
return false;
}
for (int i = 0, len = bytesText.Length / 2; i < len; i++)
{
// 1A in text becomes 0x1A
var b = SyntaxFacts.HexValue(bytesText[i * 2]) * 16 +
SyntaxFacts.HexValue(bytesText[i * 2 + 1]);
if (b != bytes[i])
{
return false;
}
}
return true;
}
private static ImmutableArray<byte> MakeChecksumBytes(string bytesText)
{
int length = bytesText.Length / 2;
var builder = ArrayBuilder<byte>.GetInstance(length);
for (int i = 0; i < length; i++)
{
// 1A in text becomes 0x1A
var b = SyntaxFacts.HexValue(bytesText[i * 2]) * 16 +
SyntaxFacts.HexValue(bytesText[i * 2 + 1]);
builder.Add((byte)b);
}
return builder.ToImmutableAndFree();
}
internal override Guid DebugSourceDocumentLanguageId => Cci.DebugSourceDocument.CorSymLanguageTypeCSharp;
internal override bool HasCodeToEmit()
{
foreach (var syntaxTree in this.SyntaxTrees)
{
var unit = syntaxTree.GetCompilationUnitRoot();
if (unit.Members.Count > 0)
{
return true;
}
}
return false;
}
#endregion
#region Common Members
protected override Compilation CommonWithReferences(IEnumerable<MetadataReference> newReferences)
{
return WithReferences(newReferences);
}
protected override Compilation CommonWithAssemblyName(string? assemblyName)
{
return WithAssemblyName(assemblyName);
}
protected override IAssemblySymbol CommonAssembly
{
get { return this.Assembly.GetPublicSymbol(); }
}
protected override INamespaceSymbol CommonGlobalNamespace
{
get { return this.GlobalNamespace.GetPublicSymbol(); }
}
protected override CompilationOptions CommonOptions
{
get { return _options; }
}
[Experimental(RoslynExperiments.NullableDisabledSemanticModel)]
protected override SemanticModel CommonGetSemanticModel(SyntaxTree syntaxTree, SemanticModelOptions options)
{
return this.GetSemanticModel(syntaxTree, options);
}
protected internal override ImmutableArray<SyntaxTree> CommonSyntaxTrees
{
get
{
return this.SyntaxTrees;
}
}
protected override Compilation CommonAddSyntaxTrees(IEnumerable<SyntaxTree> trees)
{
return this.AddSyntaxTrees(trees);
}
protected override Compilation CommonRemoveSyntaxTrees(IEnumerable<SyntaxTree> trees)
{
return this.RemoveSyntaxTrees(trees);
}
protected override Compilation CommonRemoveAllSyntaxTrees()
{
return this.RemoveAllSyntaxTrees();
}
protected override Compilation CommonReplaceSyntaxTree(SyntaxTree oldTree, SyntaxTree? newTree)
{
return this.ReplaceSyntaxTree(oldTree, newTree);
}
protected override Compilation CommonWithOptions(CompilationOptions options)
{
return this.WithOptions((CSharpCompilationOptions)options);
}
protected override Compilation CommonWithScriptCompilationInfo(ScriptCompilationInfo? info)
{
return this.WithScriptCompilationInfo((CSharpScriptCompilationInfo?)info);
}
protected override bool CommonContainsSyntaxTree(SyntaxTree? syntaxTree)
{
return this.ContainsSyntaxTree(syntaxTree);
}
protected override ISymbol? CommonGetAssemblyOrModuleSymbol(MetadataReference reference)
{
return this.GetAssemblyOrModuleSymbol(reference).GetPublicSymbol();
}
protected override Compilation CommonClone()
{
return this.Clone();
}
protected override IModuleSymbol CommonSourceModule
{
get { return this.SourceModule.GetPublicSymbol(); }
}
private protected override INamedTypeSymbolInternal CommonGetSpecialType(SpecialType specialType)
{
return this.GetSpecialType(specialType);
}
protected override INamespaceSymbol? CommonGetCompilationNamespace(INamespaceSymbol namespaceSymbol)
{
return this.GetCompilationNamespace(namespaceSymbol).GetPublicSymbol();
}
protected override INamedTypeSymbol? CommonGetTypeByMetadataName(string metadataName)
{
return this.GetTypeByMetadataName(metadataName).GetPublicSymbol();
}
protected override INamedTypeSymbol? CommonScriptClass
{
get { return this.ScriptClass.GetPublicSymbol(); }
}
protected override IArrayTypeSymbol CommonCreateArrayTypeSymbol(ITypeSymbol elementType, int rank, CodeAnalysis.NullableAnnotation elementNullableAnnotation)
{
return CreateArrayTypeSymbol(elementType.EnsureCSharpSymbolOrNull(nameof(elementType)), rank, elementNullableAnnotation.ToInternalAnnotation()).GetPublicSymbol();
}
protected override IPointerTypeSymbol CommonCreatePointerTypeSymbol(ITypeSymbol elementType)
{
return CreatePointerTypeSymbol(elementType.EnsureCSharpSymbolOrNull(nameof(elementType)), elementType.NullableAnnotation.ToInternalAnnotation()).GetPublicSymbol();
}
protected override IFunctionPointerTypeSymbol CommonCreateFunctionPointerTypeSymbol(
ITypeSymbol returnType,
RefKind returnRefKind,
ImmutableArray<ITypeSymbol> parameterTypes,
ImmutableArray<RefKind> parameterRefKinds,
SignatureCallingConvention callingConvention,
ImmutableArray<INamedTypeSymbol> callingConventionTypes)
{
if (returnType is null)
{
throw new ArgumentNullException(nameof(returnType));
}
if (parameterTypes.IsDefault)
{
throw new ArgumentNullException(nameof(parameterTypes));
}
for (int i = 0; i < parameterTypes.Length; i++)
{
if (parameterTypes[i] is null)
{
throw new ArgumentNullException($"{nameof(parameterTypes)}[{i}]");
}
}
if (parameterRefKinds.IsDefault)
{
throw new ArgumentNullException(nameof(parameterRefKinds));
}
if (parameterRefKinds.Length != parameterTypes.Length)
{
// Given {0} parameter types and {1} parameter ref kinds. These must be the same.
throw new ArgumentException(string.Format(CSharpResources.NotSameNumberParameterTypesAndRefKinds, parameterTypes.Length, parameterRefKinds.Length));
}
if (returnRefKind == RefKind.Out)
{
//'RefKind.Out' is not a valid ref kind for a return type.
throw new ArgumentException(CSharpResources.OutIsNotValidForReturn);
}
if (callingConvention != SignatureCallingConvention.Unmanaged && !callingConventionTypes.IsDefaultOrEmpty)
{
throw new ArgumentException(string.Format(CSharpResources.CallingConventionTypesRequireUnmanaged, nameof(callingConventionTypes), nameof(callingConvention)));
}
if (!callingConvention.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(callingConvention));
}
var returnTypeWithAnnotations = TypeWithAnnotations.Create(returnType.EnsureCSharpSymbolOrNull(nameof(returnType)), returnType.NullableAnnotation.ToInternalAnnotation());
var parameterTypesWithAnnotations = parameterTypes.SelectAsArray(
type => TypeWithAnnotations.Create(type.EnsureCSharpSymbolOrNull(nameof(parameterTypes)), type.NullableAnnotation.ToInternalAnnotation()));
var internalCallingConvention = callingConvention.FromSignatureConvention();
var conventionModifiers = internalCallingConvention == CallingConvention.Unmanaged && !callingConventionTypes.IsDefaultOrEmpty
? callingConventionTypes.SelectAsArray((type, i, @this) => getCustomModifierForType(type, @this, i), this)
: ImmutableArray<CustomModifier>.Empty;
return FunctionPointerTypeSymbol.CreateFromParts(
internalCallingConvention,
conventionModifiers,
returnTypeWithAnnotations,
returnRefKind: returnRefKind,
parameterTypes: parameterTypesWithAnnotations,
parameterRefKinds: parameterRefKinds,
compilation: this).GetPublicSymbol();
static CustomModifier getCustomModifierForType(INamedTypeSymbol type, CSharpCompilation @this, int index)
{
if (type is null)
{
throw new ArgumentNullException($"{nameof(callingConventionTypes)}[{index}]");
}
var internalType = type.EnsureCSharpSymbolOrNull($"{nameof(callingConventionTypes)}[{index}]");
if (!FunctionPointerTypeSymbol.IsCallingConventionModifier(internalType) || @this.Assembly.CorLibrary != internalType.ContainingAssembly)
{
throw new ArgumentException(string.Format(CSharpResources.CallingConventionTypeIsInvalid, type.ToDisplayString()));
}
return CSharpCustomModifier.CreateOptional(internalType);
}
}
protected override INamedTypeSymbol CommonCreateNativeIntegerTypeSymbol(bool signed)
{
return CreateNativeIntegerTypeSymbol(signed).GetPublicSymbol();
}
internal new NamedTypeSymbol CreateNativeIntegerTypeSymbol(bool signed)
{
return GetSpecialType(signed ? SpecialType.System_IntPtr : SpecialType.System_UIntPtr).AsNativeInteger();
}
protected override INamedTypeSymbol CommonCreateTupleTypeSymbol(
ImmutableArray<ITypeSymbol> elementTypes,
ImmutableArray<string?> elementNames,
ImmutableArray<Location?> elementLocations,
ImmutableArray<CodeAnalysis.NullableAnnotation> elementNullableAnnotations)
{
var typesBuilder = ArrayBuilder<TypeWithAnnotations>.GetInstance(elementTypes.Length);
for (int i = 0; i < elementTypes.Length; i++)
{
ITypeSymbol typeSymbol = elementTypes[i];
var elementType = typeSymbol.EnsureCSharpSymbolOrNull($"{nameof(elementTypes)}[{i}]");
var annotation = (elementNullableAnnotations.IsDefault ? typeSymbol.NullableAnnotation : elementNullableAnnotations[i]).ToInternalAnnotation();
typesBuilder.Add(TypeWithAnnotations.Create(elementType, annotation));
}
return NamedTypeSymbol.CreateTuple(
locationOpt: null, // no location for the type declaration
elementTypesWithAnnotations: typesBuilder.ToImmutableAndFree(),
elementLocations: elementLocations,
elementNames: elementNames,
compilation: this,
shouldCheckConstraints: false,
includeNullability: false,
errorPositions: default).GetPublicSymbol();
}
protected override INamedTypeSymbol CommonCreateTupleTypeSymbol(
INamedTypeSymbol underlyingType,
ImmutableArray<string?> elementNames,
ImmutableArray<Location?> elementLocations,
ImmutableArray<CodeAnalysis.NullableAnnotation> elementNullableAnnotations)
{
NamedTypeSymbol csharpUnderlyingTuple = underlyingType.EnsureCSharpSymbolOrNull(nameof(underlyingType));
if (!csharpUnderlyingTuple.IsTupleTypeOfCardinality(out int cardinality))
{
throw new ArgumentException(CodeAnalysisResources.TupleUnderlyingTypeMustBeTupleCompatible, nameof(underlyingType));
}
elementNames = CheckTupleElementNames(cardinality, elementNames);
CheckTupleElementLocations(cardinality, elementLocations);
CheckTupleElementNullableAnnotations(cardinality, elementNullableAnnotations);
var tupleType = NamedTypeSymbol.CreateTuple(
csharpUnderlyingTuple, elementNames, elementLocations: elementLocations!);
if (!elementNullableAnnotations.IsDefault)
{
tupleType = tupleType.WithElementTypes(
tupleType.TupleElementTypesWithAnnotations.ZipAsArray(
elementNullableAnnotations,
(t, a) => TypeWithAnnotations.Create(t.Type, a.ToInternalAnnotation())));
}
return tupleType.GetPublicSymbol();
}
protected override INamedTypeSymbol CommonCreateAnonymousTypeSymbol(
ImmutableArray<ITypeSymbol> memberTypes,
ImmutableArray<string> memberNames,
ImmutableArray<Location> memberLocations,
ImmutableArray<bool> memberIsReadOnly,
ImmutableArray<CodeAnalysis.NullableAnnotation> memberNullableAnnotations)
{
for (int i = 0, n = memberTypes.Length; i < n; i++)
{
memberTypes[i].EnsureCSharpSymbolOrNull($"{nameof(memberTypes)}[{i}]");
}
if (!memberIsReadOnly.IsDefault && memberIsReadOnly.Any(static v => !v))
{
throw new ArgumentException($"Non-ReadOnly members are not supported in C# anonymous types.");
}
var fields = ArrayBuilder<AnonymousTypeField>.GetInstance();
for (int i = 0, n = memberTypes.Length; i < n; i++)
{
var type = memberTypes[i].GetSymbol();
var name = memberNames[i];
var location = memberLocations.IsDefault ? Location.None : memberLocations[i];
var nullableAnnotation = memberNullableAnnotations.IsDefault ? NullableAnnotation.Oblivious : memberNullableAnnotations[i].ToInternalAnnotation();
fields.Add(new AnonymousTypeField(name, location, TypeWithAnnotations.Create(type, nullableAnnotation), RefKind.None, ScopedKind.None));
}
var descriptor = new AnonymousTypeDescriptor(fields.ToImmutableAndFree(), Location.None);
return this.AnonymousTypeManager.ConstructAnonymousTypeSymbol(descriptor).GetPublicSymbol();
}
protected override IMethodSymbol CommonCreateBuiltinOperator(
string name,
ITypeSymbol returnType,
ITypeSymbol leftType,
ITypeSymbol rightType)
{
var csharpReturnType = returnType.EnsureCSharpSymbolOrNull(nameof(returnType));
var csharpLeftType = leftType.EnsureCSharpSymbolOrNull(nameof(leftType));
var csharpRightType = rightType.EnsureCSharpSymbolOrNull(nameof(rightType));
// caller already checked all of these were not null.
Debug.Assert(csharpReturnType is not null);
Debug.Assert(csharpLeftType is not null);
Debug.Assert(csharpRightType is not null);
var syntaxKind = SyntaxFacts.GetOperatorKind(name);
if (syntaxKind == SyntaxKind.None)
throw new ArgumentException(string.Format(CodeAnalysisResources.BadBuiltInOps1, name), nameof(name));
var binaryOperatorName = OperatorFacts.BinaryOperatorNameFromSyntaxKindIfAny(syntaxKind, SyntaxFacts.IsCheckedOperator(name));
if (binaryOperatorName != name)
throw new ArgumentException(string.Format(CodeAnalysisResources.BadBuiltInOps3, name), nameof(name));
// Lang specific checks to ensure this is an acceptable operator.
validateSignature();
return new SynthesizedIntrinsicOperatorSymbol(csharpLeftType, name, csharpRightType, csharpReturnType).GetPublicSymbol();
void validateSignature()
{
// Dynamic built-in operators allow virtually all operations with all types. So we do no further checking here.
if (csharpReturnType.TypeKind is TypeKind.Dynamic ||
csharpLeftType.TypeKind is TypeKind.Dynamic ||
csharpRightType.TypeKind is TypeKind.Dynamic)
{
return;
}
// Use fast-path check to see if this types are ok.
var binaryKind = Binder.SyntaxKindToBinaryOperatorKind(SyntaxFacts.GetBinaryExpression(syntaxKind));
if (csharpReturnType.SpecialType != SpecialType.None &&
csharpLeftType.SpecialType != SpecialType.None &&
csharpRightType.SpecialType != SpecialType.None)
{
var easyOutBinaryKind = OverloadResolution.BinopEasyOut.OpKind(binaryKind, csharpLeftType, csharpRightType);
if (easyOutBinaryKind != BinaryOperatorKind.Error)
{
var signature = this.BuiltInOperators.GetSignature(easyOutBinaryKind);
if (csharpReturnType.SpecialType == signature.ReturnType.SpecialType &&
csharpLeftType.SpecialType == signature.LeftType.SpecialType &&
csharpRightType.SpecialType == signature.RightType.SpecialType)
{
return;
}
}
}
// bool operator ==(object, object) is legal.
// bool operator !=(object, object) is legal.
// bool operator ==(Delegate, Delegate) is legal.
// bool operator !=(Delegate, Delegate) is legal.
if (binaryKind is BinaryOperatorKind.Equal or BinaryOperatorKind.NotEqual &&
csharpReturnType.SpecialType is SpecialType.System_Boolean)
{
if ((csharpLeftType.SpecialType, csharpRightType.SpecialType) is
(SpecialType.System_Object, SpecialType.System_Object) or
(SpecialType.System_Delegate, SpecialType.System_Delegate))
{
return;
}
}
// Actual delegates have several operators that can be used on them.
if (csharpLeftType.TypeKind is TypeKind.Delegate &&
TypeSymbol.Equals(csharpLeftType, csharpRightType, TypeCompareKind.ConsiderEverything))
{
// bool operator ==(SomeDelegate, SomeDelegate) is legal.
// bool operator !=(SomeDelegate, SomeDelegate) is legal.
if (binaryKind is BinaryOperatorKind.Equal or BinaryOperatorKind.NotEqual &&
csharpReturnType.SpecialType == SpecialType.System_Boolean)
{
return;
}
// SomeDelegate operator +(SomeDelegate, SomeDelegate) is legal.
// SomeDelegate operator -(SomeDelegate, SomeDelegate) is legal.
if (binaryKind is BinaryOperatorKind.Addition or BinaryOperatorKind.Subtraction &&
TypeSymbol.Equals(csharpLeftType, csharpReturnType, TypeCompareKind.ConsiderEverything))
{
return;
}
}
if (csharpLeftType.IsEnumType() || csharpRightType.IsEnumType())
{
// bool operator ==(SomeEnum, SomeEnum) is legal.
// bool operator !=(SomeEnum, SomeEnum) is legal.
// bool operator >(SomeEnum, SomeEnum) is legal.
// bool operator <(SomeEnum, SomeEnum) is legal.
// bool operator >=(SomeEnum, SomeEnum) is legal.
// bool operator <=(SomeEnum, SomeEnum) is legal.
if (binaryKind is BinaryOperatorKind.Equal or
BinaryOperatorKind.NotEqual or
BinaryOperatorKind.GreaterThan or
BinaryOperatorKind.LessThan or
BinaryOperatorKind.GreaterThanOrEqual or
BinaryOperatorKind.LessThanOrEqual &&
csharpReturnType.SpecialType is SpecialType.System_Boolean &&
TypeSymbol.Equals(csharpLeftType, csharpRightType, TypeCompareKind.ConsiderEverything))
{
return;
}
// SomeEnum operator &(SomeEnum, SomeEnum) is legal.
// SomeEnum operator |(SomeEnum, SomeEnum) is legal.
// SomeEnum operator ^(SomeEnum, SomeEnum) is legal.
if (binaryKind is BinaryOperatorKind.And or
BinaryOperatorKind.Or or
BinaryOperatorKind.Xor &&
TypeSymbol.Equals(csharpLeftType, csharpRightType, TypeCompareKind.ConsiderEverything) &&
TypeSymbol.Equals(csharpReturnType, csharpRightType, TypeCompareKind.ConsiderEverything))
{
return;
}
// SomeEnum operator+(SomeEnum, EnumUnderlyingInt)
// SomeEnum operator+(EnumUnderlyingInt, SomeEnum)
// SomeEnum operator-(SomeEnum, EnumUnderlyingInt)
// SomeEnum operator-(EnumUnderlyingInt, SomeEnum)
if (binaryKind is BinaryOperatorKind.Addition or BinaryOperatorKind.Subtraction)
{
if (csharpLeftType.IsEnumType() &&
csharpRightType.SpecialType == csharpLeftType.GetEnumUnderlyingType()?.SpecialType &&
TypeSymbol.Equals(csharpLeftType, csharpReturnType, TypeCompareKind.ConsiderEverything))
{
return;
}
if (csharpRightType.IsEnumType() &&
csharpLeftType.SpecialType == csharpRightType.GetEnumUnderlyingType()?.SpecialType &&
TypeSymbol.Equals(csharpRightType, csharpReturnType, TypeCompareKind.ConsiderEverything))
{
return;
}
}
// EnumUnderlyingInt operator-(SomeEnum, SomeEnum)
if (binaryKind is BinaryOperatorKind.Subtraction &&
csharpReturnType.SpecialType == csharpLeftType.GetEnumUnderlyingType()?.SpecialType &&
TypeSymbol.Equals(csharpLeftType, csharpRightType, TypeCompareKind.ConsiderEverything))
{
return;
}
}
// void* has several comparison operators built in.
if (binaryKind is BinaryOperatorKind.Equal or
BinaryOperatorKind.NotEqual or
BinaryOperatorKind.GreaterThan or
BinaryOperatorKind.LessThan or
BinaryOperatorKind.GreaterThanOrEqual or
BinaryOperatorKind.LessThanOrEqual &&
csharpReturnType.SpecialType is SpecialType.System_Boolean &&
csharpLeftType is PointerTypeSymbol { PointedAtType.SpecialType: SpecialType.System_Void } &&
csharpRightType is PointerTypeSymbol { PointedAtType.SpecialType: SpecialType.System_Void })
{
return;
}
// T* operator+(T*, int/uint/long/ulong i)
if (binaryKind is BinaryOperatorKind.Addition &&
csharpLeftType.IsPointerType() &&
isAllowedPointerArithmeticIntegralType(csharpRightType) &&
TypeSymbol.Equals(csharpLeftType, csharpReturnType, TypeCompareKind.ConsiderEverything))
{
return;
}
// T* operator+(int/uint/long/ulong i, T*)
if (binaryKind is BinaryOperatorKind.Addition &&
csharpRightType.IsPointerType() &&
isAllowedPointerArithmeticIntegralType(csharpLeftType) &&
TypeSymbol.Equals(csharpRightType, csharpReturnType, TypeCompareKind.ConsiderEverything))
{
return;
}
// T* operator-(T*, int/uint/long/ulong i)
if (binaryKind is BinaryOperatorKind.Subtraction &&
csharpLeftType.IsPointerType() &&
isAllowedPointerArithmeticIntegralType(csharpRightType) &&
TypeSymbol.Equals(csharpLeftType, csharpReturnType, TypeCompareKind.ConsiderEverything))
{
return;
}
// long operator-(T*, T*)
if (binaryKind is BinaryOperatorKind.Subtraction &&
csharpLeftType.IsPointerType() &&
csharpReturnType.SpecialType is SpecialType.System_Int64 &&
TypeSymbol.Equals(csharpLeftType, csharpRightType, TypeCompareKind.ConsiderEverything))
{
return;
}
// ROS<byte> operator+(ROS<byte>, ROS<byte>). Legal because of utf8 strings.
if (binaryKind is BinaryOperatorKind.Addition &&
isReadOnlySpanOfByteType(csharpReturnType) &&
isReadOnlySpanOfByteType(csharpLeftType) &&
isReadOnlySpanOfByteType(csharpRightType))
{
return;
}
throw new ArgumentException(string.Format(CodeAnalysisResources.BadBuiltInOps2, $"{csharpReturnType.ToDisplayString()} operator {name}({csharpLeftType.ToDisplayString()}, {csharpRightType.ToDisplayString()})"));
}
bool isAllowedPointerArithmeticIntegralType(TypeSymbol type)
=> type.SpecialType is SpecialType.System_Int32 or SpecialType.System_UInt32 or SpecialType.System_Int64 or SpecialType.System_UInt64;
bool isReadOnlySpanOfByteType(TypeSymbol type)
=> IsReadOnlySpanType(type) && ((NamedTypeSymbol)type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].SpecialType == SpecialType.System_Byte;
}
protected override IMethodSymbol CommonCreateBuiltinOperator(
string name,
ITypeSymbol returnType,
ITypeSymbol operandType)
{
var csharpReturnType = returnType.EnsureCSharpSymbolOrNull(nameof(returnType));
var csharpOperandType = operandType.EnsureCSharpSymbolOrNull(nameof(operandType));
// caller already checked all of these were not null.
Debug.Assert(csharpReturnType is not null);
Debug.Assert(csharpOperandType is not null);
var syntaxKind = SyntaxFacts.GetOperatorKind(name);
// Currently compiler does not generate built-ins for `operator true/false`. If that changes, this check
// can be relaxed.
if (syntaxKind == SyntaxKind.None || name is WellKnownMemberNames.TrueOperatorName or WellKnownMemberNames.FalseOperatorName)
throw new ArgumentException(string.Format(CodeAnalysisResources.BadBuiltInOps1, name), nameof(name));
var unaryOperatorName = OperatorFacts.UnaryOperatorNameFromSyntaxKindIfAny(syntaxKind, SyntaxFacts.IsCheckedOperator(name));
if (unaryOperatorName != name)
throw new ArgumentException(string.Format(CodeAnalysisResources.BadBuiltInOps3, name), nameof(name));
// Lang specific checks to ensure this is an acceptable operator.
validateSignature();
return new SynthesizedIntrinsicOperatorSymbol(csharpOperandType, name, csharpReturnType).GetPublicSymbol();
void validateSignature()
{
// Dynamic built-in operators allow virtually all operations with all types. So we do no further checking here.
if (csharpReturnType.TypeKind is TypeKind.Dynamic ||
csharpOperandType.TypeKind is TypeKind.Dynamic)
{
return;
}
var unaryKind = Binder.SyntaxKindToUnaryOperatorKind(SyntaxFacts.GetPrefixUnaryExpression(syntaxKind));
// Use fast-path check to see if this types are ok.
if (csharpReturnType.SpecialType != SpecialType.None && csharpOperandType.SpecialType != SpecialType.None)
{
var easyOutUnaryKind = OverloadResolution.UnopEasyOut.OpKind(unaryKind, csharpOperandType);
if (easyOutUnaryKind != UnaryOperatorKind.Error)
{
var signature = this.BuiltInOperators.GetSignature(easyOutUnaryKind);
if (csharpReturnType.SpecialType == signature.ReturnType.SpecialType &&
csharpOperandType.SpecialType == signature.OperandType.SpecialType)
{
return;
}
}
}
// EnumType operator++(EnumType)
// EnumType operator~(EnumType)
if (csharpOperandType.IsEnumType() &&
unaryKind is UnaryOperatorKind.PrefixIncrement or UnaryOperatorKind.PrefixDecrement or UnaryOperatorKind.BitwiseComplement &&
TypeSymbol.Equals(csharpOperandType, csharpReturnType, TypeCompareKind.ConsiderEverything))
{
return;
}
// T* operator++(T*)
if (csharpOperandType.IsPointerType() &&
unaryKind is UnaryOperatorKind.PrefixIncrement or UnaryOperatorKind.PrefixDecrement &&
TypeSymbol.Equals(csharpOperandType, csharpReturnType, TypeCompareKind.ConsiderEverything))
{
return;
}
throw new ArgumentException(string.Format(CodeAnalysisResources.BadBuiltInOps2, $"{csharpReturnType.ToDisplayString()} operator {name}({csharpOperandType.ToDisplayString()})"));
}
}
protected override ITypeSymbol CommonDynamicType
{
get { return DynamicType.GetPublicSymbol(); }
}
protected override INamedTypeSymbol CommonObjectType
{
get { return this.ObjectType.GetPublicSymbol(); }
}
protected override IMethodSymbol? CommonGetEntryPoint(CancellationToken cancellationToken)
{
return this.GetEntryPoint(cancellationToken).GetPublicSymbol();
}
internal override int CompareSourceLocations(Location loc1, Location loc2)
{
Debug.Assert(loc1.IsInSource);
Debug.Assert(loc2.IsInSource);
var comparison = CompareSyntaxTreeOrdering(loc1.SourceTree!, loc2.SourceTree!);
if (comparison != 0)
{
return comparison;
}
return loc1.SourceSpan.Start - loc2.SourceSpan.Start;
}
internal override int CompareSourceLocations(SyntaxReference loc1, SyntaxReference loc2)
{
var comparison = CompareSyntaxTreeOrdering(loc1.SyntaxTree, loc2.SyntaxTree);
if (comparison != 0)
{
return comparison;
}
return loc1.Span.Start - loc2.Span.Start;
}
internal override int CompareSourceLocations(SyntaxNode loc1, SyntaxNode loc2)
{
var comparison = CompareSyntaxTreeOrdering(loc1.SyntaxTree, loc2.SyntaxTree);
if (comparison != 0)
{
return comparison;
}
return loc1.Span.Start - loc2.Span.Start;
}
/// <summary>
/// Return true if there is a source declaration symbol name that meets given predicate.
/// </summary>
public override bool ContainsSymbolsWithName(Func<string, bool> predicate, SymbolFilter filter = SymbolFilter.TypeAndMember, CancellationToken cancellationToken = default)
{
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}
if (filter == SymbolFilter.None)
{
throw new ArgumentException(CSharpResources.NoNoneSearchCriteria, nameof(filter));
}
return DeclarationTable.ContainsName(this.MergedRootDeclaration, predicate, filter, cancellationToken);
}
/// <summary>
/// Return source declaration symbols whose name meets given predicate.
/// </summary>
public override IEnumerable<ISymbol> GetSymbolsWithName(Func<string, bool> predicate, SymbolFilter filter = SymbolFilter.TypeAndMember, CancellationToken cancellationToken = default)
{
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}
if (filter == SymbolFilter.None)
{
throw new ArgumentException(CSharpResources.NoNoneSearchCriteria, nameof(filter));
}
return new PredicateSymbolSearcher(this, filter, predicate, cancellationToken).GetSymbolsWithName().GetPublicSymbols()!;
}
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
/// <summary>
/// Return true if there is a source declaration symbol name that matches the provided name.
/// This will be faster than <see cref="ContainsSymbolsWithName(Func{string, bool}, SymbolFilter, CancellationToken)"/>
/// when predicate is just a simple string check.
/// </summary>
public override bool ContainsSymbolsWithName(string name, SymbolFilter filter = SymbolFilter.TypeAndMember, CancellationToken cancellationToken = default)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (filter == SymbolFilter.None)
{
throw new ArgumentException(CSharpResources.NoNoneSearchCriteria, nameof(filter));
}
return DeclarationTable.ContainsName(this.MergedRootDeclaration, name, filter, cancellationToken);
}
/// <summary>
/// Return source declaration symbols whose name matches the provided name. This will be
/// faster than <see cref="GetSymbolsWithName(Func{string, bool}, SymbolFilter,
/// CancellationToken)"/> when predicate is just a simple string check. <paramref
/// name="name"/> is case sensitive.
/// </summary>
public override IEnumerable<ISymbol> GetSymbolsWithName(string name, SymbolFilter filter = SymbolFilter.TypeAndMember, CancellationToken cancellationToken = default)
{
return GetSymbolsWithNameCore(name, filter, cancellationToken).GetPublicSymbols()!;
}
internal IEnumerable<Symbol> GetSymbolsWithNameCore(string name, SymbolFilter filter = SymbolFilter.TypeAndMember, CancellationToken cancellationToken = default)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (filter == SymbolFilter.None)
{
throw new ArgumentException(CSharpResources.NoNoneSearchCriteria, nameof(filter));
}
return new NameSymbolSearcher(this, filter, name, cancellationToken).GetSymbolsWithName();
}
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
#endregion
/// <summary>
/// Returns if the compilation has all of the members necessary to emit metadata about
/// dynamic types.
/// </summary>
/// <returns></returns>
internal bool HasDynamicEmitAttributes(BindingDiagnosticBag diagnostics, Location location)
{
return Binder.GetWellKnownTypeMember(this, WellKnownMember.System_Runtime_CompilerServices_DynamicAttribute__ctor, diagnostics, location) is object &&
Binder.GetWellKnownTypeMember(this, WellKnownMember.System_Runtime_CompilerServices_DynamicAttribute__ctorTransformFlags, diagnostics, location) is object;
}
internal bool HasTupleNamesAttributes(BindingDiagnosticBag diagnostics, Location location) =>
Binder.GetWellKnownTypeMember(this, WellKnownMember.System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames, diagnostics, location) is object;
/// <summary>
/// Returns whether the compilation has the Boolean type and if it's good.
/// </summary>
/// <returns>Returns true if Boolean is present and healthy.</returns>
internal bool CanEmitBoolean() => CanEmitSpecialType(SpecialType.System_Boolean);
internal bool CanEmitSpecialType(SpecialType type)
{
var typeSymbol = GetSpecialType(type);
var diagnostic = typeSymbol.GetUseSiteInfo().DiagnosticInfo;
return (diagnostic == null) || (diagnostic.Severity != DiagnosticSeverity.Error);
}
internal bool EmitNullablePublicOnly
{
get
{
if (!_lazyEmitNullablePublicOnly.HasValue())
{
bool value = SyntaxTrees.FirstOrDefault()?.Options?.Features?.ContainsKey("nullablePublicOnly") == true;
_lazyEmitNullablePublicOnly = value.ToThreeState();
}
return _lazyEmitNullablePublicOnly.Value();
}
}
internal bool ShouldEmitNativeIntegerAttributes()
{
return !Assembly.RuntimeSupportsNumericIntPtr;
}
internal bool ShouldEmitNullableAttributes(Symbol symbol)
{
RoslynDebug.Assert(symbol is object);
Debug.Assert(symbol.IsDefinition);
if (symbol.ContainingModule != SourceModule)
{
return false;
}
if (!EmitNullablePublicOnly)
{
return true;
}
// For symbols that do not have explicit accessibility in metadata,
// use the accessibility of the container.
symbol = getExplicitAccessibilitySymbol(symbol);
if (!AccessCheck.IsEffectivelyPublicOrInternal(symbol, out bool isInternal))
{
return false;
}
return !isInternal || SourceAssembly.InternalsAreVisible;
static Symbol getExplicitAccessibilitySymbol(Symbol symbol)
{
while (true)
{
switch (symbol.Kind)
{
case SymbolKind.Parameter:
case SymbolKind.TypeParameter:
case SymbolKind.Property:
case SymbolKind.Event:
symbol = symbol.ContainingSymbol;
break;
default:
return symbol;
}
}
}
}
internal override AnalyzerDriver CreateAnalyzerDriver(ImmutableArray<DiagnosticAnalyzer> analyzers, AnalyzerManager analyzerManager, SeverityFilter severityFilter)
{
Func<SyntaxNode, SyntaxKind> getKind = node => node.Kind();
Func<SyntaxTrivia, bool> isComment = trivia => trivia.Kind() == SyntaxKind.SingleLineCommentTrivia || trivia.Kind() == SyntaxKind.MultiLineCommentTrivia;
return new AnalyzerDriver<SyntaxKind>(analyzers, getKind, analyzerManager, severityFilter, isComment);
}
internal void SymbolDeclaredEvent(Symbol symbol)
{
EventQueue?.TryEnqueue(new SymbolDeclaredCompilationEvent(this, symbol));
}
internal override void SerializePdbEmbeddedCompilationOptions(BlobBuilder builder)
{
// LanguageVersion should already be mapped to a specific version
Debug.Assert(LanguageVersion == LanguageVersion.MapSpecifiedToEffectiveVersion());
writeValue(CompilationOptionNames.LanguageVersion, LanguageVersion.ToDisplayString());
if (Options.CheckOverflow)
{
writeValue(CompilationOptionNames.Checked, Options.CheckOverflow.ToString());
}
if (Options.NullableContextOptions != NullableContextOptions.Disable)
{
writeValue(CompilationOptionNames.Nullable, Options.NullableContextOptions.ToString());
}
if (Options.AllowUnsafe)
{
writeValue(CompilationOptionNames.Unsafe, Options.AllowUnsafe.ToString());
}
var preprocessorSymbols = GetPreprocessorSymbols();
if (preprocessorSymbols.Any())
{
writeValue(CompilationOptionNames.Define, string.Join(",", preprocessorSymbols));
}
void writeValue(string key, string value)
{
builder.WriteUTF8(key);
builder.WriteByte(0);
builder.WriteUTF8(value);
builder.WriteByte(0);
}
}
private ImmutableArray<string> GetPreprocessorSymbols()
{
CSharpSyntaxTree? firstTree = (CSharpSyntaxTree?)SyntaxTrees.FirstOrDefault();
if (firstTree is null)
{
return ImmutableArray<string>.Empty;
}
return firstTree.Options.PreprocessorSymbolNames.ToImmutableArray();
}
/// <summary>
/// Determine if enum arrays can be initialized using block initialization.
/// </summary>
/// <returns>True if it's safe to use block initialization for enum arrays.</returns>
/// <remarks>
/// In NetFx 4.0, block array initializers do not work on all combinations of {32/64 X Debug/Retail} when array elements are enums.
/// This is fixed in 4.5 thus enabling block array initialization for a very common case.
/// We look for the presence of <see cref="System.Runtime.GCLatencyMode.SustainedLowLatency"/> which was introduced in .NET Framework 4.5
/// </remarks>
internal bool EnableEnumArrayBlockInitialization
{
get
{
var sustainedLowLatency = GetWellKnownTypeMember(WellKnownMember.System_Runtime_GCLatencyMode__SustainedLowLatency);
return sustainedLowLatency != null && sustainedLowLatency.ContainingAssembly == Assembly.CorLibrary;
}
}
private protected override bool SupportsRuntimeCapabilityCore(RuntimeCapability capability)
=> this.Assembly.SupportsRuntimeCapability(capability);
private abstract class AbstractSymbolSearcher
{
private readonly PooledDictionary<Declaration, NamespaceOrTypeSymbol> _cache;
private readonly CSharpCompilation _compilation;
private readonly bool _includeNamespace;
private readonly bool _includeType;
private readonly bool _includeMember;
private readonly CancellationToken _cancellationToken;
protected AbstractSymbolSearcher(
CSharpCompilation compilation, SymbolFilter filter, CancellationToken cancellationToken)
{
_cache = PooledDictionary<Declaration, NamespaceOrTypeSymbol>.GetInstance();
_compilation = compilation;
_includeNamespace = (filter & SymbolFilter.Namespace) == SymbolFilter.Namespace;
_includeType = (filter & SymbolFilter.Type) == SymbolFilter.Type;
_includeMember = (filter & SymbolFilter.Member) == SymbolFilter.Member;
_cancellationToken = cancellationToken;
}
protected abstract bool Matches(string name);
protected abstract bool ShouldCheckTypeForMembers(MergedTypeDeclaration current);
public IEnumerable<Symbol> GetSymbolsWithName()
{
var result = new HashSet<Symbol>();
var spine = ArrayBuilder<MergedNamespaceOrTypeDeclaration>.GetInstance();
AppendSymbolsWithName(spine, _compilation.MergedRootDeclaration, result);
spine.Free();
_cache.Free();
return result;
}
private void AppendSymbolsWithName(
ArrayBuilder<MergedNamespaceOrTypeDeclaration> spine, MergedNamespaceOrTypeDeclaration current,
HashSet<Symbol> set)
{
if (current.Kind == DeclarationKind.Namespace)
{
if (_includeNamespace && Matches(current.Name))
{
var container = GetSpineSymbol(spine);
var symbol = GetSymbol(container, current);
if (symbol != null)
{
set.Add(symbol);
}
}
}
else
{
if (_includeType && Matches(current.Name))
{
var container = GetSpineSymbol(spine);
var symbol = GetSymbol(container, current);
if (symbol != null)
{
set.Add(symbol);
}
}
if (_includeMember)
{
var typeDeclaration = (MergedTypeDeclaration)current;
if (ShouldCheckTypeForMembers(typeDeclaration))
{
AppendMemberSymbolsWithName(spine, typeDeclaration, set);
}
}
}
spine.Add(current);
foreach (var child in current.Children)
{
if (child is MergedNamespaceOrTypeDeclaration mergedNamespaceOrType)
{
if (_includeMember || _includeType || child.Kind == DeclarationKind.Namespace)
{
AppendSymbolsWithName(spine, mergedNamespaceOrType, set);
}
}
}
// pop last one
spine.RemoveAt(spine.Count - 1);
}
private void AppendMemberSymbolsWithName(
ArrayBuilder<MergedNamespaceOrTypeDeclaration> spine, MergedTypeDeclaration current, HashSet<Symbol> set)
{
_cancellationToken.ThrowIfCancellationRequested();
spine.Add(current);
var container = GetSpineSymbol(spine);
if (container != null)
{
foreach (var member in container.GetMembers())
{
if (!member.IsTypeOrTypeAlias() &&
(member.CanBeReferencedByName || member.IsExplicitInterfaceImplementation() || member.IsIndexer()) &&
Matches(member.Name))
{
set.Add(member);
}
}
}
spine.RemoveAt(spine.Count - 1);
}
protected NamespaceOrTypeSymbol? GetSpineSymbol(ArrayBuilder<MergedNamespaceOrTypeDeclaration> spine)
{
if (spine.Count == 0)
{
return null;
}
var symbol = GetCachedSymbol(spine[spine.Count - 1]);
if (symbol != null)
{
return symbol;
}
NamespaceOrTypeSymbol? current = _compilation.GlobalNamespace;
for (var i = 1; i < spine.Count; i++)
{
current = GetSymbol(current, spine[i]);
}
return current;
}
private NamespaceOrTypeSymbol? GetCachedSymbol(MergedNamespaceOrTypeDeclaration declaration)
=> _cache.TryGetValue(declaration, out NamespaceOrTypeSymbol? symbol)
? symbol
: null;
private NamespaceOrTypeSymbol? GetSymbol(NamespaceOrTypeSymbol? container, MergedNamespaceOrTypeDeclaration declaration)
{
if (container == null)
{
return _compilation.GlobalNamespace;
}
if (declaration.Kind == DeclarationKind.Namespace)
{
AddCache(container.GetMembers(declaration.Name).OfType<NamespaceOrTypeSymbol>());
}
else
{
AddCache(container.GetTypeMembers(declaration.Name));
}
return GetCachedSymbol(declaration);
}
private void AddCache(IEnumerable<NamespaceOrTypeSymbol> symbols)
{
foreach (var symbol in symbols)
{
var mergedNamespace = symbol as MergedNamespaceSymbol;
if (mergedNamespace != null)
{
_cache[mergedNamespace.ConstituentNamespaces.OfType<SourceNamespaceSymbol>().First().MergedDeclaration] = symbol;
continue;
}
var sourceNamespace = symbol as SourceNamespaceSymbol;
if (sourceNamespace != null)
{
_cache[sourceNamespace.MergedDeclaration] = sourceNamespace;
continue;
}
var sourceType = symbol as SourceMemberContainerTypeSymbol;
if (sourceType is object)
{
_cache[sourceType.MergedDeclaration] = sourceType;
}
}
}
}
private class PredicateSymbolSearcher : AbstractSymbolSearcher
{
private readonly Func<string, bool> _predicate;
public PredicateSymbolSearcher(
CSharpCompilation compilation, SymbolFilter filter, Func<string, bool> predicate, CancellationToken cancellationToken)
: base(compilation, filter, cancellationToken)
{
_predicate = predicate;
}
protected override bool ShouldCheckTypeForMembers(MergedTypeDeclaration current)
{
// Note: this preserves the behavior the compiler has always had when a predicate
// is passed in. We could potentially be smarter by checking the predicate
// against the list of member names in the type declaration first.
return true;
}
protected override bool Matches(string name)
=> _predicate(name);
}
private class NameSymbolSearcher : AbstractSymbolSearcher
{
private readonly string _name;
public NameSymbolSearcher(
CSharpCompilation compilation, SymbolFilter filter, string name, CancellationToken cancellationToken)
: base(compilation, filter, cancellationToken)
{
_name = name;
}
protected override bool ShouldCheckTypeForMembers(MergedTypeDeclaration current)
{
foreach (SingleTypeDeclaration typeDecl in current.Declarations)
{
if (typeDecl.MemberNames.Value.Contains(_name))
{
return true;
}
}
return false;
}
protected override bool Matches(string name)
=> _name == name;
}
}
}
|