// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.RuntimeMembers; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { internal partial class Binder { /// /// Binds the type for the syntax taking into account possibility of "var" type. /// /// Type syntax to bind. /// Diagnostics. /// /// 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. /// /// /// Bound type if syntax binds to a type in the current context and /// null if syntax binds to "var" keyword in the current context. /// internal TypeSymbolWithAnnotations BindTypeOrVarKeyword(TypeSyntax syntax, DiagnosticBag diagnostics, out bool isVar) { var symbol = BindTypeOrAliasOrVarKeyword(syntax, diagnostics, out isVar); Debug.Assert(isVar == symbol.IsDefault); return isVar ? default : UnwrapAlias(symbol, diagnostics, syntax).Type; } /// /// Binds the type for the syntax taking into account possibility of "unmanaged" type. /// /// Type syntax to bind. /// Diagnostics. /// /// Set to false if syntax binds to a type in the current context and true if /// syntax is "unmanaged" and it binds to "unmanaged" keyword in the current context. /// /// /// Bound type if syntax binds to a type in the current context and /// null if syntax binds to "unmanaged" keyword in the current context. /// internal TypeSymbolWithAnnotations BindTypeOrUnmanagedKeyword(TypeSyntax syntax, DiagnosticBag diagnostics, out bool isUnmanaged) { var symbol = BindTypeOrAliasOrUnmanagedKeyword(syntax, diagnostics, out isUnmanaged); Debug.Assert(isUnmanaged == symbol.IsDefault); return isUnmanaged ? default : UnwrapAlias(symbol, diagnostics, syntax).Type; } /// /// Binds the type for the syntax taking into account possibility of "var" type. /// /// Type syntax to bind. /// Diagnostics. /// /// 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. /// /// Alias symbol if syntax binds to an alias. /// /// Bound type if syntax binds to a type in the current context and /// null if syntax binds to "var" keyword in the current context. /// internal TypeSymbolWithAnnotations BindTypeOrVarKeyword(TypeSyntax syntax, DiagnosticBag 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).Type; } } /// /// 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. /// /// Type syntax to bind. /// Diagnostics. /// /// 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. /// /// /// 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. /// private NamespaceOrTypeOrAliasSymbolWithAnnotations BindTypeOrAliasOrVarKeyword(TypeSyntax syntax, DiagnosticBag diagnostics, out bool isVar) { if (syntax.IsVar) { var symbol = BindTypeOrAliasOrKeyword(syntax, diagnostics, out isVar); if (isVar) { CheckFeatureAvailability(syntax, MessageID.IDS_FeatureImplicitLocal, diagnostics); } return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(NonNullTypesContext, symbol); } else { isVar = false; return BindTypeOrAlias(syntax, diagnostics, basesBeingResolved: null); } } private NamespaceOrTypeOrAliasSymbolWithAnnotations BindTypeOrAliasOrUnmanagedKeyword(TypeSyntax syntax, DiagnosticBag diagnostics, out bool isUnmanaged) { if (syntax.IsUnmanaged) { var symbol = BindTypeOrAliasOrKeyword(syntax, diagnostics, out isUnmanaged); if (isUnmanaged) { CheckFeatureAvailability(syntax, MessageID.IDS_FeatureUnmanagedGenericTypeConstraint, diagnostics); } return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(NonNullTypesContext, symbol); } else { isUnmanaged = false; return BindTypeOrAlias(syntax, diagnostics, basesBeingResolved: null); } } /// /// 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 or . /// Otherwise, call instead. /// private Symbol BindTypeOrAliasOrKeyword(TypeSyntax syntax, DiagnosticBag diagnostics, out bool isKeyword) { // Keywords can only be IdentifierNameSyntax var identifierValueText = ((IdentifierNameSyntax)syntax).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(); HashSet useSiteDiagnostics = null; this.LookupSymbolsInternal(lookupResult, identifierValueText, arity: 0, useSiteDiagnostics: ref useSiteDiagnostics, 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) DiagnosticBag resultDiagnostics = DiagnosticBag.GetInstance(); bool wasError; symbol = ResultSymbol( lookupResult, identifierValueText, arity: 0, where: syntax, diagnostics: resultDiagnostics, suppressUseSiteDiagnostics: false, wasError: out wasError); // 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); resultDiagnostics.Free(); if (lookupResult.IsSingleViable) { var type = UnwrapAlias(symbol, diagnostics, syntax) as TypeSymbol; if ((object)type != null) { // Case (2)(a)(1) isKeyword = 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 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 TypeSymbolWithAnnotations BindType(ExpressionSyntax syntax, DiagnosticBag diagnostics, ConsList basesBeingResolved = null) { var symbol = BindTypeOrAlias(syntax, diagnostics, basesBeingResolved); return UnwrapAlias(symbol, diagnostics, syntax, basesBeingResolved).Type; } // 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 TypeSymbolWithAnnotations BindType(ExpressionSyntax syntax, DiagnosticBag diagnostics, out AliasSymbol alias, ConsList basesBeingResolved = null) { var symbol = BindTypeOrAlias(syntax, diagnostics, basesBeingResolved); return UnwrapAlias(symbol, out alias, diagnostics, syntax, basesBeingResolved).Type; } // 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, DiagnosticBag diagnostics, ConsList basesBeingResolved = null) { Debug.Assert(diagnostics != null); var symbol = BindNamespaceOrTypeOrAliasSymbol(syntax, diagnostics, basesBeingResolved, basesBeingResolved != null); // 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.Type.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 TypeSymbolWithAnnotations.Create(new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(symbol.Symbol), symbol.Symbol, LookupResultKind.NotATypeOrNamespace, diagnosticInfo)); } /// /// The immediately containing namespace or named type, or the global /// namespace if containing symbol is neither a namespace or named type. /// private NamespaceOrTypeSymbol GetContainingNamespaceOrType(Symbol symbol) { return symbol.ContainingNamespaceOrType() ?? this.Compilation.Assembly.GlobalNamespace; } internal Symbol BindNamespaceAliasSymbol(IdentifierNameSyntax node, DiagnosticBag diagnostics) { if (node.Identifier.Kind() == SyntaxKind.GlobalKeyword) { return this.Compilation.GlobalNamespaceAlias; } else { bool wasError; var plainName = node.Identifier.ValueText; var result = LookupResult.GetInstance(); HashSet useSiteDiagnostics = null; this.LookupSymbolsWithFallback(result, plainName, 0, ref useSiteDiagnostics, null, LookupOptions.NamespaceAliasesOnly); diagnostics.Add(node, useSiteDiagnostics); Symbol bindingResult = ResultSymbol(result, plainName, 0, node, diagnostics, false, out wasError, options: LookupOptions.NamespaceAliasesOnly); result.Free(); return bindingResult; } } internal NamespaceOrTypeOrAliasSymbolWithAnnotations BindNamespaceOrTypeSymbol(ExpressionSyntax syntax, DiagnosticBag diagnostics, ConsList basesBeingResolved = null) { return BindNamespaceOrTypeSymbol(syntax, diagnostics, basesBeingResolved, basesBeingResolved != null); } internal NamespaceOrTypeOrAliasSymbolWithAnnotations BindNamespaceOrTypeSymbol(ExpressionSyntax syntax, DiagnosticBag diagnostics, ConsList basesBeingResolved, bool suppressUseSiteDiagnostics) { var result = BindNamespaceOrTypeOrAliasSymbol(syntax, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics); Debug.Assert(!result.IsDefault); return UnwrapAlias(result, diagnostics, syntax, basesBeingResolved); } internal NamespaceOrTypeOrAliasSymbolWithAnnotations BindNamespaceOrTypeOrAliasSymbol(ExpressionSyntax syntax, DiagnosticBag diagnostics, ConsList basesBeingResolved, bool suppressUseSiteDiagnostics) { switch (syntax.Kind()) { case SyntaxKind.NullableType: { var nullableSyntax = (NullableTypeSyntax)syntax; TypeSyntax typeArgumentSyntax = nullableSyntax.ElementType; TypeSymbolWithAnnotations typeArgument = BindType(typeArgumentSyntax, diagnostics, basesBeingResolved); TypeSymbolWithAnnotations constructedType = typeArgument.SetIsAnnotated(Compilation); reportNullableReferenceTypesIfNeeded(nullableSyntax.QuestionToken, typeArgument); if (!ShouldCheckConstraints) { diagnostics.Add(new LazyUseSiteDiagnosticsInfoForNullableType(constructedType), syntax.GetLocation()); } else if (constructedType.IsNullableType()) { ReportUseSiteDiagnostics(constructedType.TypeSymbol.OriginalDefinition, diagnostics, syntax); var type = (NamedTypeSymbol)constructedType.TypeSymbol; var location = syntax.Location; var conversions = this.Conversions.WithNullability(includeNullability: true); if (!ShouldCheckConstraintsNullability) { diagnostics.Add(new LazyNullableContraintChecksDiagnosticInfo(type, conversions, this.Compilation), location); conversions = this.Conversions.WithNullability(includeNullability: false); } type.CheckConstraints(this.Compilation, conversions, location, diagnostics); } else if (constructedType.TypeSymbol.IsUnconstrainedTypeParameter()) { diagnostics.Add(ErrorCode.ERR_NullableUnconstrainedTypeParameter, syntax.Location); } return constructedType; } case SyntaxKind.PredefinedType: { var type = BindPredefinedTypeSymbol((PredefinedTypeSyntax)syntax, diagnostics); return TypeSymbolWithAnnotations.Create(NonNullTypesContext, type); } case SyntaxKind.IdentifierName: return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(NonNullTypesContext, BindNonGenericSimpleNamespaceOrTypeOrAliasSymbol((IdentifierNameSyntax)syntax, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics, qualifierOpt: null)); case SyntaxKind.GenericName: return TypeSymbolWithAnnotations.Create(NonNullTypesContext, BindGenericSimpleNamespaceOrTypeOrAliasSymbol((GenericNameSyntax)syntax, diagnostics, basesBeingResolved, qualifierOpt: null)); case SyntaxKind.AliasQualifiedName: { var node = (AliasQualifiedNameSyntax)syntax; var bindingResult = BindNamespaceAliasSymbol(node.Alias, diagnostics); var alias = bindingResult as AliasSymbol; NamespaceOrTypeSymbol left = ((object)alias != null) ? alias.Target : (NamespaceOrTypeSymbol)bindingResult; if (left.Kind == SymbolKind.NamedType) { return TypeSymbolWithAnnotations.Create(new ExtendedErrorTypeSymbol(left, LookupResultKind.NotATypeOrNamespace, diagnostics.Add(ErrorCode.ERR_ColColWithTypeAlias, node.Alias.Location, node.Alias.Identifier.Text))); } return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(NonNullTypesContext, this.BindSimpleNamespaceOrTypeOrAliasSymbol(node.Name, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics, left)); } case SyntaxKind.QualifiedName: { var node = (QualifiedNameSyntax)syntax; return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(NonNullTypesContext, BindQualifiedName(node.Left, node.Right, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics)); } case SyntaxKind.SimpleMemberAccessExpression: { var node = (MemberAccessExpressionSyntax)syntax; return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(NonNullTypesContext, BindQualifiedName(node.Expression, node.Name, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics)); } case SyntaxKind.ArrayType: { var node = (ArrayTypeSyntax)syntax; TypeSymbolWithAnnotations 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.TypeSymbol); } if (ShouldCheckConstraints) { if (type.IsRestrictedType()) { // CS0611: Array elements cannot be of type '{0}' Error(diagnostics, ErrorCode.ERR_ArrayElementCantBeRefAny, node.ElementType, type.TypeSymbol); } } else { diagnostics.Add(new LazyArrayElementCantBeRefAnyDiagnosticInfo(type), node.ElementType.GetLocation()); } for (int i = node.RankSpecifiers.Count - 1; i >= 0; i--) { var a = node.RankSpecifiers[i]; var array = ArrayTypeSymbol.CreateCSharpArray(this.Compilation.Assembly, type, a.Rank); if (a.QuestionToken.IsKind(SyntaxKind.QuestionToken)) { type = TypeSymbolWithAnnotations.Create(array, isNullableIfReferenceType: true, fromDeclaration: true); reportNullableReferenceTypesIfNeeded(a.QuestionToken); } else { type = TypeSymbolWithAnnotations.Create(NonNullTypesContext, array); } } return type; } case SyntaxKind.PointerType: { var node = (PointerTypeSyntax)syntax; var elementType = BindType(node.ElementType, diagnostics, basesBeingResolved); ReportUnsafeIfNotAllowed(node, diagnostics); // Checking BinderFlags.GenericConstraintsClause to prevent cycles in binding if (Flags.HasFlag(BinderFlags.GenericConstraintsClause) && elementType.TypeKind == TypeKind.TypeParameter) { // Invalid constraint type. A type used as a constraint must be an interface, a non-sealed class or a type parameter. Error(diagnostics, ErrorCode.ERR_BadConstraintType, node); } else { CheckManagedAddr(elementType.TypeSymbol, node, diagnostics); } return TypeSymbolWithAnnotations.Create(new PointerTypeSymbol(elementType)); } case SyntaxKind.OmittedTypeArgument: { return BindTypeArgument((TypeSyntax)syntax, diagnostics, basesBeingResolved); } case SyntaxKind.TupleType: { return TypeSymbolWithAnnotations.Create(NonNullTypesContext, BindTupleType((TupleTypeSyntax)syntax, diagnostics)); } case SyntaxKind.RefType: { // ref needs to be handled by the caller var refTypeSyntax = (RefTypeSyntax)syntax; var refToken = refTypeSyntax.RefKeyword; if (!syntax.HasErrors) { diagnostics.Add(ErrorCode.ERR_UnexpectedToken, refToken.GetLocation(), refToken.ToString()); } return BindNamespaceOrTypeOrAliasSymbol(refTypeSyntax.Type, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics); } default: throw ExceptionUtilities.UnexpectedValue(syntax.Kind()); } void reportNullableReferenceTypesIfNeeded(SyntaxToken questionToken, TypeSymbolWithAnnotations typeArgument = default) { // Inside a method body or other executable code, we can pull on NonNullTypes symbol or question IsValueType without causing cycles. // We still need to delay that check when binding in an attribute argument if (!InExecutableBinder || !ShouldCheckConstraintsNullability) { diagnostics.Add(new LazyMissingNonNullTypesContextDiagnosticInfo(Compilation, NonNullTypesContext, typeArgument), questionToken.GetLocation()); } else { DiagnosticInfo info = LazyMissingNonNullTypesContextDiagnosticInfo.ReportNullableReferenceTypesIfNeeded(Compilation, NonNullTypesContext, typeArgument); if (!(info is null)) { diagnostics.Add(info, questionToken.GetLocation()); } } } } private TypeSymbol BindTupleType(TupleTypeSyntax syntax, DiagnosticBag diagnostics) { int numElements = syntax.Elements.Count; var types = ArrayBuilder.GetInstance(numElements); var locations = ArrayBuilder.GetInstance(numElements); ArrayBuilder elementNames = null; // set of names already used var uniqueFieldNames = PooledHashSet.GetInstance(); bool hasExplicitNames = false; for (int i = 0; i < numElements; i++) { var argumentSyntax = syntax.Elements[i]; var argumentType = BindType(argumentSyntax.Type, diagnostics); 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 // and we need System.String as well // Report diagnostics if System.String doesn't exist this.GetSpecialType(SpecialType.System_String, diagnostics, syntax); if (!Compilation.HasTupleNamesAttributes) { var info = new CSDiagnosticInfo(ErrorCode.ERR_TupleElementNamesAttributeMissing, AttributeDescription.TupleElementNamesAttribute.FullName); Error(diagnostics, info, syntax); } } var typesArray = types.ToImmutableAndFree(); var locationsArray = locations.ToImmutableAndFree(); if (typesArray.Length < 2) { throw ExceptionUtilities.UnexpectedValue(typesArray.Length); } return TupleTypeSymbol.Create(syntax.Location, typesArray, locationsArray, elementNames == null ? default(ImmutableArray) : elementNames.ToImmutableAndFree(), this.Compilation, this.ShouldCheckConstraints, errorPositions: default(ImmutableArray), syntax: syntax, diagnostics: diagnostics); } private static void CollectTupleFieldMemberName(string name, int elementIndex, int tupleSize, ref ArrayBuilder 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.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, DiagnosticBag diagnostics, PooledHashSet uniqueFieldNames) { int reserved = TupleTypeSymbol.IsElementNameReserved(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, DiagnosticBag diagnostics) { return GetSpecialType(node.Keyword.Kind().GetSpecialType(), diagnostics, node); } /// /// Binds a simple name or the simple name portion of a qualified name. /// private Symbol BindSimpleNamespaceOrTypeOrAliasSymbol( SimpleNameSyntax syntax, DiagnosticBag diagnostics, ConsList 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 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); } } private 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; } protected Symbol BindNonGenericSimpleNamespaceOrTypeOrAliasSymbol( IdentifierNameSyntax node, DiagnosticBag diagnostics, ConsList basesBeingResolved, bool suppressUseSiteDiagnostics, NamespaceOrTypeSymbol qualifierOpt) { var identifierValueText = node.Identifier.ValueText; // If we are here in an error-recovery scenario, say, "goo(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 new ExtendedErrorTypeSymbol( Compilation.Assembly.GlobalNamespace, identifierValueText, 0, new CSDiagnosticInfo(ErrorCode.ERR_SingleTypeNameNotFound)); } var errorResult = CreateErrorIfLookupOnTypeParameter(node.Parent, qualifierOpt, identifierValueText, 0, diagnostics); if ((object)errorResult != null) { return errorResult; } var result = LookupResult.GetInstance(); LookupOptions options = GetSimpleNameLookupOptions(node, node.Identifier.IsVerbatimIdentifier()); HashSet useSiteDiagnostics = null; this.LookupSymbolsSimpleName(result, qualifierOpt, identifierValueText, 0, basesBeingResolved, options, diagnose: true, useSiteDiagnostics: ref useSiteDiagnostics); diagnostics.Add(node, useSiteDiagnostics); Symbol bindingResult; // If we were looking up the identifier "dynamic" at the topmost level and didn't find anything good, // we actually have the type dynamic (assuming /langversion is at least 4). if ((object)qualifierOpt == null && (node.Parent == null || node.Parent.Kind() != SyntaxKind.Attribute && // dynamic not allowed as an attribute type SyntaxFacts.IsInTypeOnlyContext(node)) && node.Identifier.ValueText == "dynamic" && !IsViableType(result) && ((CSharpParseOptions)node.SyntaxTree.Options).LanguageVersion >= MessageID.IDS_FeatureDynamic.RequiredVersion()) { bindingResult = Compilation.DynamicType; ReportUseSiteDiagnosticForDynamic(diagnostics, node); } else { 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.Kind == SymbolKind.NamedType && ((NamedTypeSymbol)aliasTarget).ContainsDynamic()) { ReportUseSiteDiagnosticForDynamic(diagnostics, node); } } } result.Free(); return bindingResult; } private void ReportUseSiteDiagnosticForDynamic(DiagnosticBag 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()) { if (!Compilation.HasDynamicEmitAttributes()) { // 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); } 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 basesBeingResolved = null) { if (symbol.Kind == SymbolKind.Alias) { return ((AliasSymbol)symbol).GetAliasTarget(basesBeingResolved); } return symbol; } private NamespaceOrTypeOrAliasSymbolWithAnnotations UnwrapAlias(NamespaceOrTypeOrAliasSymbolWithAnnotations symbol, DiagnosticBag diagnostics, SyntaxNode syntax, ConsList basesBeingResolved = null) { if (symbol.IsAlias) { AliasSymbol discarded; return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(NonNullTypesContext, (NamespaceOrTypeSymbol)UnwrapAlias(symbol.Symbol, out discarded, diagnostics, syntax, basesBeingResolved)); } return symbol; } private NamespaceOrTypeOrAliasSymbolWithAnnotations UnwrapAlias(NamespaceOrTypeOrAliasSymbolWithAnnotations symbol, out AliasSymbol alias, DiagnosticBag diagnostics, SyntaxNode syntax, ConsList basesBeingResolved = null) { if (symbol.IsAlias) { return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(NonNullTypesContext, (NamespaceOrTypeSymbol)UnwrapAlias(symbol.Symbol, out alias, diagnostics, syntax, basesBeingResolved)); } alias = null; return symbol; } private Symbol UnwrapAlias(Symbol symbol, DiagnosticBag diagnostics, SyntaxNode syntax, ConsList basesBeingResolved = null) { AliasSymbol discarded; return UnwrapAlias(symbol, out discarded, diagnostics, syntax, basesBeingResolved); } private Symbol UnwrapAlias(Symbol symbol, out AliasSymbol alias, DiagnosticBag diagnostics, SyntaxNode syntax, ConsList 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 NamedTypeSymbol BindGenericSimpleNamespaceOrTypeOrAliasSymbol( GenericNameSyntax node, DiagnosticBag diagnostics, ConsList 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)"? // 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" of the expression and so it seems wrong to // deduce more errors from it. var plainName = node.Identifier.ValueText; SeparatedSyntaxList 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 { // 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, BindTypeArguments(typeArguments, diagnostics, basesBeingResolved), basesBeingResolved, diagnostics); } if (options.IsAttributeTypeLookup()) { // Generic type cannot be an attribute type. // Parser error has already been reported, just wrap the result type with error type symbol. Debug.Assert(unconstructedType.IsErrorType()); Debug.Assert(resultType.IsErrorType()); resultType = new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(resultType), resultType, LookupResultKind.NotAnAttributeType, errorInfo: null); } return resultType; } private NamedTypeSymbol LookupGenericTypeName( DiagnosticBag diagnostics, ConsList 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(); HashSet useSiteDiagnostics = null; this.LookupSymbolsSimpleName(lookupResult, qualifierOpt, plainName, arity, basesBeingResolved, options, diagnose: true, useSiteDiagnostics: ref useSiteDiagnostics); diagnostics.Add(node, useSiteDiagnostics); 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(lookupResultSymbol), lookupResult.Kind, lookupResult.Error, arity); } lookupResult.Free(); return type; } private ExtendedErrorTypeSymbol CreateErrorIfLookupOnTypeParameter( CSharpSyntaxNode node, NamespaceOrTypeSymbol qualifierOpt, string name, int arity, DiagnosticBag 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 BindTypeArguments(SeparatedSyntaxList typeArguments, DiagnosticBag diagnostics, ConsList basesBeingResolved = null) { Debug.Assert(typeArguments.Count > 0); var args = ArrayBuilder.GetInstance(); foreach (var argSyntax in typeArguments) { args.Add(BindTypeArgument(argSyntax, diagnostics, basesBeingResolved)); } return args.ToImmutableAndFree(); } private TypeSymbolWithAnnotations BindTypeArgument(TypeSyntax typeArgument, DiagnosticBag diagnostics, ConsList basesBeingResolved = null) { // Unsafe types can never be type arguments, but there's a special error code for that. var binder = this.WithAdditionalFlags(BinderFlags.SuppressUnsafeDiagnostics); var arg = typeArgument.Kind() == SyntaxKind.OmittedTypeArgument ? TypeSymbolWithAnnotations.Create(UnboundArgumentErrorTypeSymbol.Instance) : binder.BindType(typeArgument, diagnostics, basesBeingResolved); return arg; } /// /// Keep check and error in sync with ConstructBoundMethodGroupAndReportOmittedTypeArguments. /// private NamedTypeSymbol ConstructNamedTypeUnlessTypeArgumentOmitted(SyntaxNode typeSyntax, NamedTypeSymbol type, SeparatedSyntaxList typeArgumentsSyntax, ImmutableArray typeArguments, DiagnosticBag diagnostics) { if (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, typeSyntax, type, MessageID.IDS_SK_TYPE.Localize(), typeArgumentsSyntax.Count); // 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. 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); } } /// /// Keep check and error in sync with ConstructNamedTypeUnlessTypeArgumentOmitted. /// private static BoundMethodOrPropertyGroup ConstructBoundMemberGroupAndReportOmittedTypeArguments( SyntaxNode syntax, SeparatedSyntaxList typeArgumentsSyntax, ImmutableArray typeArguments, BoundExpression receiver, string plainName, ArrayBuilder members, LookupResult lookupResult, BoundMethodGroupFlags methodGroupFlags, bool hasErrors, DiagnosticBag 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); switch (members[0].Kind) { case SymbolKind.Method: return new BoundMethodGroup( syntax, typeArguments, receiver, plainName, members.SelectAsArray(s_toMethodSymbolFunc), lookupResult, methodGroupFlags, hasErrors); case SymbolKind.Property: return new BoundPropertyGroup( syntax, members.SelectAsArray(s_toPropertySymbolFunc), receiver, lookupResult.Kind, hasErrors); default: throw ExceptionUtilities.UnexpectedValue(members[0].Kind); } } private static readonly Func s_toMethodSymbolFunc = s => (MethodSymbol)s; private static readonly Func s_toPropertySymbolFunc = s => (PropertySymbol)s; private NamedTypeSymbol ConstructNamedType( NamedTypeSymbol type, SyntaxNode typeSyntax, SeparatedSyntaxList typeArgumentsSyntax, ImmutableArray typeArguments, ConsList basesBeingResolved, DiagnosticBag diagnostics) { Debug.Assert(!typeArguments.IsEmpty); type = type.Construct(typeArguments); if (ShouldCheckConstraints && ConstraintsHelper.RequiresChecking(type)) { bool includeNullability = Compilation.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes); var conversions = this.Conversions.WithNullability(includeNullability); if (includeNullability && !ShouldCheckConstraintsNullability) { diagnostics.Add(new LazyNullableContraintChecksDiagnosticInfo(type, conversions, this.Compilation), typeSyntax.GetLocation()); conversions = this.Conversions.WithNullability(includeNullability: false); } type.CheckConstraintsForNonTuple(conversions, typeSyntax, typeArgumentsSyntax, this.Compilation, basesBeingResolved, diagnostics); } type = (NamedTypeSymbol)TupleTypeSymbol.TransformToTupleIfCompatible(type); return type; } /// /// 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. /// private bool ShouldCheckConstraints { get { return !this.Flags.Includes(BinderFlags.SuppressConstraintChecks); } } private bool ShouldCheckConstraintsNullability { get { return ShouldCheckConstraints && !this.Flags.Includes(BinderFlags.AttributeArgument); } } private NamespaceOrTypeSymbol BindQualifiedName( ExpressionSyntax leftName, SimpleNameSyntax rightName, DiagnosticBag diagnostics, ConsList 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) as NamespaceOrTypeSymbol; // 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) { var namedTypeRight = right as NamedTypeSymbol; if ((object)namedTypeRight != null && namedTypeRight.IsGenericType) { right = namedTypeRight.AsUnboundGenericType(); } } return right; } internal NamedTypeSymbol GetSpecialType(SpecialType typeId, DiagnosticBag diagnostics, SyntaxNode node) { return GetSpecialType(this.Compilation, typeId, node, diagnostics); } internal static NamedTypeSymbol GetSpecialType(CSharpCompilation compilation, SpecialType typeId, SyntaxNode node, DiagnosticBag diagnostics) { NamedTypeSymbol typeSymbol = compilation.GetSpecialType(typeId); Debug.Assert((object)typeSymbol != null, "Expect an error type if special type isn't found"); ReportUseSiteDiagnostics(typeSymbol, diagnostics, node); return typeSymbol; } /// /// This is a layer on top of the Compilation version that generates a diagnostic if the special /// member isn't found. /// internal Symbol GetSpecialTypeMember(SpecialMember member, DiagnosticBag diagnostics, SyntaxNode syntax) { Symbol memberSymbol; return TryGetSpecialTypeMember(this.Compilation, member, syntax, diagnostics, out memberSymbol) ? memberSymbol : null; } internal static bool TryGetSpecialTypeMember(CSharpCompilation compilation, SpecialMember specialMember, SyntaxNode syntax, DiagnosticBag diagnostics, out TSymbol symbol) where TSymbol : Symbol { symbol = (TSymbol)compilation.GetSpecialTypeMember(specialMember); if ((object)symbol == null) { MemberDescriptor descriptor = SpecialMembers.GetDescriptor(specialMember); diagnostics.Add(ErrorCode.ERR_MissingPredefinedMember, syntax.Location, descriptor.DeclaringTypeMetadataName, descriptor.Name); return false; } var useSiteDiagnostic = symbol.GetUseSiteDiagnosticForSymbolOrContainingType(); if (useSiteDiagnostic != null) { Symbol.ReportUseSiteDiagnostic(useSiteDiagnostic, diagnostics, new SourceLocation(syntax)); } return true; } /// /// Reports use-site diagnostics for the specified symbol. /// /// /// True if there was an error among the reported diagnostics /// internal static bool ReportUseSiteDiagnostics(Symbol symbol, DiagnosticBag diagnostics, SyntaxNode node) { DiagnosticInfo info = symbol.GetUseSiteDiagnostic(); return info != null && Symbol.ReportUseSiteDiagnostic(info, diagnostics, node.Location); } internal static bool ReportUseSiteDiagnostics(Symbol symbol, DiagnosticBag diagnostics, SyntaxToken token) { DiagnosticInfo info = symbol.GetUseSiteDiagnostic(); return info != null && Symbol.ReportUseSiteDiagnostic(info, diagnostics, token.GetLocation()); } /// /// Reports use-site diagnostics for the specified symbol. /// /// /// True if there was an error among the reported diagnostics /// internal static bool ReportUseSiteDiagnostics(Symbol symbol, DiagnosticBag diagnostics, Location location) { DiagnosticInfo info = symbol.GetUseSiteDiagnostic(); return info != null && Symbol.ReportUseSiteDiagnostic(info, diagnostics, location); } /// /// This is a layer on top of the Compilation version that generates a diagnostic if the well-known /// type isn't found. /// internal NamedTypeSymbol GetWellKnownType(WellKnownType type, DiagnosticBag diagnostics, SyntaxNode node) { NamedTypeSymbol typeSymbol = this.Compilation.GetWellKnownType(type); Debug.Assert((object)typeSymbol != null, "Expect an error type if well-known type isn't found"); ReportUseSiteDiagnostics(typeSymbol, diagnostics, node); return typeSymbol; } /// /// This is a layer on top of the Compilation version that generates a diagnostic if the well-known /// type isn't found. /// internal NamedTypeSymbol GetWellKnownType(WellKnownType type, ref HashSet useSiteDiagnostics) { NamedTypeSymbol typeSymbol = this.Compilation.GetWellKnownType(type); Debug.Assert((object)typeSymbol != null, "Expect an error type if well-known type isn't found"); HashSetExtensions.InitializeAndAdd(ref useSiteDiagnostics, typeSymbol.GetUseSiteDiagnostic()); return typeSymbol; } /// /// Retrieves a well-known type member and reports diagnostics. /// /// Null if the symbol is missing. internal static Symbol GetWellKnownTypeMember(CSharpCompilation compilation, WellKnownMember member, DiagnosticBag diagnostics, Location location = null, SyntaxNode syntax = null, bool isOptional = false) { Debug.Assert((syntax != null) ^ (location != null)); DiagnosticInfo useSiteDiagnostic; Symbol memberSymbol = GetWellKnownTypeMember(compilation, member, out useSiteDiagnostic, isOptional); if (useSiteDiagnostic != null) { // report the diagnostic only for non-optional members: Symbol.ReportUseSiteDiagnostic(useSiteDiagnostic, diagnostics, location ?? syntax.Location); } return memberSymbol; } internal static Symbol GetWellKnownTypeMember(CSharpCompilation compilation, WellKnownMember member, out DiagnosticInfo diagnosticInfo, bool isOptional = false) { Symbol memberSymbol = compilation.GetWellKnownTypeMember(member); if ((object)memberSymbol != null) { diagnosticInfo = memberSymbol.GetUseSiteDiagnosticForSymbolOrContainingType(); if (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 = diagnosticInfo.Severity; // ignore warnings: diagnosticInfo = null; // if the member is optional and bad for whatever reason ignore it: if (severity == DiagnosticSeverity.Error) { return null; } } } } else if (!isOptional) { // member is missing MemberDescriptor memberDescriptor = WellKnownMembers.GetDescriptor(member); diagnosticInfo = new CSDiagnosticInfo(ErrorCode.ERR_MissingPredefinedMember, memberDescriptor.DeclaringTypeMetadataName, memberDescriptor.Name); } else { diagnosticInfo = null; } return memberSymbol; } private class ConsistentSymbolOrder : IComparer { 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.Locations[0]; Location lb = fst.Locations[0]; 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, DiagnosticBag diagnostics, bool suppressUseSiteDiagnostics, out bool wasError, NamespaceOrTypeSymbol qualifierOpt = null, LookupOptions options = default(LookupOptions)) { 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]; //if names match, arities match, and containing symbols match (recursively), ... if (srcSymbol.ToDisplayString(SymbolDisplayFormat.QualifiedNameArityFormat) == mdSymbol.ToDisplayString(SymbolDisplayFormat.QualifiedNameArityFormat)) { if (srcSymbol.Equals(Compilation.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_NonNullTypesAttribute))) { // Silently prefer the injected symbol return originalSymbols[best.Index]; } object arg0; if (best.IsFromSourceModule) { SyntaxTree tree = srcSymbol.Locations.FirstOrNone().SourceTree; arg0 = tree != null ? (object)tree.FilePath : MessageID.IDS_InjectedDeclaration.Localize(); } else { Debug.Assert(best.IsFromAddedModule); arg0 = srcSymbol.ContainingModule; } 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(originalSymbols[best.Index] != originalSymbols[secondBest.Index] || options.IsAttributeTypeLookup(), "This kind of ambiguity is only possible for attributes."); Debug.Assert(first != second || originalSymbols[best.Index] != originalSymbols[secondBest.Index], "Why does the LookupResult contain the same symbol twice?"); CSDiagnosticInfo info; bool reportError; //if names match, arities match, and containing symbols match (recursively), ... if (first != second && first.ToDisplayString(SymbolDisplayFormat.QualifiedNameArityFormat) == second.ToDisplayString(SymbolDisplayFormat.QualifiedNameArityFormat)) { // 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.Locations.First().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 || originalSymbols[best.Index] != originalSymbols[secondBest.Index], "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) { 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. Emit.NoPia.EmbeddedTypesManager.IsValidEmbeddableType((NamedTypeSymbol)singleResult, where, diagnostics); } if (!suppressUseSiteDiagnostics) { wasError = ReportUseSiteDiagnostics(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++) { ReportUseSiteDiagnostics(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, FromSourceModule, FromAddedModule, FromReferencedAssembly, FromCorLibrary, } [DebuggerDisplay("Location = {_location}, Index = {_index}")] private struct BestSymbolInfo { private readonly BestSymbolLocation _location; private readonly int _index; /// /// Returns -1 if None. /// 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 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; } /// /// Prefers symbols from source module, then from added modules, then from referenced assemblies. /// Returns true if values were swapped. /// 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; } /// /// Returns true if the second is a better location than the first. /// public static bool IsSecondLocationBetter(BestSymbolLocation firstLocation, BestSymbolLocation secondLocation) { Debug.Assert(secondLocation != 0); return (firstLocation == BestSymbolLocation.None) || (firstLocation > secondLocation); } } /// /// Prefer symbols from source module, then from added modules, then from referenced assemblies. /// private BestSymbolInfo GetBestSymbolInfo(ArrayBuilder 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) { 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; } } /// /// 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). /// private CSDiagnosticInfo NotFound(SyntaxNode where, string simpleName, int arity, string whereText, DiagnosticBag 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()) { // just recurse one level, so cheat and OR verbatim name option :) NotFound(where, simpleName, arity, whereText + "Attribute", 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, qualifierOpt) : 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 "". 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, DiagnosticBag diagnostics, Location location) { return Next?.GetForwardedToAssemblyInUsingNamespaces(metadataName, ref qualifierOpt, diagnostics, location); } protected AssemblySymbol GetForwardedToAssembly(string fullName, DiagnosticBag diagnostics, Location location) { var metadataName = MetadataTypeName.FromFullName(fullName); foreach (var referencedAssembly in Compilation.Assembly.Modules[0].GetReferencedAssemblySymbols()) { var forwardedType = referencedAssembly.TryLookupForwardedMetadataType(ref metadataName); 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; } /// /// Look for a type forwarder for the given type in the containing assembly and any referenced assemblies. /// /// The name of the (potentially) forwarded type. /// The arity of the forwarded type. /// 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. /// Will be used to report non-fatal errors during look up. /// Location to report errors on. /// Returns the Assembly to which the type is forwarded, or null if none is found. /// /// 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. /// protected AssemblySymbol GetForwardedToAssembly(string name, int arity, ref NamespaceOrTypeSymbol qualifierOpt, DiagnosticBag 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. if ((this.Flags & BinderFlags.InContextualAttributeBinder) != 0) { var current = this; do { var contextualAttributeBinder = current as ContextualAttributeBinder; if (contextualAttributeBinder != null) { if ((object)contextualAttributeBinder.AttributeTarget != null && contextualAttributeBinder.AttributeTarget.Kind == SymbolKind.Assembly) { return null; } break; } current = current.Next; } while (current != 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. var metadataName = MetadataHelpers.ComposeAritySuffixedMetadataName(name, arity); 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; } internal static bool CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, DiagnosticBag diagnostics, Location locationOpt = null) { return CheckFeatureAvailability(syntax.SyntaxTree, feature, diagnostics, locationOpt ?? syntax.GetLocation()); } internal static bool CheckFeatureAvailability(SyntaxTree tree, MessageID feature, DiagnosticBag diagnostics, Location location) { CSDiagnosticInfo error = GetFeatureAvailabilityDiagnosticInfo(tree, feature); if (error is null) { return true; } diagnostics.Add(new CSDiagnostic(error, location)); return false; } internal static CSDiagnosticInfo GetLanguageVersionDiagnosticInfo(LanguageVersion availableVersion, MessageID feature) { LanguageVersion requiredVersion = feature.RequiredVersion(); if (requiredVersion > availableVersion) { return new CSDiagnosticInfo(availableVersion.GetErrorCode(), feature.Localize(), new CSharpRequiredLanguageVersion(requiredVersion)); } return null; } private static CSDiagnosticInfo GetFeatureAvailabilityDiagnosticInfo(SyntaxTree tree, MessageID feature) { CSharpParseOptions options = (CSharpParseOptions)tree.Options; if (options.IsFeatureEnabled(feature)) { return null; } string requiredFeature = feature.RequiredFeature(); if (requiredFeature != null) { return new CSDiagnosticInfo(ErrorCode.ERR_FeatureIsExperimental, feature.Localize(), requiredFeature); } return GetLanguageVersionDiagnosticInfo(options.LanguageVersion, feature); } } }