|
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.RuntimeMembers;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class Binder
{
/// <summary>
/// Binds the type for the syntax taking into account possibility of "var" type.
/// </summary>
/// <param name="syntax">Type syntax to bind.</param>
/// <param name="diagnostics">Diagnostics.</param>
/// <param name="isVar">
/// Set to false if syntax binds to a type in the current context and true if
/// syntax is "var" and it binds to "var" keyword in the current context.
/// </param>
/// <returns>
/// Bound type if syntax binds to a type in the current context and
/// null if syntax binds to "var" keyword in the current context.
/// </returns>
internal TypeWithAnnotations BindTypeOrVarKeyword(TypeSyntax syntax, BindingDiagnosticBag diagnostics, out bool isVar)
{
var symbol = BindTypeOrAliasOrVarKeyword(syntax, diagnostics, out isVar);
Debug.Assert(isVar == symbol.IsDefault);
return isVar ? default : UnwrapAlias(symbol, diagnostics, syntax).TypeWithAnnotations;
}
/// <summary>
/// Binds the type for the syntax taking into account possibility of "unmanaged" type.
/// </summary>
/// <param name="syntax">Type syntax to bind.</param>
/// <param name="diagnostics">Diagnostics.</param>
/// <param name="keyword">
/// Set to <see cref="ConstraintContextualKeyword.None"/> if syntax binds to a type in the current context, otherwise
/// syntax binds to the corresponding keyword in the current context.
/// </param>
/// <returns>
/// Bound type if syntax binds to a type in the current context and
/// null if syntax binds to a contextual constraint keyword.
/// </returns>
private TypeWithAnnotations BindTypeOrConstraintKeyword(TypeSyntax syntax, BindingDiagnosticBag diagnostics, out ConstraintContextualKeyword keyword)
{
var symbol = BindTypeOrAliasOrConstraintKeyword(syntax, diagnostics, out keyword);
Debug.Assert((keyword != ConstraintContextualKeyword.None) == symbol.IsDefault);
return (keyword != ConstraintContextualKeyword.None) ? default : UnwrapAlias(symbol, diagnostics, syntax).TypeWithAnnotations;
}
/// <summary>
/// Binds the type for the syntax taking into account possibility of "var" type.
/// </summary>
/// <param name="syntax">Type syntax to bind.</param>
/// <param name="diagnostics">Diagnostics.</param>
/// <param name="isVar">
/// Set to false if syntax binds to a type in the current context and true if
/// syntax is "var" and it binds to "var" keyword in the current context.
/// </param>
/// <param name="alias">Alias symbol if syntax binds to an alias.</param>
/// <returns>
/// Bound type if syntax binds to a type in the current context and
/// null if syntax binds to "var" keyword in the current context.
/// </returns>
internal TypeWithAnnotations BindTypeOrVarKeyword(TypeSyntax syntax, BindingDiagnosticBag diagnostics, out bool isVar, out AliasSymbol alias)
{
var symbol = BindTypeOrAliasOrVarKeyword(syntax, diagnostics, out isVar);
Debug.Assert(isVar == symbol.IsDefault);
if (isVar)
{
alias = null;
return default;
}
else
{
return UnwrapAlias(symbol, out alias, diagnostics, syntax).TypeWithAnnotations;
}
}
/// <summary>
/// Binds the type for the syntax taking into account possibility of "var" type.
/// If the syntax binds to an alias symbol to a type, it returns the alias symbol.
/// </summary>
/// <param name="syntax">Type syntax to bind.</param>
/// <param name="diagnostics">Diagnostics.</param>
/// <param name="isVar">
/// Set to false if syntax binds to a type or alias to a type in the current context and true if
/// syntax is "var" and it binds to "var" keyword in the current context.
/// </param>
/// <returns>
/// Bound type or alias if syntax binds to a type or alias to a type in the current context and
/// null if syntax binds to "var" keyword in the current context.
/// </returns>
private NamespaceOrTypeOrAliasSymbolWithAnnotations BindTypeOrAliasOrVarKeyword(TypeSyntax syntax, BindingDiagnosticBag diagnostics, out bool isVar)
{
if (syntax.IsVar)
{
var symbol = BindTypeOrAliasOrKeyword((IdentifierNameSyntax)syntax, diagnostics, out isVar);
if (isVar)
{
CheckFeatureAvailability(syntax, MessageID.IDS_FeatureImplicitLocal, diagnostics);
}
return symbol;
}
else
{
isVar = false;
return BindTypeOrAlias(syntax, diagnostics, basesBeingResolved: null);
}
}
private enum ConstraintContextualKeyword
{
None,
Unmanaged,
NotNull,
}
private NamespaceOrTypeOrAliasSymbolWithAnnotations BindTypeOrAliasOrConstraintKeyword(TypeSyntax syntax, BindingDiagnosticBag diagnostics, out ConstraintContextualKeyword keyword)
{
if (syntax.IsUnmanaged)
{
keyword = ConstraintContextualKeyword.Unmanaged;
}
else if (syntax.IsNotNull)
{
keyword = ConstraintContextualKeyword.NotNull;
}
else
{
keyword = ConstraintContextualKeyword.None;
}
if (keyword != ConstraintContextualKeyword.None)
{
var identifierSyntax = (IdentifierNameSyntax)syntax;
var symbol = BindTypeOrAliasOrKeyword(identifierSyntax, diagnostics, out bool isKeyword);
if (isKeyword)
{
switch (keyword)
{
case ConstraintContextualKeyword.Unmanaged:
CheckFeatureAvailability(syntax, MessageID.IDS_FeatureUnmanagedGenericTypeConstraint, diagnostics);
break;
case ConstraintContextualKeyword.NotNull:
CheckFeatureAvailability(identifierSyntax, MessageID.IDS_FeatureNotNullGenericTypeConstraint, diagnostics);
break;
default:
throw ExceptionUtilities.UnexpectedValue(keyword);
}
}
else
{
keyword = ConstraintContextualKeyword.None;
}
return symbol;
}
else
{
return BindTypeOrAlias(syntax, diagnostics, basesBeingResolved: null);
}
}
/// <summary>
/// Binds the type for the syntax taking into account possibility of the type being a keyword.
/// If the syntax binds to an alias symbol to a type, it returns the alias symbol.
/// PREREQUISITE: syntax should be checked to match the keyword, like <see cref="TypeSyntax.IsVar"/> or <see cref="TypeSyntax.IsUnmanaged"/>.
/// Otherwise, call <see cref="Binder.BindTypeOrAlias(ExpressionSyntax, BindingDiagnosticBag, ConsList{TypeSymbol}, bool)"/> instead.
/// </summary>
private NamespaceOrTypeOrAliasSymbolWithAnnotations BindTypeOrAliasOrKeyword(IdentifierNameSyntax syntax, BindingDiagnosticBag diagnostics, out bool isKeyword)
{
return BindTypeOrAliasOrKeyword(((IdentifierNameSyntax)syntax).Identifier, syntax, diagnostics, out isKeyword);
}
private NamespaceOrTypeOrAliasSymbolWithAnnotations BindTypeOrAliasOrKeyword(SyntaxToken identifier, SyntaxNode syntax, BindingDiagnosticBag diagnostics, out bool isKeyword)
{
// Keywords can only be IdentifierNameSyntax
var identifierValueText = identifier.ValueText;
Symbol symbol = null;
// Perform name lookup without generating diagnostics as it could possibly be a keyword in the current context.
var lookupResult = LookupResult.GetInstance();
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
this.LookupSymbolsInternal(lookupResult, identifierValueText, arity: 0, useSiteInfo: ref discardedUseSiteInfo, basesBeingResolved: null, options: LookupOptions.NamespacesOrTypesOnly, diagnose: false);
// We have following possible cases for lookup:
// 1) LookupResultKind.Empty: must be a keyword
// 2) LookupResultKind.Viable:
// a) Single viable result that corresponds to 1) a non-error type: cannot be a keyword
// 2) an error type: must be a keyword
// b) Single viable result that corresponds to namespace: must be a keyword
// c) Multi viable result (ambiguous result), we must return an error type: cannot be a keyword
// 3) Non viable, non empty lookup result: must be a keyword
// BREAKING CHANGE: Case (2)(c) is a breaking change from the native compiler.
// BREAKING CHANGE: Native compiler interprets lookup with ambiguous result to correspond to bind
// BREAKING CHANGE: to "var" keyword (isVar = true), rather than reporting an error.
// BREAKING CHANGE: See test SemanticErrorTests.ErrorMeansSuccess_var() for an example.
switch (lookupResult.Kind)
{
case LookupResultKind.Empty:
// Case (1)
isKeyword = true;
symbol = null;
break;
case LookupResultKind.Viable:
// Case (2)
var resultDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: diagnostics.AccumulatesDependencies);
bool wasError;
symbol = ResultSymbol(
lookupResult,
identifierValueText,
arity: 0,
where: syntax,
diagnostics: resultDiagnostics,
suppressUseSiteDiagnostics: false,
wasError: out wasError,
qualifierOpt: null);
diagnostics.AddDependencies(resultDiagnostics);
// Here, we're mimicking behavior of dev10. If the identifier fails to bind
// as a type, even if the reason is (e.g.) a type/alias conflict, then treat
// it as the contextual keyword.
if (wasError && lookupResult.IsSingleViable)
{
// NOTE: don't report diagnostics - we're not going to use the lookup result.
resultDiagnostics.Free();
// Case (2)(a)(2)
goto default;
}
diagnostics.AddRange(resultDiagnostics.DiagnosticBag);
resultDiagnostics.Free();
if (lookupResult.IsSingleViable)
{
var type = UnwrapAlias(symbol, diagnostics, syntax) as TypeSymbol;
if ((object)type != null)
{
// Case (2)(a)(1)
isKeyword = false;
if (symbol.Kind != SymbolKind.Alias)
{
ReportDiagnosticsIfObsolete(diagnostics, type, syntax, hasBaseReceiver: false);
}
}
else
{
// Case (2)(b)
Debug.Assert(UnwrapAliasNoDiagnostics(symbol) is NamespaceSymbol);
isKeyword = true;
symbol = null;
}
}
else
{
// Case (2)(c)
isKeyword = false;
}
break;
default:
// Case (3)
isKeyword = true;
symbol = null;
break;
}
lookupResult.Free();
return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(AreNullableAnnotationsEnabled(identifier), symbol);
}
// Binds the given expression syntax as Type.
// If the resulting symbol is an Alias to a Type, it unwraps the alias
// and returns it's target type.
internal TypeWithAnnotations BindType(ExpressionSyntax syntax, BindingDiagnosticBag diagnostics, ConsList<TypeSymbol> basesBeingResolved = null, bool suppressUseSiteDiagnostics = false)
{
var symbol = BindTypeOrAlias(syntax, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics);
return UnwrapAlias(symbol, diagnostics, syntax, basesBeingResolved).TypeWithAnnotations;
}
// Binds the given expression syntax as Type.
// If the resulting symbol is an Alias to a Type, it stores the AliasSymbol in
// the alias parameter, unwraps the alias and returns it's target type.
internal TypeWithAnnotations BindType(ExpressionSyntax syntax, BindingDiagnosticBag diagnostics, out AliasSymbol alias, ConsList<TypeSymbol> basesBeingResolved = null)
{
var symbol = BindTypeOrAlias(syntax, diagnostics, basesBeingResolved);
return UnwrapAlias(symbol, out alias, diagnostics, syntax, basesBeingResolved).TypeWithAnnotations;
}
// Binds the given expression syntax as Type or an Alias to Type
// and returns the resultant symbol.
// NOTE: This method doesn't unwrap aliases.
internal NamespaceOrTypeOrAliasSymbolWithAnnotations BindTypeOrAlias(ExpressionSyntax syntax, BindingDiagnosticBag diagnostics, ConsList<TypeSymbol> basesBeingResolved = null, bool suppressUseSiteDiagnostics = false)
{
Debug.Assert(diagnostics != null);
var symbol = BindNamespaceOrTypeOrAliasSymbol(syntax, diagnostics, basesBeingResolved, basesBeingResolved != null || suppressUseSiteDiagnostics);
// symbol must be a TypeSymbol or an Alias to a TypeSymbol
if (symbol.IsType ||
(symbol.IsAlias && UnwrapAliasNoDiagnostics(symbol.Symbol, basesBeingResolved) is TypeSymbol))
{
if (symbol.IsType)
{
// Obsolete alias targets are reported in UnwrapAlias, but if it was a type (not an
// alias to a type) we report the obsolete type here.
symbol.TypeWithAnnotations.ReportDiagnosticsIfObsolete(this, syntax, diagnostics);
}
return symbol;
}
var diagnosticInfo = diagnostics.Add(ErrorCode.ERR_BadSKknown, syntax.Location, syntax, symbol.Symbol.GetKindText(), MessageID.IDS_SK_TYPE.Localize());
return TypeWithAnnotations.Create(new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(symbol.Symbol), symbol.Symbol, LookupResultKind.NotATypeOrNamespace, diagnosticInfo));
}
/// <summary>
/// The immediately containing namespace or named type, or the global
/// namespace if containing symbol is neither a namespace or named type.
/// </summary>
private NamespaceOrTypeSymbol GetContainingNamespaceOrType(Symbol symbol)
{
return symbol.ContainingNamespaceOrType() ?? this.Compilation.Assembly.GlobalNamespace;
}
internal Symbol BindNamespaceAliasSymbol(IdentifierNameSyntax node, BindingDiagnosticBag diagnostics)
{
if (node.Identifier.Kind() == SyntaxKind.GlobalKeyword)
{
return this.Compilation.GlobalNamespaceAlias;
}
else
{
bool wasError;
var plainName = node.Identifier.ValueText;
var result = LookupResult.GetInstance();
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
this.LookupSymbolsWithFallback(result, plainName, 0, ref useSiteInfo, null, LookupOptions.NamespaceAliasesOnly);
diagnostics.Add(node, useSiteInfo);
Symbol bindingResult = ResultSymbol(result, plainName, 0, node, diagnostics, false, out wasError, qualifierOpt: null, options: LookupOptions.NamespaceAliasesOnly);
result.Free();
return bindingResult;
}
}
internal NamespaceOrTypeOrAliasSymbolWithAnnotations BindNamespaceOrTypeSymbol(ExpressionSyntax syntax, BindingDiagnosticBag diagnostics, ConsList<TypeSymbol> basesBeingResolved = null)
{
return BindNamespaceOrTypeSymbol(syntax, diagnostics, basesBeingResolved, basesBeingResolved != null);
}
/// <summary>
/// This method is used in deeply recursive parts of the compiler and requires a non-trivial amount of stack
/// space to execute. Preventing inlining here to keep recursive frames small.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
internal NamespaceOrTypeOrAliasSymbolWithAnnotations BindNamespaceOrTypeSymbol(ExpressionSyntax syntax, BindingDiagnosticBag diagnostics, ConsList<TypeSymbol> basesBeingResolved, bool suppressUseSiteDiagnostics)
{
var result = BindNamespaceOrTypeOrAliasSymbol(syntax, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics);
Debug.Assert(!result.IsDefault);
return UnwrapAlias(result, diagnostics, syntax, basesBeingResolved);
}
#nullable enable
/// <summary>
/// Bind the syntax into a namespace, type or alias symbol.
/// </summary>
/// <remarks>
/// This method is used in deeply recursive parts of the compiler. Specifically this and
/// <see cref="BindQualifiedName(ExpressionSyntax, SimpleNameSyntax, BindingDiagnosticBag, ConsList{TypeSymbol}, bool)"/>
/// are mutually recursive. The non-recursive parts of this method tend to reserve significantly large
/// stack frames due to their use of large struct like <see cref="TypeWithAnnotations"/>.
///
/// To keep the stack frame size on recursive paths small the non-recursive parts are factored into local
/// functions. This means we pay their stack penalty only when they are used. They are themselves big
/// enough they should be disqualified from inlining. In the future when attributes are allowed on
/// local functions we should explicitly mark them as <see cref="MethodImplOptions.NoInlining"/>
/// </remarks>
internal NamespaceOrTypeOrAliasSymbolWithAnnotations BindNamespaceOrTypeOrAliasSymbol(ExpressionSyntax syntax, BindingDiagnosticBag diagnostics, ConsList<TypeSymbol> basesBeingResolved, bool suppressUseSiteDiagnostics)
{
switch (syntax.Kind())
{
case SyntaxKind.NullableType:
return bindNullable();
case SyntaxKind.PredefinedType:
return bindPredefined();
case SyntaxKind.IdentifierName:
return BindNonGenericSimpleNamespaceOrTypeOrAliasSymbol((IdentifierNameSyntax)syntax, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics, qualifierOpt: null);
case SyntaxKind.GenericName:
return BindGenericSimpleNamespaceOrTypeOrAliasSymbol((GenericNameSyntax)syntax, diagnostics, basesBeingResolved, qualifierOpt: null);
case SyntaxKind.AliasQualifiedName:
return bindAlias();
case SyntaxKind.QualifiedName:
{
var node = (QualifiedNameSyntax)syntax;
return BindQualifiedName(node.Left, node.Right, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics);
}
case SyntaxKind.SimpleMemberAccessExpression:
{
var node = (MemberAccessExpressionSyntax)syntax;
return BindQualifiedName(node.Expression, node.Name, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics);
}
case SyntaxKind.ArrayType:
{
return BindArrayType((ArrayTypeSyntax)syntax, diagnostics, permitDimensions: false, basesBeingResolved, disallowRestrictedTypes: true);
}
case SyntaxKind.PointerType:
return bindPointer();
case SyntaxKind.FunctionPointerType:
var functionPointerTypeSyntax = (FunctionPointerTypeSyntax)syntax;
MessageID.IDS_FeatureFunctionPointers.CheckFeatureAvailability(diagnostics, functionPointerTypeSyntax.DelegateKeyword);
if (GetUnsafeDiagnosticInfo(sizeOfTypeOpt: null) is CSDiagnosticInfo info)
{
var @delegate = functionPointerTypeSyntax.DelegateKeyword;
var asterisk = functionPointerTypeSyntax.AsteriskToken;
RoslynDebug.Assert(@delegate.SyntaxTree is object);
diagnostics.Add(info, Location.Create(@delegate.SyntaxTree, TextSpan.FromBounds(@delegate.SpanStart, asterisk.Span.End)));
}
return TypeWithAnnotations.Create(
FunctionPointerTypeSymbol.CreateFromSource(
functionPointerTypeSyntax,
this,
diagnostics,
basesBeingResolved,
suppressUseSiteDiagnostics));
case SyntaxKind.OmittedTypeArgument:
{
return BindTypeArgument((TypeSyntax)syntax, diagnostics, basesBeingResolved);
}
case SyntaxKind.TupleType:
{
var tupleTypeSyntax = (TupleTypeSyntax)syntax;
return TypeWithAnnotations.Create(AreNullableAnnotationsEnabled(tupleTypeSyntax.CloseParenToken), BindTupleType(tupleTypeSyntax, diagnostics, basesBeingResolved));
}
case SyntaxKind.RefType:
{
// ref needs to be handled by the caller
var refTypeSyntax = (RefTypeSyntax)syntax;
if (!syntax.HasErrors)
{
var refToken = refTypeSyntax.RefKeyword;
// Specialized diagnostic if our parent is a using directive.
if (refTypeSyntax.Parent is UsingDirectiveSyntax)
{
diagnostics.Add(ErrorCode.ERR_BadRefInUsingAlias, refToken.GetLocation());
}
else
{
diagnostics.Add(ErrorCode.ERR_UnexpectedToken, refToken.GetLocation(), refToken.ToString());
}
}
return BindNamespaceOrTypeOrAliasSymbol(refTypeSyntax.Type, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics);
}
case SyntaxKind.ScopedType:
{
// scoped needs to be handled by the caller
var scopedTypeSyntax = (ScopedTypeSyntax)syntax;
var scopedToken = scopedTypeSyntax.ScopedKeyword;
if (!syntax.HasErrors)
{
diagnostics.Add(ErrorCode.ERR_UnexpectedToken, scopedToken.GetLocation(), scopedToken.ToString());
}
return BindNamespaceOrTypeOrAliasSymbol(scopedTypeSyntax.Type, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics);
}
default:
{
// This is invalid syntax for a type. This arises when a constant pattern that fails to bind
// is attempted to be bound as a type pattern.
return createErrorType();
}
}
void reportNullableReferenceTypesIfNeeded(SyntaxToken questionToken, TypeWithAnnotations typeArgument = default)
{
if (diagnostics.DiagnosticBag is DiagnosticBag diagnosticBag)
{
// Inside a method body or other executable code, we can question IsValueType without causing cycles.
if (typeArgument.HasType && !ShouldCheckConstraints)
{
LazyMissingNonNullTypesContextDiagnosticInfo.AddAll(this, questionToken, typeArgument, diagnosticBag);
}
else if (LazyMissingNonNullTypesContextDiagnosticInfo.IsNullableReference(typeArgument.Type))
{
LazyMissingNonNullTypesContextDiagnosticInfo.AddAll(this, questionToken, type: null, diagnosticBag);
}
}
}
NamespaceOrTypeOrAliasSymbolWithAnnotations bindNullable()
{
var nullableSyntax = (NullableTypeSyntax)syntax;
MessageID.IDS_FeatureNullable.CheckFeatureAvailability(diagnostics, nullableSyntax.QuestionToken);
TypeSyntax typeArgumentSyntax = nullableSyntax.ElementType;
TypeWithAnnotations typeArgument = BindType(typeArgumentSyntax, diagnostics, basesBeingResolved);
TypeWithAnnotations constructedType = typeArgument.SetIsAnnotated(Compilation);
reportNullableReferenceTypesIfNeeded(nullableSyntax.QuestionToken, typeArgument);
if (!ShouldCheckConstraints)
{
diagnostics.Add(new LazyUseSiteDiagnosticsInfoForNullableType(Compilation.LanguageVersion, constructedType), syntax.GetLocation());
}
else if (constructedType.IsNullableType())
{
ReportUseSite(constructedType.Type.OriginalDefinition, diagnostics, syntax);
var type = (NamedTypeSymbol)constructedType.Type;
var location = syntax.Location;
type.CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(this.Compilation, this.Conversions, includeNullability: true, location, diagnostics));
}
else if (GetNullableUnconstrainedTypeParameterDiagnosticIfNecessary(Compilation.LanguageVersion, constructedType) is { } diagnosticInfo)
{
diagnostics.Add(diagnosticInfo, syntax.Location);
}
return constructedType;
}
NamespaceOrTypeOrAliasSymbolWithAnnotations bindPredefined()
{
var predefinedType = (PredefinedTypeSyntax)syntax;
var type = BindPredefinedTypeSymbol(predefinedType, diagnostics);
return TypeWithAnnotations.Create(AreNullableAnnotationsEnabled(predefinedType.Keyword), type);
}
NamespaceOrTypeOrAliasSymbolWithAnnotations bindAlias()
{
var node = (AliasQualifiedNameSyntax)syntax;
MessageID.IDS_FeatureGlobalNamespace.CheckFeatureAvailability(diagnostics, node.Alias);
var bindingResult = BindNamespaceAliasSymbol(node.Alias, diagnostics);
NamespaceOrTypeSymbol left = bindingResult is AliasSymbol alias ? alias.Target : (NamespaceOrTypeSymbol)bindingResult;
if (left.Kind == SymbolKind.NamedType)
{
return TypeWithAnnotations.Create(new ExtendedErrorTypeSymbol(left, LookupResultKind.NotATypeOrNamespace, diagnostics.Add(ErrorCode.ERR_ColColWithTypeAlias, node.Alias.Location, node.Alias.Identifier.Text)));
}
return this.BindSimpleNamespaceOrTypeOrAliasSymbol(node.Name, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics, left);
}
NamespaceOrTypeOrAliasSymbolWithAnnotations bindPointer()
{
var node = (PointerTypeSyntax)syntax;
var elementType = BindType(node.ElementType, diagnostics, basesBeingResolved);
ReportUnsafeIfNotAllowed(node, diagnostics);
if (!Flags.HasFlag(BinderFlags.SuppressConstraintChecks))
{
CheckManagedAddr(Compilation, elementType.Type, node.Location, diagnostics);
}
return TypeWithAnnotations.Create(new PointerTypeSymbol(elementType));
}
NamespaceOrTypeOrAliasSymbolWithAnnotations createErrorType()
{
diagnostics.Add(ErrorCode.ERR_TypeExpected, syntax.GetLocation());
return TypeWithAnnotations.Create(CreateErrorType());
}
}
internal static CSDiagnosticInfo? GetNullableUnconstrainedTypeParameterDiagnosticIfNecessary(LanguageVersion languageVersion, in TypeWithAnnotations type)
{
if (type.Type.IsTypeParameterDisallowingAnnotationInCSharp8())
{
// Check IDS_FeatureDefaultTypeParameterConstraint feature since `T?` and `where ... : default`
// are treated as a single feature, even though the errors reported for the two cases are distinct.
var requiredVersion = MessageID.IDS_FeatureDefaultTypeParameterConstraint.RequiredVersion();
if (requiredVersion > languageVersion)
{
return new CSDiagnosticInfo(ErrorCode.ERR_NullableUnconstrainedTypeParameter, new CSharpRequiredLanguageVersion(requiredVersion));
}
}
return null;
}
#nullable disable
private TypeWithAnnotations BindArrayType(
ArrayTypeSyntax node,
BindingDiagnosticBag diagnostics,
bool permitDimensions,
ConsList<TypeSymbol> basesBeingResolved,
bool disallowRestrictedTypes)
{
TypeWithAnnotations type = BindType(node.ElementType, diagnostics, basesBeingResolved);
if (type.IsStatic)
{
// CS0719: '{0}': array elements cannot be of static type
Error(diagnostics, ErrorCode.ERR_ArrayOfStaticClass, node.ElementType, type.Type);
}
if (disallowRestrictedTypes)
{
// Restricted types cannot be on the heap, but they can be on the stack, so are allowed in a stackalloc
if (ShouldCheckConstraints)
{
if (type.IsRestrictedType())
{
// CS0611: Array elements cannot be of type '{0}'
Error(diagnostics, ErrorCode.ERR_ArrayElementCantBeRefAny, node.ElementType, type.Type);
}
}
else
{
diagnostics.Add(new LazyArrayElementCantBeRefAnyDiagnosticInfo(type), node.ElementType.GetLocation());
}
}
for (int i = node.RankSpecifiers.Count - 1; i >= 0; i--)
{
var rankSpecifier = node.RankSpecifiers[i];
var dimension = rankSpecifier.Sizes;
if (!permitDimensions && dimension.Count != 0 && dimension[0].Kind() != SyntaxKind.OmittedArraySizeExpression)
{
// https://github.com/dotnet/roslyn/issues/32464
// Should capture invalid dimensions for use in `SemanticModel` and `IOperation`.
Error(diagnostics, ErrorCode.ERR_ArraySizeInDeclaration, rankSpecifier);
}
var array = ArrayTypeSymbol.CreateCSharpArray(this.Compilation.Assembly, type, rankSpecifier.Rank);
type = TypeWithAnnotations.Create(AreNullableAnnotationsEnabled(rankSpecifier.CloseBracketToken), array);
}
return type;
}
private TypeSymbol BindTupleType(TupleTypeSyntax syntax, BindingDiagnosticBag diagnostics, ConsList<TypeSymbol> basesBeingResolved)
{
MessageID.IDS_FeatureTuples.CheckFeatureAvailability(diagnostics, syntax);
int numElements = syntax.Elements.Count;
var types = ArrayBuilder<TypeWithAnnotations>.GetInstance(numElements);
var locations = ArrayBuilder<Location>.GetInstance(numElements);
ArrayBuilder<string> elementNames = null;
// set of names already used
var uniqueFieldNames = PooledHashSet<string>.GetInstance();
bool hasExplicitNames = false;
for (int i = 0; i < numElements; i++)
{
var argumentSyntax = syntax.Elements[i];
var argumentType = BindType(argumentSyntax.Type, diagnostics, basesBeingResolved);
types.Add(argumentType);
string name = null;
SyntaxToken nameToken = argumentSyntax.Identifier;
if (nameToken.Kind() == SyntaxKind.IdentifierToken)
{
name = nameToken.ValueText;
// validate name if we have one
hasExplicitNames = true;
CheckTupleMemberName(name, i, nameToken, diagnostics, uniqueFieldNames);
locations.Add(nameToken.GetLocation());
}
else
{
locations.Add(argumentSyntax.Location);
}
CollectTupleFieldMemberName(name, i, numElements, ref elementNames);
}
uniqueFieldNames.Free();
if (hasExplicitNames)
{
// If the tuple type with names is bound we must have the TupleElementNamesAttribute to emit
// it is typically there though, if we have ValueTuple at all
ReportMissingTupleElementNamesAttributesIfNeeded(Compilation, syntax.GetLocation(), diagnostics);
}
var typesArray = types.ToImmutableAndFree();
var locationsArray = locations.ToImmutableAndFree();
if (typesArray.Length < 2)
{
throw ExceptionUtilities.UnexpectedValue(typesArray.Length);
}
bool includeNullability = Compilation.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes);
return NamedTypeSymbol.CreateTuple(syntax.Location,
typesArray,
locationsArray,
elementNames == null ?
default(ImmutableArray<string>) :
elementNames.ToImmutableAndFree(),
this.Compilation,
this.ShouldCheckConstraints,
includeNullability: this.ShouldCheckConstraints && includeNullability,
errorPositions: default(ImmutableArray<bool>),
syntax: syntax,
diagnostics: diagnostics);
}
internal static void ReportMissingTupleElementNamesAttributesIfNeeded(CSharpCompilation compilation, Location location, BindingDiagnosticBag diagnostics)
{
var bag = BindingDiagnosticBag.GetInstance(diagnostics);
if (!compilation.HasTupleNamesAttributes(bag, location))
{
var info = new CSDiagnosticInfo(ErrorCode.ERR_TupleElementNamesAttributeMissing,
AttributeDescription.TupleElementNamesAttribute.FullName);
Error(diagnostics, info, location);
}
else
{
diagnostics.AddRange(bag);
}
bag.Free();
}
private static void CollectTupleFieldMemberName(string name, int elementIndex, int tupleSize, ref ArrayBuilder<string> elementNames)
{
// add the name to the list
// names would typically all be there or none at all
// but in case we need to handle this in error cases
if (elementNames != null)
{
elementNames.Add(name);
}
else
{
if (name != null)
{
elementNames = ArrayBuilder<string>.GetInstance(tupleSize);
for (int j = 0; j < elementIndex; j++)
{
elementNames.Add(null);
}
elementNames.Add(name);
}
}
}
private static bool CheckTupleMemberName(string name, int index, SyntaxNodeOrToken syntax, BindingDiagnosticBag diagnostics, PooledHashSet<string> uniqueFieldNames)
{
int reserved = NamedTypeSymbol.IsTupleElementNameReserved(name);
if (reserved == 0)
{
Error(diagnostics, ErrorCode.ERR_TupleReservedElementNameAnyPosition, syntax, name);
return false;
}
else if (reserved > 0 && reserved != index + 1)
{
Error(diagnostics, ErrorCode.ERR_TupleReservedElementName, syntax, name, reserved);
return false;
}
else if (!uniqueFieldNames.Add(name))
{
Error(diagnostics, ErrorCode.ERR_TupleDuplicateElementName, syntax);
return false;
}
return true;
}
private NamedTypeSymbol BindPredefinedTypeSymbol(PredefinedTypeSyntax node, BindingDiagnosticBag diagnostics)
{
return GetSpecialType(node.Keyword.Kind().GetSpecialType(), diagnostics, node);
}
/// <summary>
/// Binds a simple name or the simple name portion of a qualified name.
/// </summary>
private NamespaceOrTypeOrAliasSymbolWithAnnotations BindSimpleNamespaceOrTypeOrAliasSymbol(
SimpleNameSyntax syntax,
BindingDiagnosticBag diagnostics,
ConsList<TypeSymbol> basesBeingResolved,
bool suppressUseSiteDiagnostics,
NamespaceOrTypeSymbol qualifierOpt = null)
{
// Note that the comment above is a small lie; there is no such thing as the "simple name portion" of
// a qualified alias member expression. A qualified alias member expression has the form
// "identifier :: identifier optional-type-arguments" -- the right hand side of which
// happens to match the syntactic form of a simple name. As a convenience, we analyze the
// right hand side of the "::" here because it is so similar to a simple name; the left hand
// side is in qualifierOpt.
switch (syntax.Kind())
{
default:
return TypeWithAnnotations.Create(new ExtendedErrorTypeSymbol(qualifierOpt ?? this.Compilation.Assembly.GlobalNamespace, string.Empty, arity: 0, errorInfo: null));
case SyntaxKind.IdentifierName:
return BindNonGenericSimpleNamespaceOrTypeOrAliasSymbol((IdentifierNameSyntax)syntax, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics, qualifierOpt);
case SyntaxKind.GenericName:
return BindGenericSimpleNamespaceOrTypeOrAliasSymbol((GenericNameSyntax)syntax, diagnostics, basesBeingResolved, qualifierOpt);
}
}
protected NamespaceOrTypeOrAliasSymbolWithAnnotations BindNonGenericSimpleNamespaceOrTypeOrAliasSymbol(
IdentifierNameSyntax node,
BindingDiagnosticBag diagnostics,
ConsList<TypeSymbol> basesBeingResolved,
bool suppressUseSiteDiagnostics,
NamespaceOrTypeSymbol qualifierOpt)
{
var identifierValueText = node.Identifier.ValueText;
// If we are here in an error-recovery scenario, say, "goo<int, >(123);" then
// we might have an 'empty' simple name. In that case do not report an
// 'unable to find ""' error; we've already reported an error in the parser so
// just bail out with an error symbol.
if (string.IsNullOrWhiteSpace(identifierValueText))
{
return TypeWithAnnotations.Create(new ExtendedErrorTypeSymbol(
Compilation.Assembly.GlobalNamespace, identifierValueText, 0,
new CSDiagnosticInfo(ErrorCode.ERR_SingleTypeNameNotFound, identifierValueText)));
}
var errorResult = CreateErrorIfLookupOnTypeParameter(node.Parent, qualifierOpt, identifierValueText, 0, diagnostics);
if (errorResult is not null)
{
return TypeWithAnnotations.Create(errorResult);
}
var result = LookupResult.GetInstance();
LookupOptions options = GetSimpleNameLookupOptions(node, node.Identifier.IsVerbatimIdentifier());
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
this.LookupSymbolsSimpleName(result, qualifierOpt, identifierValueText, 0, basesBeingResolved, options, diagnose: true, useSiteInfo: ref useSiteInfo);
diagnostics.Add(node, useSiteInfo);
Symbol bindingResult = null;
// If we were looking up "dynamic" or "nint" at the topmost level and didn't find anything good,
// use that particular type (assuming the /langversion is supported).
if (qualifierOpt is null &&
!isViableType(result))
{
if (node.Identifier.ValueText == "dynamic")
{
if (dynamicAllowed())
{
bindingResult = Compilation.DynamicType;
ReportUseSiteDiagnosticForDynamic(diagnostics, node);
}
}
else
{
// nint/nuint is allowed to bind to an existing namespace.
if (!isViableNamespace(result))
{
bindingResult = BindNativeIntegerSymbolIfAny(node, diagnostics);
}
}
}
if (bindingResult is null)
{
bool wasError;
bindingResult = ResultSymbol(result, identifierValueText, 0, node, diagnostics, suppressUseSiteDiagnostics, out wasError, qualifierOpt, options);
if (bindingResult.Kind == SymbolKind.Alias)
{
var aliasTarget = ((AliasSymbol)bindingResult).GetAliasTarget(basesBeingResolved);
if (aliasTarget is TypeSymbol type)
{
if (type.ContainsDynamic())
{
ReportUseSiteDiagnosticForDynamic(diagnostics, node);
}
if (type.ContainsPointer())
{
ReportUnsafeIfNotAllowed(node, diagnostics);
}
}
}
}
result.Free();
return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(AreNullableAnnotationsEnabled(node.Identifier), bindingResult);
bool dynamicAllowed()
{
if (Compilation.LanguageVersion < MessageID.IDS_FeatureDynamic.RequiredVersion())
return false;
if (node.Parent == null)
return true;
// dynamic not allowed as attribute type
if (node.Parent.Kind() == SyntaxKind.Attribute)
return false;
if (SyntaxFacts.IsInTypeOnlyContext(node))
return true;
// using X = dynamic; is legal.
if (node.Parent is UsingDirectiveSyntax { Alias: not null })
return true;
return false;
}
static bool isViableType(LookupResult result)
{
if (!result.IsMultiViable)
return false;
foreach (var s in result.Symbols)
{
switch (s.Kind)
{
case SymbolKind.Alias:
if (((AliasSymbol)s).Target.Kind == SymbolKind.NamedType) return true;
break;
case SymbolKind.NamedType:
case SymbolKind.TypeParameter:
return true;
}
}
return false;
}
static bool isViableNamespace(LookupResult result)
{
if (!result.IsMultiViable)
return false;
foreach (var s in result.Symbols)
{
if (s.Kind == SymbolKind.Namespace)
return true;
}
return false;
}
}
/// <summary>
/// If the node is "nint" or "nuint" and not alone inside nameof, return the corresponding native integer symbol.
/// Otherwise return null.
/// </summary>
private NamedTypeSymbol BindNativeIntegerSymbolIfAny(IdentifierNameSyntax node, BindingDiagnosticBag diagnostics)
{
var specialType =
node.IsNint ? SpecialType.System_IntPtr :
node.IsNuint ? SpecialType.System_UIntPtr : SpecialType.None;
if (specialType == SpecialType.None)
return null;
switch (node.Parent)
{
case AttributeSyntax parent when parent.Name == node: // [nint]
return null;
case UsingDirectiveSyntax usingDirective:
if (usingDirective.Alias != null && usingDirective.NamespaceOrType == node)
{
// legal to write `using A = nuint;` as long as using-alias-to-type is enabled (checked later).
break;
}
// `using nint;` not legal where 'nint' has the System.IntPtr meaning. It is legal if you were to
// have `namespace nint { }` somewhere. That is handled though in our caller.
return null;
case ArgumentSyntax parent when // nameof(nint)
(IsInsideNameof &&
parent.Parent?.Parent is InvocationExpressionSyntax invocation &&
(invocation.Expression as IdentifierNameSyntax)?.Identifier.ContextualKind() == SyntaxKind.NameOfKeyword):
// Don't bind nameof(nint) or nameof(nuint) so that ERR_NameNotInContext is reported.
return null;
}
CheckFeatureAvailability(node, MessageID.IDS_FeatureNativeInt, diagnostics);
return this.GetSpecialType(specialType, diagnostics, node).AsNativeInteger();
}
private void ReportUseSiteDiagnosticForDynamic(BindingDiagnosticBag diagnostics, IdentifierNameSyntax node)
{
// Dynamic type might be bound in a declaration context where we need to synthesize the DynamicAttribute.
// Here we report the use site error (ERR_DynamicAttributeMissing) for missing DynamicAttribute type or it's constructors.
//
// BREAKING CHANGE: Native compiler reports ERR_DynamicAttributeMissing at emit time when synthesizing DynamicAttribute.
// Currently, in Roslyn we don't support reporting diagnostics while synthesizing attributes, these diagnostics are reported at bind time.
// Hence, we report this diagnostic here. Note that DynamicAttribute has two constructors, and either of them may be used while
// synthesizing the DynamicAttribute (see DynamicAttributeEncoder.Encode method for details).
// However, unlike the native compiler which reports use site diagnostic only for the specific DynamicAttribute constructor which is going to be used,
// we report it for both the constructors and also for boolean type (used by the second constructor).
// This is a breaking change for the case where only one of the two constructor of DynamicAttribute is missing, but we never use it for any of the synthesized DynamicAttributes.
// However, this seems like a very unlikely scenario and an acceptable break.
if (node.IsTypeInContextWhichNeedsDynamicAttribute())
{
var bag = BindingDiagnosticBag.GetInstance(diagnostics);
if (!Compilation.HasDynamicEmitAttributes(bag, node.Location))
{
// CONSIDER: Native compiler reports error CS1980 for each syntax node which binds to dynamic type, we do the same by reporting a diagnostic here.
// However, this means we generate multiple duplicate diagnostics, when a single one would suffice.
// We may want to consider adding an "Unreported" flag to the DynamicTypeSymbol to suppress duplicate CS1980.
// CS1980: Cannot define a class or member that utilizes 'dynamic' because the compiler required type '{0}' cannot be found. Are you missing a reference?
var info = new CSDiagnosticInfo(ErrorCode.ERR_DynamicAttributeMissing, AttributeDescription.DynamicAttribute.FullName);
Symbol.ReportUseSiteDiagnostic(info, diagnostics, node.Location);
}
else
{
diagnostics.AddRange(bag);
}
bag.Free();
this.GetSpecialType(SpecialType.System_Boolean, diagnostics, node);
}
}
// Gets the name lookup options for simple generic or non-generic name.
private static LookupOptions GetSimpleNameLookupOptions(NameSyntax node, bool isVerbatimIdentifier)
{
if (SyntaxFacts.IsAttributeName(node))
{
// SPEC: By convention, attribute classes are named with a suffix of Attribute.
// SPEC: An attribute-name of the form type-name may either include or omit this suffix.
// SPEC: If an attribute class is found both with and without this suffix, an ambiguity
// SPEC: is present, and a compile-time error results. If the attribute-name is spelled
// SPEC: such that its right-most identifier is a verbatim identifier (§2.4.2), then only
// SPEC: an attribute without a suffix is matched, thus enabling such an ambiguity to be resolved.
return isVerbatimIdentifier ? LookupOptions.VerbatimNameAttributeTypeOnly : LookupOptions.AttributeTypeOnly;
}
else
{
return LookupOptions.NamespacesOrTypesOnly;
}
}
private static Symbol UnwrapAliasNoDiagnostics(Symbol symbol, ConsList<TypeSymbol> basesBeingResolved = null)
{
if (symbol.Kind == SymbolKind.Alias)
{
return ((AliasSymbol)symbol).GetAliasTarget(basesBeingResolved);
}
return symbol;
}
private NamespaceOrTypeOrAliasSymbolWithAnnotations UnwrapAlias(in NamespaceOrTypeOrAliasSymbolWithAnnotations symbol, BindingDiagnosticBag diagnostics, SyntaxNode syntax, ConsList<TypeSymbol> basesBeingResolved = null)
{
if (symbol.IsAlias)
{
AliasSymbol discarded;
return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(symbol.IsNullableEnabled, (NamespaceOrTypeSymbol)UnwrapAlias(symbol.Symbol, out discarded, diagnostics, syntax, basesBeingResolved));
}
return symbol;
}
private NamespaceOrTypeOrAliasSymbolWithAnnotations UnwrapAlias(in NamespaceOrTypeOrAliasSymbolWithAnnotations symbol, out AliasSymbol alias, BindingDiagnosticBag diagnostics, SyntaxNode syntax, ConsList<TypeSymbol> basesBeingResolved = null)
{
if (symbol.IsAlias)
{
return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(symbol.IsNullableEnabled, (NamespaceOrTypeSymbol)UnwrapAlias(symbol.Symbol, out alias, diagnostics, syntax, basesBeingResolved));
}
alias = null;
return symbol;
}
private Symbol UnwrapAlias(Symbol symbol, BindingDiagnosticBag diagnostics, SyntaxNode syntax, ConsList<TypeSymbol> basesBeingResolved = null)
{
AliasSymbol discarded;
return UnwrapAlias(symbol, out discarded, diagnostics, syntax, basesBeingResolved);
}
private Symbol UnwrapAlias(Symbol symbol, out AliasSymbol alias, BindingDiagnosticBag diagnostics, SyntaxNode syntax, ConsList<TypeSymbol> basesBeingResolved = null)
{
Debug.Assert(syntax != null);
Debug.Assert(diagnostics != null);
if (symbol.Kind == SymbolKind.Alias)
{
alias = (AliasSymbol)symbol;
var result = alias.GetAliasTarget(basesBeingResolved);
var type = result as TypeSymbol;
if ((object)type != null)
{
// pass args in a value tuple to avoid allocating a closure
var args = (this, diagnostics, syntax);
type.VisitType((typePart, argTuple, isNested) =>
{
argTuple.Item1.ReportDiagnosticsIfObsolete(argTuple.diagnostics, typePart, argTuple.syntax, hasBaseReceiver: false);
return false;
}, args);
}
return result;
}
alias = null;
return symbol;
}
private TypeWithAnnotations BindGenericSimpleNamespaceOrTypeOrAliasSymbol(
GenericNameSyntax node,
BindingDiagnosticBag diagnostics,
ConsList<TypeSymbol> basesBeingResolved,
NamespaceOrTypeSymbol qualifierOpt)
{
// We are looking for a namespace, alias or type name and the user has given
// us an identifier followed by a type argument list. Therefore they
// must expect the result to be a generic type, and not a namespace or alias.
// The result of this method will therefore always be a type symbol of the
// correct arity, though it might have to be an error type.
// We might be asked to bind a generic simple name of the form "T<,,,>",
// which is only legal in the context of "typeof(T<,,,>)". If we are given
// no type arguments and we are not in such a context, we'll give an error.
// If we do have type arguments, then the result of this method will always
// be a generic type symbol constructed with the given type arguments.
// There are a number of possible error conditions. First, errors involving lookup:
//
// * Lookup could fail to find anything at all.
// * Lookup could find a type of the wrong arity
// * Lookup could find something but it is not a type.
//
// Second, we could be asked to resolve an unbound type T<,,,> when
// not in a context where it is legal to do so. Note that this is
// intended an improvement over the analysis performed by the
// native compiler; in the native compiler we catch bad uses of unbound
// types at parse time, not at semantic analysis time. That means that
// we end up giving confusing "unexpected comma" or "expected type"
// errors when it would be more informative to the user to simply
// tell them that an unbound type is not legal in this position.
//
// This also means that we can get semantic analysis of the open
// type in the IDE even in what would have been a syntax error case
// in the native compiler.
//
// We need a heuristic to deal with the situation where both kinds of errors
// are potentially in play: what if someone says "typeof(Bogus<>.Blah<int>)"?
// There are two errors there: first, that Bogus is not found, not a type,
// or not of the appropriate arity, and second, that it is illegal to make
// a partially unbound type.
//
// The heuristic we will use is that the former kind of error takes priority
// over the latter; if the meaning of "Bogus<>" cannot be successfully
// determined then there is no point telling the user that in addition,
// it is syntactically wrong. Moreover, at this point we do not know what they
// mean by the remainder ".Blah<int>" of the expression and so it seems wrong to
// deduce more errors from it.
var plainName = node.Identifier.ValueText;
SeparatedSyntaxList<TypeSyntax> typeArguments = node.TypeArgumentList.Arguments;
bool isUnboundTypeExpr = node.IsUnboundGenericName;
LookupOptions options = GetSimpleNameLookupOptions(node, isVerbatimIdentifier: false);
NamedTypeSymbol unconstructedType = LookupGenericTypeName(
diagnostics, basesBeingResolved, qualifierOpt, node, plainName, node.Arity, options);
NamedTypeSymbol resultType;
if (isUnboundTypeExpr)
{
if (!IsUnboundTypeAllowed(node))
{
// If we already have an error type then skip reporting that the unbound type is illegal.
if (!unconstructedType.IsErrorType())
{
// error CS7003: Unexpected use of an unbound generic name
diagnostics.Add(ErrorCode.ERR_UnexpectedUnboundGenericName, node.Location);
}
resultType = unconstructedType.Construct(
UnboundArgumentErrorTypeSymbol.CreateTypeArguments(
unconstructedType.TypeParameters,
node.Arity,
errorInfo: null),
unbound: false);
}
else
{
resultType = unconstructedType.AsUnboundGenericType();
}
}
else if ((Flags & BinderFlags.SuppressTypeArgumentBinding) != 0)
{
resultType = unconstructedType.Construct(PlaceholderTypeArgumentSymbol.CreateTypeArguments(unconstructedType.TypeParameters));
}
else
{
var boundTypeArguments = BindTypeArguments(typeArguments, diagnostics, basesBeingResolved);
// It's not an unbound type expression, so we must have type arguments, and we have a
// generic type of the correct arity in hand (possibly an error type). Bind the type
// arguments and construct the final result.
resultType = ConstructNamedType(
unconstructedType,
node,
typeArguments,
boundTypeArguments,
basesBeingResolved,
diagnostics);
}
return TypeWithAnnotations.Create(AreNullableAnnotationsEnabled(node.TypeArgumentList.GreaterThanToken), resultType);
}
private NamedTypeSymbol LookupGenericTypeName(
BindingDiagnosticBag diagnostics,
ConsList<TypeSymbol> basesBeingResolved,
NamespaceOrTypeSymbol qualifierOpt,
GenericNameSyntax node,
string plainName,
int arity,
LookupOptions options)
{
var errorResult = CreateErrorIfLookupOnTypeParameter(node.Parent, qualifierOpt, plainName, arity, diagnostics);
if ((object)errorResult != null)
{
return errorResult;
}
var lookupResult = LookupResult.GetInstance();
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
this.LookupSymbolsSimpleName(lookupResult, qualifierOpt, plainName, arity, basesBeingResolved, options, diagnose: true, useSiteInfo: ref useSiteInfo);
diagnostics.Add(node, useSiteInfo);
bool wasError;
Symbol lookupResultSymbol = ResultSymbol(lookupResult, plainName, arity, node, diagnostics, (basesBeingResolved != null), out wasError, qualifierOpt, options);
// As we said in the method above, there are three cases here:
//
// * Lookup could fail to find anything at all.
// * Lookup could find a type of the wrong arity
// * Lookup could find something but it is not a type.
//
// In the first two cases we will be given back an error type symbol of the appropriate arity.
// In the third case we will be given back the symbol -- say, a local variable symbol.
//
// In all three cases the appropriate error has already been reported. (That the
// type was not found, that the generic type found does not have that arity, that
// the non-generic type found cannot be used with a type argument list, or that
// the symbol found is not something that takes type arguments. )
// The first thing to do is to make sure that we have some sort of generic type in hand.
// (Note that an error type symbol is always a generic type.)
NamedTypeSymbol type = lookupResultSymbol as NamedTypeSymbol;
if ((object)type == null)
{
// We did a lookup with a generic arity, filtered to types and namespaces. If
// we got back something other than a type, there had better be an error info
// for us.
Debug.Assert(lookupResult.Error != null);
type = new ExtendedErrorTypeSymbol(
GetContainingNamespaceOrType(lookupResultSymbol),
ImmutableArray.Create<Symbol>(lookupResultSymbol),
lookupResult.Kind,
lookupResult.Error,
arity);
}
lookupResult.Free();
return type;
}
private ExtendedErrorTypeSymbol CreateErrorIfLookupOnTypeParameter(
CSharpSyntaxNode node,
NamespaceOrTypeSymbol qualifierOpt,
string name,
int arity,
BindingDiagnosticBag diagnostics)
{
if (((object)qualifierOpt != null) && (qualifierOpt.Kind == SymbolKind.TypeParameter))
{
var diagnosticInfo = new CSDiagnosticInfo(ErrorCode.ERR_LookupInTypeVariable, qualifierOpt);
diagnostics.Add(diagnosticInfo, node.Location);
return new ExtendedErrorTypeSymbol(this.Compilation, name, arity, diagnosticInfo, unreported: false);
}
return null;
}
private ImmutableArray<TypeWithAnnotations> BindTypeArguments(SeparatedSyntaxList<TypeSyntax> typeArguments, BindingDiagnosticBag diagnostics, ConsList<TypeSymbol> basesBeingResolved = null)
{
Debug.Assert(typeArguments.Count > 0);
var args = ArrayBuilder<TypeWithAnnotations>.GetInstance(typeArguments.Count);
foreach (var argSyntax in typeArguments)
{
args.Add(BindTypeArgument(argSyntax, diagnostics, basesBeingResolved));
}
return args.ToImmutableAndFree();
}
private TypeWithAnnotations BindTypeArgument(TypeSyntax typeArgument, BindingDiagnosticBag diagnostics, ConsList<TypeSymbol> basesBeingResolved = null)
{
// BackCompat. The compiler would previously suppress reporting errors for pointers in generic types. This
// was intended so you would get a specific error in CheckBasicConstraints.CheckBasicConstraints for a type
// like (like `List<int*>`). i.e. you would get the error about an unsafe type not being a legal type argument,
// but not the error about not being in an unsafe context. This had the unfortunate consequence though of
// preventing the latter check for something like `List<int*[]>`. Here, this is a legal generic type, but we
// still want to report the error that you need to be in an unsafe context. So, to maintain compat, we only
// do the suppression if you're on C# 11 and prior. In later versions we do the correct check.
var binder = !Compilation.IsFeatureEnabled(MessageID.IDS_FeatureUsingTypeAlias)
? this.WithAdditionalFlags(BinderFlags.SuppressUnsafeDiagnostics)
: this;
var arg = typeArgument.Kind() == SyntaxKind.OmittedTypeArgument
? TypeWithAnnotations.Create(UnboundArgumentErrorTypeSymbol.Instance)
: binder.BindType(typeArgument, diagnostics, basesBeingResolved);
return arg;
}
/// <remarks>
/// Keep check and error in sync with ConstructBoundMethodGroupAndReportOmittedTypeArguments.
/// </remarks>
private NamedTypeSymbol ConstructNamedTypeUnlessTypeArgumentOmitted(SyntaxNode typeSyntax, NamedTypeSymbol type, SeparatedSyntaxList<TypeSyntax> typeArgumentsSyntax, ImmutableArray<TypeWithAnnotations> typeArguments, BindingDiagnosticBag diagnostics)
{
if (typeArgumentsSyntax.Any(SyntaxKind.OmittedTypeArgument))
{
if (this.IsInsideNameof)
{
// Inside a nameof an open-generic type is acceptable. Fall through and bind the remainder accordingly.
CheckFeatureAvailability(typeSyntax, MessageID.IDS_FeatureUnboundGenericTypesInNameof, diagnostics);
// From the spec:
//
// Member lookup on an unbound type expression will be performed the same way as for a `this`
// expression within that type declaration.
//
// So we want to just return the originating type symbol as is (e.g. List<T> in nameof(List<>)).
// This is distinctly different than how typeof(List<>) works, where it returns an unbound generic
// type.
}
else
{
// Note: lookup won't have reported this, since the arity was correct.
// CONSIDER: the text of this error message makes sense, but we might want to add a separate code.
// If the syntax looks like an unbound generic type, then they probably wanted the definition.
// Give an error indicating that the syntax is incorrect and then use the definition.
// CONSIDER: we could construct an unbound generic type symbol, but that would probably be confusing
// outside a typeof.
Error(diagnostics, ErrorCode.ERR_BadArity, typeSyntax, type, MessageID.IDS_SK_TYPE.Localize(), typeArgumentsSyntax.Count);
}
return type;
}
else
{
// we pass an empty basesBeingResolved here because this invocation is not on any possible path of
// infinite recursion in binding base clauses.
return ConstructNamedType(type, typeSyntax, typeArgumentsSyntax, typeArguments, basesBeingResolved: null, diagnostics: diagnostics);
}
}
/// <remarks>
/// Keep check and error in sync with ConstructNamedTypeUnlessTypeArgumentOmitted.
/// </remarks>
private BoundMethodOrPropertyGroup ConstructBoundMemberGroupAndReportOmittedTypeArguments(
SyntaxNode syntax,
SeparatedSyntaxList<TypeSyntax> typeArgumentsSyntax,
ImmutableArray<TypeWithAnnotations> typeArguments,
BoundExpression receiver,
string plainName,
ArrayBuilder<Symbol> members,
LookupResult lookupResult,
BoundMethodGroupFlags methodGroupFlags,
bool hasErrors,
BindingDiagnosticBag diagnostics)
{
if (!hasErrors && lookupResult.IsMultiViable && typeArgumentsSyntax.Any(SyntaxKind.OmittedTypeArgument))
{
// Note: lookup won't have reported this, since the arity was correct.
// CONSIDER: the text of this error message makes sense, but we might want to add a separate code.
Error(diagnostics, ErrorCode.ERR_BadArity, syntax, plainName, MessageID.IDS_MethodGroup.Localize(), typeArgumentsSyntax.Count);
hasErrors = true;
}
Debug.Assert(members.Count > 0);
BoundExpression colorColorValueReceiver = GetValueExpressionIfTypeOrValueReceiver(receiver);
Debug.Assert(colorColorValueReceiver is null || (methodGroupFlags & BoundMethodGroupFlags.SearchExtensionMethods) != 0);
if (IsPossiblyCapturingPrimaryConstructorParameterReference(colorColorValueReceiver, out ParameterSymbol parameter))
{
bool haveInstanceCandidates, haveStaticCandidates;
LookupResult tempLookupResult = null;
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
CheckWhatCandidatesWeHave(members, parameter.Type, plainName,
typeArguments.IsDefault ? 0 : typeArguments.Length,
ref tempLookupResult, ref useSiteInfo,
out haveInstanceCandidates, out haveStaticCandidates);
tempLookupResult?.Free();
diagnostics.Add(colorColorValueReceiver.Syntax, useSiteInfo);
if (haveInstanceCandidates)
{
BindingDiagnosticBag discarded = null;
if (haveStaticCandidates)
{
Error(diagnostics, ErrorCode.ERR_AmbiguousPrimaryConstructorParameterAsColorColorReceiver, colorColorValueReceiver.Syntax, parameter.Name, parameter.Type, parameter);
discarded = BindingDiagnosticBag.GetInstance(diagnostics);
}
receiver = ReplaceTypeOrValueReceiver(receiver, useType: false, discarded ?? diagnostics);
discarded?.Free();
if (haveStaticCandidates)
{
// Wrap into bad expression with HasErrors in an attempt to suppress cascading diagnostics
receiver = new BoundBadExpression(receiver.Syntax, LookupResultKind.Ambiguous, ImmutableArray<Symbol>.Empty, ImmutableArray.Create(receiver), receiver.Type, hasErrors: true).MakeCompilerGenerated();
}
}
else
{
Debug.Assert(haveStaticCandidates);
receiver = ReplaceTypeOrValueReceiver(receiver, useType: true, diagnostics);
}
}
switch (members[0].Kind)
{
case SymbolKind.Method:
return new BoundMethodGroup(
syntax,
typeArguments,
receiver,
plainName,
members.SelectAsArray(s_toMethodSymbolFunc),
lookupResult,
methodGroupFlags,
this,
hasErrors);
case SymbolKind.Property:
return new BoundPropertyGroup(
syntax,
members.SelectAsArray(s_toPropertySymbolFunc),
receiver,
lookupResult.Kind,
hasErrors);
default:
throw ExceptionUtilities.UnexpectedValue(members[0].Kind);
}
}
private bool IsPossiblyCapturingPrimaryConstructorParameterReference(BoundExpression colorColorValueReceiver, out ParameterSymbol parameterSymbol)
{
if (colorColorValueReceiver is BoundParameter { ParameterSymbol: { ContainingSymbol: SynthesizedPrimaryConstructor primaryConstructor } parameter } &&
IsInDeclaringTypeInstanceMember(primaryConstructor) &&
!InFieldInitializer &&
this.ContainingMember() != (object)primaryConstructor &&
!IsInsideNameof)
{
parameterSymbol = parameter;
return true;
}
parameterSymbol = null;
return false;
}
private void CheckWhatCandidatesWeHave(
ArrayBuilder<Symbol> members, TypeSymbol receiverType,
string plainName, int arity,
ref LookupResult lookupResult,
ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
out bool haveInstanceCandidates, out bool haveStaticCandidates)
{
Debug.Assert(lookupResult?.IsClear != false);
haveInstanceCandidates = members.Any(m => !m.IsStatic);
haveStaticCandidates = members.Any(m => m.IsStatic);
Debug.Assert(haveStaticCandidates || haveInstanceCandidates);
if (!haveInstanceCandidates && members[0].Kind == SymbolKind.Method)
{
// See if there could be extension methods in scope
foreach (var scope in new ExtensionMethodScopes(this))
{
lookupResult ??= LookupResult.GetInstance();
LookupExtensionMethods(lookupResult, scope, plainName, arity, ref useSiteInfo);
if (lookupResult.IsMultiViable)
{
foreach (var symbol in lookupResult.Symbols)
{
var method = (MethodSymbol)symbol;
if (method.ReduceExtensionMethod(receiverType, Compilation) is not null)
{
haveInstanceCandidates = true;
break;
}
}
}
lookupResult.Clear();
if (haveInstanceCandidates)
{
break;
}
}
}
}
private static readonly Func<Symbol, MethodSymbol> s_toMethodSymbolFunc = s => (MethodSymbol)s;
private static readonly Func<Symbol, PropertySymbol> s_toPropertySymbolFunc = s => (PropertySymbol)s;
private NamedTypeSymbol ConstructNamedType(
NamedTypeSymbol type,
SyntaxNode typeSyntax,
SeparatedSyntaxList<TypeSyntax> typeArgumentsSyntax,
ImmutableArray<TypeWithAnnotations> typeArguments,
ConsList<TypeSymbol> basesBeingResolved,
BindingDiagnosticBag diagnostics)
{
Debug.Assert(!typeArguments.IsEmpty);
type = type.Construct(typeArguments);
if (ShouldCheckConstraints && ConstraintsHelper.RequiresChecking(type))
{
bool includeNullability = Compilation.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes);
type.CheckConstraintsForNamedType(new ConstraintsHelper.CheckConstraintsArgs(this.Compilation, this.Conversions, includeNullability, typeSyntax.Location, diagnostics),
typeSyntax, typeArgumentsSyntax, basesBeingResolved);
}
return type;
}
/// <summary>
/// Check generic type constraints unless the type is used as part of a type or method
/// declaration. In those cases, constraints checking is handled by the caller.
/// </summary>
private bool ShouldCheckConstraints
{
get
{
return !this.Flags.Includes(BinderFlags.SuppressConstraintChecks);
}
}
private NamespaceOrTypeOrAliasSymbolWithAnnotations BindQualifiedName(
ExpressionSyntax leftName,
SimpleNameSyntax rightName,
BindingDiagnosticBag diagnostics,
ConsList<TypeSymbol> basesBeingResolved,
bool suppressUseSiteDiagnostics)
{
var left = BindNamespaceOrTypeSymbol(leftName, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics: false).NamespaceOrTypeSymbol;
ReportDiagnosticsIfObsolete(diagnostics, left, leftName, hasBaseReceiver: false);
bool isLeftUnboundGenericType = left.Kind == SymbolKind.NamedType &&
((NamedTypeSymbol)left).IsUnboundGenericType;
if (isLeftUnboundGenericType)
{
// If left name bound to an unbound generic type,
// we want to perform right name lookup within
// left's original named type definition.
left = ((NamedTypeSymbol)left).OriginalDefinition;
}
// since the name is qualified, it cannot result in a using alias symbol, only a type or namespace
var right = this.BindSimpleNamespaceOrTypeOrAliasSymbol(rightName, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics, left);
// If left name bound to an unbound generic type
// and right name bound to a generic type, we must
// convert right to an unbound generic type.
if (isLeftUnboundGenericType)
{
return convertToUnboundGenericType();
}
return right;
// This part is moved into a local function to reduce the method's stack frame size
NamespaceOrTypeOrAliasSymbolWithAnnotations convertToUnboundGenericType()
{
var namedTypeRight = right.Symbol as NamedTypeSymbol;
if ((object)namedTypeRight != null && namedTypeRight.IsGenericType)
{
TypeWithAnnotations type = right.TypeWithAnnotations;
return type.WithTypeAndModifiers(namedTypeRight.AsUnboundGenericType(), type.CustomModifiers);
}
return right;
}
}
internal NamedTypeSymbol GetSpecialType(SpecialType typeId, BindingDiagnosticBag diagnostics, SyntaxNode node)
{
return GetSpecialType(this.Compilation, typeId, node, diagnostics);
}
internal static NamedTypeSymbol GetSpecialType(CSharpCompilation compilation, SpecialType typeId, SyntaxNode node, BindingDiagnosticBag diagnostics)
{
NamedTypeSymbol typeSymbol = compilation.GetSpecialType(typeId);
Debug.Assert((object)typeSymbol != null, "Expect an error type if special type isn't found");
ReportUseSite(typeSymbol, diagnostics, node);
return typeSymbol;
}
internal static NamedTypeSymbol GetSpecialType(CSharpCompilation compilation, SpecialType typeId, Location location, BindingDiagnosticBag diagnostics)
{
NamedTypeSymbol typeSymbol = compilation.GetSpecialType(typeId);
Debug.Assert((object)typeSymbol != null, "Expect an error type if special type isn't found");
ReportUseSite(typeSymbol, diagnostics, location);
return typeSymbol;
}
/// <summary>
/// This is a layer on top of the Compilation version that generates a diagnostic if the special
/// member isn't found.
/// </summary>
internal Symbol GetSpecialTypeMember(SpecialMember member, BindingDiagnosticBag diagnostics, SyntaxNode syntax)
{
return GetSpecialTypeMember(this.Compilation, member, diagnostics, syntax);
}
internal static Symbol GetSpecialTypeMember(CSharpCompilation compilation, SpecialMember member, BindingDiagnosticBag diagnostics, SyntaxNode syntax)
{
Symbol memberSymbol;
return TryGetSpecialTypeMember(compilation, member, syntax, diagnostics, out memberSymbol)
? memberSymbol
: null;
}
internal static bool TryGetSpecialTypeMember<TSymbol>(CSharpCompilation compilation, SpecialMember specialMember, SyntaxNode syntax, BindingDiagnosticBag diagnostics, out TSymbol symbol, bool isOptional = false)
where TSymbol : Symbol
{
symbol = (TSymbol)compilation.GetSpecialTypeMember(specialMember);
if (symbol is null)
{
if (!isOptional)
{
MemberDescriptor descriptor = SpecialMembers.GetDescriptor(specialMember);
diagnostics.Add(ErrorCode.ERR_MissingPredefinedMember, syntax.Location, descriptor.DeclaringTypeMetadataName, descriptor.Name);
}
return false;
}
var useSiteInfo = GetUseSiteInfoForWellKnownMemberOrContainingType(symbol);
if (useSiteInfo.DiagnosticInfo != null)
{
// Report errors only for non-optional members
if (isOptional)
{
var severity = useSiteInfo.DiagnosticInfo.Severity;
// If the member is optional and bad for whatever reason ignore it
if (severity == DiagnosticSeverity.Error)
{
symbol = null;
return false;
}
}
else
{
diagnostics.ReportUseSiteDiagnostic(useSiteInfo.DiagnosticInfo, new SourceLocation(syntax));
}
}
// No need to track assemblies used by special members or types. They are coming from core library, which
// doesn't have any dependencies.
return true;
}
private static UseSiteInfo<AssemblySymbol> GetUseSiteInfoForWellKnownMemberOrContainingType(Symbol symbol)
{
Debug.Assert(symbol.IsDefinition);
UseSiteInfo<AssemblySymbol> info = symbol.GetUseSiteInfo();
symbol.MergeUseSiteInfo(ref info, symbol.ContainingType.GetUseSiteInfo());
return info;
}
/// <summary>
/// Reports use-site diagnostics and dependencies for the specified symbol.
/// </summary>
/// <returns>
/// True if there was an error among the reported diagnostics
/// </returns>
internal static bool ReportUseSite(Symbol symbol, BindingDiagnosticBag diagnostics, SyntaxNode node)
{
return diagnostics.ReportUseSite(symbol, node);
}
internal static bool ReportUseSite(Symbol symbol, BindingDiagnosticBag diagnostics, SyntaxToken token)
{
return diagnostics.ReportUseSite(symbol, token);
}
/// <summary>
/// Reports use-site diagnostics and dependencies for the specified symbol.
/// </summary>
/// <returns>
/// True if there was an error among the reported diagnostics
/// </returns>
internal static bool ReportUseSite(Symbol symbol, BindingDiagnosticBag diagnostics, Location location)
{
return diagnostics.ReportUseSite(symbol, location);
}
/// <summary>
/// This is a layer on top of the Compilation version that generates a diagnostic if the well-known
/// type isn't found.
/// </summary>
internal NamedTypeSymbol GetWellKnownType(WellKnownType type, BindingDiagnosticBag diagnostics, SyntaxNode node)
{
return GetWellKnownType(type, diagnostics, node.Location);
}
/// <summary>
/// This is a layer on top of the Compilation version that generates a diagnostic if the well-known
/// type isn't found.
/// </summary>
internal NamedTypeSymbol GetWellKnownType(WellKnownType type, BindingDiagnosticBag diagnostics, Location location)
{
return GetWellKnownType(this.Compilation, type, diagnostics, location);
}
/// <summary>
/// This is a layer on top of the Compilation version that generates a diagnostic if the well-known
/// type isn't found.
/// </summary>
internal static NamedTypeSymbol GetWellKnownType(CSharpCompilation compilation, WellKnownType type, BindingDiagnosticBag diagnostics, SyntaxNode node)
{
return GetWellKnownType(compilation, type, diagnostics, node.Location);
}
internal static NamedTypeSymbol GetWellKnownType(CSharpCompilation compilation, WellKnownType type, BindingDiagnosticBag diagnostics, Location location)
{
NamedTypeSymbol typeSymbol = compilation.GetWellKnownType(type);
Debug.Assert((object)typeSymbol != null, "Expect an error type if well-known type isn't found");
ReportUseSite(typeSymbol, diagnostics, location);
return typeSymbol;
}
/// <summary>
/// This is a layer on top of the Compilation version that generates a diagnostic if the well-known
/// type isn't found.
/// </summary>
internal NamedTypeSymbol GetWellKnownType(WellKnownType type, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
NamedTypeSymbol typeSymbol = this.Compilation.GetWellKnownType(type);
Debug.Assert((object)typeSymbol != null, "Expect an error type if well-known type isn't found");
typeSymbol.AddUseSiteInfo(ref useSiteInfo);
return typeSymbol;
}
internal Symbol GetWellKnownTypeMember(WellKnownMember member, BindingDiagnosticBag diagnostics, Location location = null, SyntaxNode syntax = null, bool isOptional = false)
{
return GetWellKnownTypeMember(Compilation, member, diagnostics, location, syntax, isOptional);
}
/// <summary>
/// Retrieves a well-known type member and reports diagnostics.
/// </summary>
/// <returns>Null if the symbol is missing.</returns>
internal static Symbol GetWellKnownTypeMember(CSharpCompilation compilation, WellKnownMember member, BindingDiagnosticBag diagnostics, Location location = null, SyntaxNode syntax = null, bool isOptional = false)
{
Debug.Assert((syntax != null) ^ (location != null));
UseSiteInfo<AssemblySymbol> useSiteInfo;
Symbol memberSymbol = GetWellKnownTypeMember(compilation, member, out useSiteInfo, isOptional);
if (syntax != null)
diagnostics.Add(useSiteInfo, syntax);
else
diagnostics.Add(useSiteInfo, location);
return memberSymbol;
}
internal static Symbol GetWellKnownTypeMember(CSharpCompilation compilation, WellKnownMember member, out UseSiteInfo<AssemblySymbol> useSiteInfo, bool isOptional = false)
{
Symbol memberSymbol = compilation.GetWellKnownTypeMember(member);
if ((object)memberSymbol != null)
{
useSiteInfo = GetUseSiteInfoForWellKnownMemberOrContainingType(memberSymbol);
if (useSiteInfo.DiagnosticInfo != null)
{
// Dev11 reports use-site diagnostics even for optional symbols that are found.
// We decided to silently ignore bad optional symbols.
// Report errors only for non-optional members:
if (isOptional)
{
var severity = useSiteInfo.DiagnosticInfo.Severity;
// if the member is optional and bad for whatever reason ignore it:
if (severity == DiagnosticSeverity.Error)
{
useSiteInfo = default;
return null;
}
// ignore warnings:
useSiteInfo = new UseSiteInfo<AssemblySymbol>(diagnosticInfo: null, useSiteInfo.PrimaryDependency, useSiteInfo.SecondaryDependencies);
}
}
}
else if (!isOptional)
{
// member is missing
MemberDescriptor memberDescriptor = WellKnownMembers.GetDescriptor(member);
useSiteInfo = new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_MissingPredefinedMember, memberDescriptor.DeclaringTypeMetadataName, memberDescriptor.Name));
}
else
{
useSiteInfo = default;
}
return memberSymbol;
}
private class ConsistentSymbolOrder : IComparer<Symbol>
{
public static readonly ConsistentSymbolOrder Instance = new ConsistentSymbolOrder();
public int Compare(Symbol fst, Symbol snd)
{
if (snd == fst) return 0;
if ((object)fst == null) return -1;
if ((object)snd == null) return 1;
if (snd.Name != fst.Name) return string.CompareOrdinal(fst.Name, snd.Name);
if (snd.Kind != fst.Kind) return (int)fst.Kind - (int)snd.Kind;
int aLocationsCount = !snd.Locations.IsDefault ? snd.Locations.Length : 0;
int bLocationsCount = fst.Locations.Length;
if (aLocationsCount != bLocationsCount) return aLocationsCount - bLocationsCount;
if (aLocationsCount == 0 && bLocationsCount == 0) return Compare(fst.ContainingSymbol, snd.ContainingSymbol);
Location la = snd.GetFirstLocation();
Location lb = fst.GetFirstLocation();
if (la.IsInSource != lb.IsInSource) return la.IsInSource ? 1 : -1;
int containerResult = Compare(fst.ContainingSymbol, snd.ContainingSymbol);
if (!la.IsInSource) return containerResult;
if (containerResult == 0 && la.SourceTree == lb.SourceTree) return lb.SourceSpan.Start - la.SourceSpan.Start;
return containerResult;
}
}
// return the type or namespace symbol in a lookup result, or report an error.
internal Symbol ResultSymbol(
LookupResult result,
string simpleName,
int arity,
SyntaxNode where,
BindingDiagnosticBag diagnostics,
bool suppressUseSiteDiagnostics,
out bool wasError,
NamespaceOrTypeSymbol qualifierOpt,
LookupOptions options = default(LookupOptions))
{
Symbol symbol = resultSymbol(result, simpleName, arity, where, diagnostics, suppressUseSiteDiagnostics, out wasError, qualifierOpt, options);
if (symbol.Kind == SymbolKind.NamedType)
{
CheckReceiverAndRuntimeSupportForSymbolAccess(where, receiverOpt: null, symbol, diagnostics);
if (suppressUseSiteDiagnostics && diagnostics.DependenciesBag is object)
{
AssemblySymbol container = symbol.ContainingAssembly;
if (container is object && container != Compilation.Assembly && container != Compilation.Assembly.CorLibrary)
{
diagnostics.AddDependency(container);
}
}
}
return symbol;
Symbol resultSymbol(
LookupResult result,
string simpleName,
int arity,
SyntaxNode where,
BindingDiagnosticBag diagnostics,
bool suppressUseSiteDiagnostics,
out bool wasError,
NamespaceOrTypeSymbol qualifierOpt,
LookupOptions options)
{
Debug.Assert(where != null);
Debug.Assert(diagnostics != null);
var symbols = result.Symbols;
wasError = false;
if (result.IsMultiViable)
{
if (symbols.Count > 1)
{
// gracefully handle symbols.Count > 2
symbols.Sort(ConsistentSymbolOrder.Instance);
var originalSymbols = symbols.ToImmutable();
for (int i = 0; i < symbols.Count; i++)
{
symbols[i] = UnwrapAlias(symbols[i], diagnostics, where);
}
BestSymbolInfo secondBest;
BestSymbolInfo best = GetBestSymbolInfo(symbols, out secondBest);
Debug.Assert(!best.IsNone);
Debug.Assert(!secondBest.IsNone);
if (best.IsFromCompilation && !secondBest.IsFromCompilation)
{
var srcSymbol = symbols[best.Index];
var mdSymbol = symbols[secondBest.Index];
object arg0;
if (best.IsFromSourceModule)
{
arg0 = srcSymbol.GetFirstLocation().SourceTree.FilePath;
}
else
{
Debug.Assert(best.IsFromAddedModule);
arg0 = srcSymbol.ContainingModule;
}
//if names match, arities match, and containing symbols match (recursively), ...
if (NameAndArityMatchRecursively(srcSymbol, mdSymbol))
{
if (srcSymbol.Kind == SymbolKind.Namespace && mdSymbol.Kind == SymbolKind.NamedType)
{
// ErrorCode.WRN_SameFullNameThisNsAgg: The namespace '{1}' in '{0}' conflicts with the imported type '{3}' in '{2}'. Using the namespace defined in '{0}'.
diagnostics.Add(ErrorCode.WRN_SameFullNameThisNsAgg, where.Location, originalSymbols,
arg0,
srcSymbol,
mdSymbol.ContainingAssembly,
mdSymbol);
return originalSymbols[best.Index];
}
else if (srcSymbol.Kind == SymbolKind.NamedType && mdSymbol.Kind == SymbolKind.Namespace)
{
// ErrorCode.WRN_SameFullNameThisAggNs: The type '{1}' in '{0}' conflicts with the imported namespace '{3}' in '{2}'. Using the type defined in '{0}'.
diagnostics.Add(ErrorCode.WRN_SameFullNameThisAggNs, where.Location, originalSymbols,
arg0,
srcSymbol,
GetContainingAssembly(mdSymbol),
mdSymbol);
return originalSymbols[best.Index];
}
else if (srcSymbol.Kind == SymbolKind.NamedType && mdSymbol.Kind == SymbolKind.NamedType)
{
// WRN_SameFullNameThisAggAgg: The type '{1}' in '{0}' conflicts with the imported type '{3}' in '{2}'. Using the type defined in '{0}'.
diagnostics.Add(ErrorCode.WRN_SameFullNameThisAggAgg, where.Location, originalSymbols,
arg0,
srcSymbol,
mdSymbol.ContainingAssembly,
mdSymbol);
return originalSymbols[best.Index];
}
else
{
// namespace would be merged with the source namespace:
Debug.Assert(!(srcSymbol.Kind == SymbolKind.Namespace && mdSymbol.Kind == SymbolKind.Namespace));
}
}
}
var first = symbols[best.Index];
var second = symbols[secondBest.Index];
Debug.Assert(!Symbol.Equals(originalSymbols[best.Index], originalSymbols[secondBest.Index], TypeCompareKind.ConsiderEverything) || options.IsAttributeTypeLookup(),
"This kind of ambiguity is only possible for attributes.");
Debug.Assert(!Symbol.Equals(first, second, TypeCompareKind.ConsiderEverything) || !Symbol.Equals(originalSymbols[best.Index], originalSymbols[secondBest.Index], TypeCompareKind.ConsiderEverything),
"Why does the LookupResult contain the same symbol twice?");
if (best.IsFromFile && !secondBest.IsFromFile)
{
// a lookup of a file-local type is "better" than a lookup of a non-file-local type; no need to further diagnose
// https://github.com/dotnet/roslyn/issues/62331
// some "single symbol" diagnostics are missed here for similar reasons
// that make us miss diagnostics when reporting WRN_SameFullNameThisAggAgg.
//
return first;
}
CSDiagnosticInfo info;
bool reportError;
//if names match, arities match, and containing symbols match (recursively), ...
if (first != second &&
NameAndArityMatchRecursively(first, second))
{
// suppress reporting the error if we found multiple symbols from source module
// since an error has already been reported from the declaration
reportError = !(best.IsFromSourceModule && secondBest.IsFromSourceModule);
if (first.Kind == SymbolKind.NamedType && second.Kind == SymbolKind.NamedType)
{
if (first.OriginalDefinition == second.OriginalDefinition)
{
// We imported different generic instantiations of the same generic type
// and have an ambiguous reference to a type nested in it
reportError = true;
// '{0}' is an ambiguous reference between '{1}' and '{2}'
info = new CSDiagnosticInfo(ErrorCode.ERR_AmbigContext, originalSymbols,
new object[] {
(where as NameSyntax)?.ErrorDisplayName() ?? simpleName,
new FormattedSymbol(first, SymbolDisplayFormat.CSharpErrorMessageFormat),
new FormattedSymbol(second, SymbolDisplayFormat.CSharpErrorMessageFormat) });
}
else
{
Debug.Assert(!best.IsFromCorLibrary);
// ErrorCode.ERR_SameFullNameAggAgg: The type '{1}' exists in both '{0}' and '{2}'
info = new CSDiagnosticInfo(ErrorCode.ERR_SameFullNameAggAgg, originalSymbols,
new object[] { first.ContainingAssembly, first, second.ContainingAssembly });
// Do not report this error if the first is declared in source and the second is declared in added module,
// we already reported declaration error about this name collision.
// Do not report this error if both are declared in added modules,
// we will report assembly level declaration error about this name collision.
if (secondBest.IsFromAddedModule)
{
Debug.Assert(best.IsFromCompilation);
reportError = false;
}
else if (this.Flags.Includes(BinderFlags.IgnoreCorLibraryDuplicatedTypes) &&
secondBest.IsFromCorLibrary)
{
// Ignore duplicate types from the cor library if necessary.
// (Specifically the framework assemblies loaded at runtime in
// the EE may contain types also available from mscorlib.dll.)
return first;
}
}
}
else if (first.Kind == SymbolKind.Namespace && second.Kind == SymbolKind.NamedType)
{
// ErrorCode.ERR_SameFullNameNsAgg: The namespace '{1}' in '{0}' conflicts with the type '{3}' in '{2}'
info = new CSDiagnosticInfo(ErrorCode.ERR_SameFullNameNsAgg, originalSymbols,
new object[] { GetContainingAssembly(first), first, second.ContainingAssembly, second });
// Do not report this error if namespace is declared in source and the type is declared in added module,
// we already reported declaration error about this name collision.
if (best.IsFromSourceModule && secondBest.IsFromAddedModule)
{
reportError = false;
}
}
else if (first.Kind == SymbolKind.NamedType && second.Kind == SymbolKind.Namespace)
{
if (!secondBest.IsFromCompilation || secondBest.IsFromSourceModule)
{
// ErrorCode.ERR_SameFullNameNsAgg: The namespace '{1}' in '{0}' conflicts with the type '{3}' in '{2}'
info = new CSDiagnosticInfo(ErrorCode.ERR_SameFullNameNsAgg, originalSymbols,
new object[] { GetContainingAssembly(second), second, first.ContainingAssembly, first });
}
else
{
Debug.Assert(secondBest.IsFromAddedModule);
// ErrorCode.ERR_SameFullNameThisAggThisNs: The type '{1}' in '{0}' conflicts with the namespace '{3}' in '{2}'
object arg0;
if (best.IsFromSourceModule)
{
arg0 = first.GetFirstLocation().SourceTree.FilePath;
}
else
{
Debug.Assert(best.IsFromAddedModule);
arg0 = first.ContainingModule;
}
ModuleSymbol arg2 = second.ContainingModule;
// Merged namespaces that span multiple modules don't have a containing module,
// so just use module with the smallest ordinal from the containing assembly.
if ((object)arg2 == null)
{
foreach (NamespaceSymbol ns in ((NamespaceSymbol)second).ConstituentNamespaces)
{
if (ns.ContainingAssembly == Compilation.Assembly)
{
ModuleSymbol module = ns.ContainingModule;
if ((object)arg2 == null || arg2.Ordinal > module.Ordinal)
{
arg2 = module;
}
}
}
}
Debug.Assert(arg2.ContainingAssembly == Compilation.Assembly);
info = new CSDiagnosticInfo(ErrorCode.ERR_SameFullNameThisAggThisNs, originalSymbols,
new object[] { arg0, first, arg2, second });
}
}
else if (first.Kind == SymbolKind.RangeVariable && second.Kind == SymbolKind.RangeVariable)
{
// We will already have reported a conflicting range variable declaration.
info = new CSDiagnosticInfo(ErrorCode.ERR_AmbigMember, originalSymbols,
new object[] { first, second });
}
else
{
// TODO: this is not an appropriate error message here, but used as a fallback until the
// appropriate diagnostics are implemented.
// '{0}' is an ambiguous reference between '{1}' and '{2}'
//info = diagnostics.Add(ErrorCode.ERR_AmbigContext, location, readOnlySymbols,
// whereText,
// first,
// second);
// CS0229: Ambiguity between '{0}' and '{1}'
info = new CSDiagnosticInfo(ErrorCode.ERR_AmbigMember, originalSymbols,
new object[] { first, second });
reportError = true;
}
}
else
{
Debug.Assert(originalSymbols[best.Index].Name != originalSymbols[secondBest.Index].Name ||
!Symbol.Equals(originalSymbols[best.Index], originalSymbols[secondBest.Index], TypeCompareKind.ConsiderEverything),
"Why was the lookup result viable if it contained non-equal symbols with the same name?");
reportError = true;
if (first is NamespaceOrTypeSymbol && second is NamespaceOrTypeSymbol)
{
if (options.IsAttributeTypeLookup() &&
first.Kind == SymbolKind.NamedType &&
second.Kind == SymbolKind.NamedType &&
originalSymbols[best.Index].Name != originalSymbols[secondBest.Index].Name && // Use alias names, if available.
Compilation.IsAttributeType((NamedTypeSymbol)first) &&
Compilation.IsAttributeType((NamedTypeSymbol)second))
{
// SPEC: If an attribute class is found both with and without Attribute suffix, an ambiguity
// SPEC: is present, and a compile-time error results.
info = new CSDiagnosticInfo(ErrorCode.ERR_AmbiguousAttribute, originalSymbols,
new object[] { (where as NameSyntax)?.ErrorDisplayName() ?? simpleName, first, second });
}
else
{
// '{0}' is an ambiguous reference between '{1}' and '{2}'
info = new CSDiagnosticInfo(ErrorCode.ERR_AmbigContext, originalSymbols,
new object[] {
(where as NameSyntax)?.ErrorDisplayName() ?? simpleName,
new FormattedSymbol(first, SymbolDisplayFormat.CSharpErrorMessageFormat),
new FormattedSymbol(second, SymbolDisplayFormat.CSharpErrorMessageFormat) });
}
}
else
{
// CS0229: Ambiguity between '{0}' and '{1}'
info = new CSDiagnosticInfo(ErrorCode.ERR_AmbigMember, originalSymbols,
new object[] { first, second });
}
}
wasError = true;
if (reportError && info != null)
{
diagnostics.Add(info, where.Location);
}
return new ExtendedErrorTypeSymbol(
GetContainingNamespaceOrType(originalSymbols[0]),
originalSymbols,
LookupResultKind.Ambiguous,
info,
arity);
}
else
{
// Single viable result.
var singleResult = symbols[0];
// Cannot reference System.Void directly.
var singleType = singleResult as TypeSymbol;
if ((object)singleType != null && singleType.PrimitiveTypeCode == Cci.PrimitiveTypeCode.Void && simpleName == "Void")
{
wasError = true;
var errorInfo = new CSDiagnosticInfo(ErrorCode.ERR_SystemVoid);
diagnostics.Add(errorInfo, where.Location);
singleResult = new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(singleResult), singleResult, LookupResultKind.NotReferencable, errorInfo); // UNDONE: Review resultkind.
}
// Check for bad symbol.
else
{
if (singleResult.Kind == SymbolKind.NamedType &&
((SourceModuleSymbol)this.Compilation.SourceModule).AnyReferencedAssembliesAreLinked)
{
// Complain about unembeddable types from linked assemblies.
if (diagnostics.DiagnosticBag is object)
{
Emit.NoPia.EmbeddedTypesManager.IsValidEmbeddableType((NamedTypeSymbol)singleResult, where, diagnostics.DiagnosticBag);
}
}
if (!suppressUseSiteDiagnostics)
{
wasError = ReportUseSite(singleResult, diagnostics, where);
}
else if (singleResult.Kind == SymbolKind.ErrorType)
{
// We want to report ERR_CircularBase error on the spot to make sure
// that the right location is used for it.
var errorType = (ErrorTypeSymbol)singleResult;
if (errorType.Unreported)
{
DiagnosticInfo errorInfo = errorType.ErrorInfo;
if (errorInfo != null && errorInfo.Code == (int)ErrorCode.ERR_CircularBase)
{
wasError = true;
diagnostics.Add(errorInfo, where.Location);
singleResult = new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(errorType), errorType.Name, errorType.Arity, errorInfo, unreported: false);
}
}
}
}
return singleResult;
}
}
// Below here is the error case; no viable symbols found (but maybe one or more non-viable.)
wasError = true;
if (result.Kind == LookupResultKind.Empty)
{
string aliasOpt = null;
SyntaxNode node = where;
while (node is ExpressionSyntax)
{
if (node.Kind() == SyntaxKind.AliasQualifiedName)
{
aliasOpt = ((AliasQualifiedNameSyntax)node).Alias.Identifier.ValueText;
break;
}
node = node.Parent;
}
CSDiagnosticInfo info = NotFound(where, simpleName, arity, (where as NameSyntax)?.ErrorDisplayName() ?? simpleName, diagnostics, aliasOpt, qualifierOpt, options);
return new ExtendedErrorTypeSymbol(qualifierOpt ?? Compilation.Assembly.GlobalNamespace, simpleName, arity, info);
}
Debug.Assert(symbols.Count > 0);
// Report any errors we encountered with the symbol we looked up.
if (!suppressUseSiteDiagnostics)
{
for (int i = 0; i < symbols.Count; i++)
{
ReportUseSite(symbols[i], diagnostics, where);
}
}
// result.Error might be null if we have already generated parser errors,
// e.g. when generic name is used for attribute name.
if (result.Error != null &&
((object)qualifierOpt == null || qualifierOpt.Kind != SymbolKind.ErrorType)) // Suppress cascading.
{
diagnostics.Add(new CSDiagnostic(result.Error, where.Location));
}
if ((symbols.Count > 1) || (symbols[0] is NamespaceOrTypeSymbol || symbols[0] is AliasSymbol) ||
result.Kind == LookupResultKind.NotATypeOrNamespace || result.Kind == LookupResultKind.NotAnAttributeType)
{
// Bad type or namespace (or things expected as types/namespaces) are packaged up as error types, preserving the symbols and the result kind.
// We do this if there are multiple symbols too, because just returning one would be losing important information, and they might
// be of different kinds.
return new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(symbols[0]), symbols.ToImmutable(), result.Kind, result.Error, arity);
}
else
{
// It's a single non-type-or-namespace; error was already reported, so just return it.
return symbols[0];
}
}
}
private static AssemblySymbol GetContainingAssembly(Symbol symbol)
{
// Merged namespaces that span multiple assemblies don't have a containing assembly,
// so just use the containing assembly of the first constituent.
return symbol.ContainingAssembly ?? ((NamespaceSymbol)symbol).ConstituentNamespaces.First().ContainingAssembly;
}
[Flags]
private enum BestSymbolLocation
{
None,
FromFile,
FromSourceModule,
FromAddedModule,
FromReferencedAssembly,
FromCorLibrary,
}
[DebuggerDisplay("Location = {_location}, Index = {_index}")]
private readonly struct BestSymbolInfo
{
private readonly BestSymbolLocation _location;
private readonly int _index;
/// <summary>
/// Returns -1 if None.
/// </summary>
public int Index
{
get
{
return IsNone ? -1 : _index;
}
}
public bool IsFromSourceModule
{
get
{
return _location == BestSymbolLocation.FromSourceModule;
}
}
public bool IsFromAddedModule
{
get
{
return _location == BestSymbolLocation.FromAddedModule;
}
}
public bool IsFromCompilation
{
get
{
return (_location == BestSymbolLocation.FromSourceModule) || (_location == BestSymbolLocation.FromAddedModule);
}
}
public bool IsFromFile
{
get
{
return _location == BestSymbolLocation.FromFile;
}
}
public bool IsNone
{
get
{
return _location == BestSymbolLocation.None;
}
}
public bool IsFromCorLibrary
{
get
{
return _location == BestSymbolLocation.FromCorLibrary;
}
}
public BestSymbolInfo(BestSymbolLocation location, int index)
{
Debug.Assert(location != BestSymbolLocation.None);
_location = location;
_index = index;
}
/// <summary>
/// Prefers symbols from source module, then from added modules, then from referenced assemblies.
/// Returns true if values were swapped.
/// </summary>
public static bool Sort(ref BestSymbolInfo first, ref BestSymbolInfo second)
{
if (IsSecondLocationBetter(first._location, second._location))
{
BestSymbolInfo temp = first;
first = second;
second = temp;
return true;
}
return false;
}
/// <summary>
/// Returns true if the second is a better location than the first.
/// </summary>
public static bool IsSecondLocationBetter(BestSymbolLocation firstLocation, BestSymbolLocation secondLocation)
{
Debug.Assert(secondLocation != 0);
return (firstLocation == BestSymbolLocation.None) || (firstLocation > secondLocation);
}
}
/// <summary>
/// Prefer symbols from source module, then from added modules, then from referenced assemblies.
/// </summary>
private BestSymbolInfo GetBestSymbolInfo(ArrayBuilder<Symbol> symbols, out BestSymbolInfo secondBest)
{
BestSymbolInfo first = default(BestSymbolInfo);
BestSymbolInfo second = default(BestSymbolInfo);
var compilation = this.Compilation;
for (int i = 0; i < symbols.Count; i++)
{
var symbol = symbols[i];
BestSymbolLocation location;
if (symbol.Kind == SymbolKind.Namespace)
{
location = BestSymbolLocation.None;
foreach (var ns in ((NamespaceSymbol)symbol).ConstituentNamespaces)
{
var current = GetLocation(compilation, ns);
if (BestSymbolInfo.IsSecondLocationBetter(location, current))
{
location = current;
if (location == BestSymbolLocation.FromSourceModule)
{
break;
}
}
}
}
else
{
location = GetLocation(compilation, symbol);
}
var third = new BestSymbolInfo(location, i);
if (BestSymbolInfo.Sort(ref second, ref third))
{
BestSymbolInfo.Sort(ref first, ref second);
}
}
Debug.Assert(!first.IsNone);
Debug.Assert(!second.IsNone);
secondBest = second;
return first;
}
private static BestSymbolLocation GetLocation(CSharpCompilation compilation, Symbol symbol)
{
if (symbol is NamedTypeSymbol { IsFileLocal: true })
{
return BestSymbolLocation.FromFile;
}
var containingAssembly = symbol.ContainingAssembly;
if (containingAssembly == compilation.SourceAssembly)
{
return (symbol.ContainingModule == compilation.SourceModule) ?
BestSymbolLocation.FromSourceModule :
BestSymbolLocation.FromAddedModule;
}
else
{
return (containingAssembly == containingAssembly.CorLibrary) ?
BestSymbolLocation.FromCorLibrary :
BestSymbolLocation.FromReferencedAssembly;
}
}
/// <remarks>
/// This is only intended to be called when the type isn't found (i.e. not when it is found but is inaccessible, has the wrong arity, etc).
/// </remarks>
private CSDiagnosticInfo NotFound(SyntaxNode where, string simpleName, int arity, string whereText, BindingDiagnosticBag diagnostics, string aliasOpt, NamespaceOrTypeSymbol qualifierOpt, LookupOptions options)
{
var location = where.Location;
// Lookup totally ignores type forwarders, but we want the type lookup diagnostics
// to distinguish between a type that can't be found and a type that is only present
// as a type forwarder. We'll look for type forwarders in the containing and
// referenced assemblies and report more specific diagnostics if they are found.
AssemblySymbol forwardedToAssembly;
// for attributes, suggest both, but not for verbatim name
if (options.IsAttributeTypeLookup() && !options.IsVerbatimNameAttributeTypeLookup())
{
string attributeName = arity > 0 ? $"{simpleName}Attribute<>" : $"{simpleName}Attribute";
NotFound(where, simpleName, arity, attributeName, diagnostics, aliasOpt, qualifierOpt, options | LookupOptions.VerbatimNameAttributeTypeOnly);
}
if ((object)qualifierOpt != null)
{
if (qualifierOpt.IsType)
{
var errorQualifier = qualifierOpt as ErrorTypeSymbol;
if ((object)errorQualifier != null && errorQualifier.ErrorInfo != null)
{
return (CSDiagnosticInfo)errorQualifier.ErrorInfo;
}
return diagnostics.Add(ErrorCode.ERR_DottedTypeNameNotFoundInAgg, location, whereText, qualifierOpt);
}
else
{
Debug.Assert(qualifierOpt.IsNamespace);
forwardedToAssembly = GetForwardedToAssembly(simpleName, arity, ref qualifierOpt, diagnostics, location);
if (ReferenceEquals(qualifierOpt, Compilation.GlobalNamespace))
{
Debug.Assert(aliasOpt == null || aliasOpt == SyntaxFacts.GetText(SyntaxKind.GlobalKeyword));
return (object)forwardedToAssembly == null
? diagnostics.Add(ErrorCode.ERR_GlobalSingleTypeNameNotFound, location, whereText)
: diagnostics.Add(ErrorCode.ERR_GlobalSingleTypeNameNotFoundFwd, location, whereText, forwardedToAssembly);
}
else
{
object container = qualifierOpt;
// If there was an alias (e.g. A::C) and the given qualifier is the global namespace of the alias,
// then use the alias name in the error message, since it's more helpful than "<global namespace>".
if (aliasOpt != null && qualifierOpt.IsNamespace && ((NamespaceSymbol)qualifierOpt).IsGlobalNamespace)
{
container = aliasOpt;
}
return (object)forwardedToAssembly == null
? diagnostics.Add(ErrorCode.ERR_DottedTypeNameNotFoundInNS, location, whereText, container)
: diagnostics.Add(ErrorCode.ERR_DottedTypeNameNotFoundInNSFwd, location, whereText, container, forwardedToAssembly);
}
}
}
if (options == LookupOptions.NamespaceAliasesOnly)
{
return diagnostics.Add(ErrorCode.ERR_AliasNotFound, location, whereText);
}
if ((where as IdentifierNameSyntax)?.Identifier.Text == "var" && !options.IsAttributeTypeLookup())
{
var code = (where.Parent is QueryClauseSyntax) ? ErrorCode.ERR_TypeVarNotFoundRangeVariable : ErrorCode.ERR_TypeVarNotFound;
return diagnostics.Add(code, location);
}
forwardedToAssembly = GetForwardedToAssembly(simpleName, arity, ref qualifierOpt, diagnostics, location);
if ((object)forwardedToAssembly != null)
{
return qualifierOpt == null
? diagnostics.Add(ErrorCode.ERR_SingleTypeNameNotFoundFwd, location, whereText, forwardedToAssembly)
: diagnostics.Add(ErrorCode.ERR_DottedTypeNameNotFoundInNSFwd, location, whereText, qualifierOpt, forwardedToAssembly);
}
return diagnostics.Add(ErrorCode.ERR_SingleTypeNameNotFound, location, whereText);
}
protected virtual AssemblySymbol GetForwardedToAssemblyInUsingNamespaces(string metadataName, ref NamespaceOrTypeSymbol qualifierOpt, BindingDiagnosticBag diagnostics, Location location)
{
return Next?.GetForwardedToAssemblyInUsingNamespaces(metadataName, ref qualifierOpt, diagnostics, location);
}
protected AssemblySymbol GetForwardedToAssembly(string fullName, BindingDiagnosticBag diagnostics, Location location)
{
var metadataName = MetadataTypeName.FromFullName(fullName);
foreach (var referencedAssembly in
Compilation.Assembly.Modules[0].GetReferencedAssemblySymbols())
{
var forwardedType =
referencedAssembly.TryLookupForwardedMetadataTypeWithCycleDetection(ref metadataName, visitedAssemblies: null);
if ((object)forwardedType != null)
{
if (forwardedType.Kind == SymbolKind.ErrorType)
{
DiagnosticInfo diagInfo = ((ErrorTypeSymbol)forwardedType).ErrorInfo;
if (diagInfo.Code == (int)ErrorCode.ERR_CycleInTypeForwarder)
{
Debug.Assert((object)forwardedType.ContainingAssembly != null, "How did we find a cycle if there was no forwarding?");
diagnostics.Add(ErrorCode.ERR_CycleInTypeForwarder, location, fullName, forwardedType.ContainingAssembly.Name);
}
else if (diagInfo.Code == (int)ErrorCode.ERR_TypeForwardedToMultipleAssemblies)
{
diagnostics.Add(diagInfo, location);
return null; // Cannot determine a suitable forwarding assembly
}
}
return forwardedType.ContainingAssembly;
}
}
return null;
}
internal static ContextualAttributeBinder TryGetContextualAttributeBinder(Binder binder)
{
if ((binder.Flags & BinderFlags.InContextualAttributeBinder) != 0)
{
do
{
if (binder is ContextualAttributeBinder contextualAttributeBinder)
{
return contextualAttributeBinder;
}
binder = binder.Next;
}
while (binder != null);
Debug.Assert(false);
}
return null;
}
/// <summary>
/// Look for a type forwarder for the given type in the containing assembly and any referenced assemblies.
/// </summary>
/// <param name="name">The name of the (potentially) forwarded type.</param>
/// <param name="arity">The arity of the forwarded type.</param>
/// <param name="qualifierOpt">The namespace of the potentially forwarded type. If none is provided, will
/// try Usings of the current import for eligible namespaces and return the namespace of the found forwarder,
/// if any.</param>
/// <param name="diagnostics">Will be used to report non-fatal errors during look up.</param>
/// <param name="location">Location to report errors on.</param>
/// <returns>Returns the Assembly to which the type is forwarded, or null if none is found.</returns>
/// <remarks>
/// Since this method is intended to be used for error reporting, it stops as soon as it finds
/// any type forwarder (or an error to report). It does not check other assemblies for consistency or better results.
/// </remarks>
protected AssemblySymbol GetForwardedToAssembly(string name, int arity, ref NamespaceOrTypeSymbol qualifierOpt, BindingDiagnosticBag diagnostics, Location location)
{
// If we are in the process of binding assembly level attributes, we might get into an infinite cycle
// if any of the referenced assemblies forwards type to this assembly. Since forwarded types
// are specified through assembly level attributes, an attempt to resolve the forwarded type
// might require us to examine types forwarded by this assembly, thus binding assembly level
// attributes again. And the cycle continues.
// So, we won't do the analysis in this case, at the expense of better diagnostics.
var contextualAttributeBinder = TryGetContextualAttributeBinder(this);
if (contextualAttributeBinder is { AttributeTarget: { Kind: SymbolKind.Assembly } })
{
return null;
}
// NOTE: This won't work if the type isn't using CLS-style generic naming (i.e. `arity), but this code is
// only intended to improve diagnostic messages, so false negatives in corner cases aren't a big deal.
// File types can't be forwarded, so we won't attempt to determine a file identifier to attach to the metadata name.
var metadataName = MetadataHelpers.ComposeAritySuffixedMetadataName(name, arity, associatedFileIdentifier: null);
var fullMetadataName = MetadataHelpers.BuildQualifiedName(qualifierOpt?.ToDisplayString(SymbolDisplayFormat.QualifiedNameOnlyFormat), metadataName);
var result = GetForwardedToAssembly(fullMetadataName, diagnostics, location);
if ((object)result != null)
{
return result;
}
if ((object)qualifierOpt == null)
{
return GetForwardedToAssemblyInUsingNamespaces(metadataName, ref qualifierOpt, diagnostics, location);
}
return null;
}
#nullable enable
internal static bool CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, BindingDiagnosticBag diagnostics, Location? location = null)
=> CheckFeatureAvailability(syntax, feature, diagnostics.DiagnosticBag, location);
internal static bool CheckFeatureAvailability(SyntaxToken syntax, MessageID feature, BindingDiagnosticBag diagnostics, bool forceWarning = false)
=> CheckFeatureAvailability(syntax, feature, diagnostics.DiagnosticBag, forceWarning: forceWarning);
internal static bool CheckFeatureAvailability(SyntaxTree tree, MessageID feature, BindingDiagnosticBag diagnostics, Location location)
=> CheckFeatureAvailability(tree, feature, diagnostics.DiagnosticBag, location);
private static bool CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, DiagnosticBag? diagnostics, Location? location = null)
=> CheckFeatureAvailability(syntax.SyntaxTree, feature, diagnostics, (location, syntax), static tuple => tuple.location ?? tuple.syntax.GetLocation());
private static bool CheckFeatureAvailability(SyntaxToken syntax, MessageID feature, DiagnosticBag? diagnostics, bool forceWarning = false)
=> CheckFeatureAvailability(syntax.SyntaxTree!, feature, diagnostics, syntax, static syntax => syntax.GetLocation(), forceWarning: forceWarning);
private static bool CheckFeatureAvailability(SyntaxTree tree, MessageID feature, DiagnosticBag? diagnostics, Location location)
=> CheckFeatureAvailability(tree, feature, diagnostics, location, static location => location);
/// <param name="getLocation">Callback function that computes the location to report the diagnostics at
/// <em>if</em> a diagnostic should be reported. Should always be passed a static/cached callback to prevent
/// allocations of the delegate.</param>
private static bool CheckFeatureAvailability<TData>(SyntaxTree tree, MessageID feature, DiagnosticBag? diagnostics, TData data, Func<TData, Location> getLocation, bool forceWarning = false)
{
if (feature.GetFeatureAvailabilityDiagnosticInfo((CSharpParseOptions)tree.Options) is { } diagInfo)
{
if (forceWarning)
{
diagnostics?.Add(ErrorCode.WRN_ErrorOverride, getLocation(data), diagInfo, (int)diagInfo.Code);
}
else
{
diagnostics?.Add(diagInfo, getLocation(data));
}
return false;
}
return true;
}
}
}
|