|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.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 Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class Binder
{
internal ImmutableArray<Symbol> BindCref(CrefSyntax syntax, out Symbol? ambiguityWinner, BindingDiagnosticBag diagnostics)
{
ImmutableArray<Symbol> symbols = BindCrefInternal(syntax, out ambiguityWinner, diagnostics);
Debug.Assert(!symbols.IsDefault, "Prefer empty to null.");
Debug.Assert((symbols.Length > 1) == ((object?)ambiguityWinner != null), "ambiguityWinner should be set iff more than one symbol is returned.");
return symbols;
}
private ImmutableArray<Symbol> BindCrefInternal(CrefSyntax syntax, out Symbol? ambiguityWinner, BindingDiagnosticBag diagnostics)
{
switch (syntax.Kind())
{
case SyntaxKind.TypeCref:
return BindTypeCref((TypeCrefSyntax)syntax, out ambiguityWinner, diagnostics);
case SyntaxKind.QualifiedCref:
return BindQualifiedCref((QualifiedCrefSyntax)syntax, out ambiguityWinner, diagnostics);
case SyntaxKind.NameMemberCref:
case SyntaxKind.IndexerMemberCref:
case SyntaxKind.OperatorMemberCref:
case SyntaxKind.ConversionOperatorMemberCref:
return BindMemberCref((MemberCrefSyntax)syntax, containerOpt: null, ambiguityWinner: out ambiguityWinner, diagnostics: diagnostics);
default:
throw ExceptionUtilities.UnexpectedValue(syntax.Kind());
}
}
private ImmutableArray<Symbol> BindTypeCref(TypeCrefSyntax syntax, out Symbol? ambiguityWinner, BindingDiagnosticBag diagnostics)
{
NamespaceOrTypeSymbol result = BindNamespaceOrTypeSymbolInCref(syntax.Type);
// NOTE: we don't have to worry about the case where a non-error type is constructed
// with erroneous type arguments, because only MemberCrefs have type arguments -
// all other crefs only have type parameters.
if (result.Kind == SymbolKind.ErrorType)
{
var noTrivia = syntax.WithLeadingTrivia(null).WithTrailingTrivia(null);
diagnostics.Add(ErrorCode.WRN_BadXMLRef, syntax.Location, noTrivia.ToFullString());
}
// We'll never have more than one type, but it is conceivable that result could
// be an ExtendedErrorTypeSymbol with multiple candidates.
ambiguityWinner = null;
return ImmutableArray.Create<Symbol>(result);
}
private ImmutableArray<Symbol> BindQualifiedCref(QualifiedCrefSyntax syntax, out Symbol? ambiguityWinner, BindingDiagnosticBag diagnostics)
{
// NOTE: we won't check whether container is an error type - we'll just let BindMemberCref fail
// and report a blanket diagnostic.
NamespaceOrTypeSymbol container = BindNamespaceOrTypeSymbolInCref(syntax.Container);
return BindMemberCref(syntax.Member, container, out ambiguityWinner, diagnostics);
}
/// <summary>
/// We can't use BindNamespaceOrTypeSymbol, since it doesn't return inaccessible symbols (directly).
/// </summary>
/// <remarks>
/// Guaranteed not to return null.
///
/// CONSIDER: As in dev11, we don't handle ambiguity at this level. Hypothetically,
/// we could just pick one, though an "ideal" solution would probably involve a search
/// down all ambiguous branches.
/// </remarks>
private NamespaceOrTypeSymbol BindNamespaceOrTypeSymbolInCref(TypeSyntax syntax)
{
Debug.Assert(Flags.Includes(BinderFlags.Cref));
// BREAK: Dev11 used to do a second lookup, ignoring accessibility, if the first lookup failed.
// VS BUG#3321137: we need to try to find accessible members first
// especially for compiler generated events (the backing field is private
// but has the same name as the public event, and there is no easy way to
// set the isEvent field on imported MEMBVARSYMs)
// Diagnostics that don't prevent us from getting a symbol don't matter - the caller will report
// an umbrella diagnostic if the result is an error type.
NamespaceOrTypeSymbol namespaceOrTypeSymbol = BindNamespaceOrTypeSymbol(syntax, BindingDiagnosticBag.Discarded).NamespaceOrTypeSymbol;
Debug.Assert((object)namespaceOrTypeSymbol != null);
return namespaceOrTypeSymbol;
}
private ImmutableArray<Symbol> BindMemberCref(MemberCrefSyntax syntax, NamespaceOrTypeSymbol? containerOpt, out Symbol? ambiguityWinner, BindingDiagnosticBag diagnostics)
{
if ((object?)containerOpt != null && containerOpt.Kind == SymbolKind.TypeParameter)
{
// As in normal lookup (see CreateErrorIfLookupOnTypeParameter), you can't dot into a type parameter
// (though you can dot into an expression of type parameter type).
CrefSyntax crefSyntax = GetRootCrefSyntax(syntax);
var noTrivia = syntax.WithLeadingTrivia(null).WithTrailingTrivia(null);
diagnostics.Add(ErrorCode.WRN_BadXMLRef, crefSyntax.Location, noTrivia.ToFullString());
ambiguityWinner = null;
return ImmutableArray<Symbol>.Empty;
}
ImmutableArray<Symbol> result;
switch (syntax.Kind())
{
case SyntaxKind.NameMemberCref:
result = BindNameMemberCref((NameMemberCrefSyntax)syntax, containerOpt, out ambiguityWinner, diagnostics);
break;
case SyntaxKind.IndexerMemberCref:
result = BindIndexerMemberCref((IndexerMemberCrefSyntax)syntax, containerOpt, out ambiguityWinner, diagnostics);
break;
case SyntaxKind.OperatorMemberCref:
result = BindOperatorMemberCref((OperatorMemberCrefSyntax)syntax, containerOpt, out ambiguityWinner, diagnostics);
break;
case SyntaxKind.ConversionOperatorMemberCref:
result = BindConversionOperatorMemberCref((ConversionOperatorMemberCrefSyntax)syntax, containerOpt, out ambiguityWinner, diagnostics);
break;
default:
throw ExceptionUtilities.UnexpectedValue(syntax.Kind());
}
if (!result.Any())
{
CrefSyntax crefSyntax = GetRootCrefSyntax(syntax);
var noTrivia = syntax.WithLeadingTrivia(null).WithTrailingTrivia(null);
diagnostics.Add(ErrorCode.WRN_BadXMLRef, crefSyntax.Location, noTrivia.ToFullString());
}
return result;
}
private ImmutableArray<Symbol> BindNameMemberCref(NameMemberCrefSyntax syntax, NamespaceOrTypeSymbol? containerOpt, out Symbol? ambiguityWinner, BindingDiagnosticBag diagnostics)
{
SimpleNameSyntax? nameSyntax = syntax.Name as SimpleNameSyntax;
int arity;
string memberName;
string memberNameText;
if (nameSyntax != null)
{
arity = nameSyntax.Arity;
memberName = nameSyntax.Identifier.ValueText;
memberNameText = nameSyntax.Identifier.Text;
}
else
{
// If the name isn't a SimpleNameSyntax, then we must have a type name followed by a parameter list.
// Thus, we're looking for a constructor.
Debug.Assert((object?)containerOpt == null);
// Could be an error type, but we'll just lookup fail below.
containerOpt = BindNamespaceOrTypeSymbolInCref(syntax.Name);
arity = 0;
memberName = memberNameText = WellKnownMemberNames.InstanceConstructorName;
}
if (string.IsNullOrEmpty(memberName))
{
ambiguityWinner = null;
return ImmutableArray<Symbol>.Empty;
}
ImmutableArray<Symbol> sortedSymbols = ComputeSortedCrefMembers(syntax, containerOpt, memberName, memberNameText, arity, syntax.Parameters != null, diagnostics);
if (sortedSymbols.IsEmpty)
{
ambiguityWinner = null;
return ImmutableArray<Symbol>.Empty;
}
return ProcessCrefMemberLookupResults(
sortedSymbols,
arity,
syntax,
typeArgumentListSyntax: arity == 0 ? null : ((GenericNameSyntax)nameSyntax!).TypeArgumentList,
parameterListSyntax: syntax.Parameters,
ambiguityWinner: out ambiguityWinner,
diagnostics: diagnostics);
}
private ImmutableArray<Symbol> BindIndexerMemberCref(IndexerMemberCrefSyntax syntax, NamespaceOrTypeSymbol? containerOpt, out Symbol? ambiguityWinner, BindingDiagnosticBag diagnostics)
{
const int arity = 0;
ImmutableArray<Symbol> sortedSymbols = ComputeSortedCrefMembers(syntax, containerOpt, WellKnownMemberNames.Indexer, memberNameText: WellKnownMemberNames.Indexer, arity, syntax.Parameters != null, diagnostics);
if (sortedSymbols.IsEmpty)
{
ambiguityWinner = null;
return ImmutableArray<Symbol>.Empty;
}
// Since only indexers are named WellKnownMemberNames.Indexer.
Debug.Assert(sortedSymbols.All(SymbolExtensions.IsIndexer));
// NOTE: guaranteed to be a property, because only indexers are considered.
return ProcessCrefMemberLookupResults(
sortedSymbols,
arity,
syntax,
typeArgumentListSyntax: null,
parameterListSyntax: syntax.Parameters,
ambiguityWinner: out ambiguityWinner,
diagnostics: diagnostics);
}
// NOTE: not guaranteed to be a method (e.g. class op_Addition)
// NOTE: constructor fallback logic applies
private ImmutableArray<Symbol> BindOperatorMemberCref(OperatorMemberCrefSyntax syntax, NamespaceOrTypeSymbol? containerOpt, out Symbol? ambiguityWinner, BindingDiagnosticBag diagnostics)
{
const int arity = 0;
CrefParameterListSyntax? parameterListSyntax = syntax.Parameters;
bool isChecked = syntax.CheckedKeyword.IsKind(SyntaxKind.CheckedKeyword);
// NOTE: Prefer binary to unary, unless there is exactly one parameter.
// CONSIDER: we're following dev11 by never using a binary operator name if there's
// exactly one parameter, but doing so would allow us to match single-parameter constructors.
SyntaxKind operatorTokenKind = syntax.OperatorToken.Kind();
string? memberName = parameterListSyntax != null && parameterListSyntax.Parameters.Count == 1
? null
: OperatorFacts.BinaryOperatorNameFromSyntaxKindIfAny(operatorTokenKind, isChecked);
memberName = memberName ?? OperatorFacts.UnaryOperatorNameFromSyntaxKindIfAny(operatorTokenKind, isChecked: isChecked);
if (memberName == null ||
(isChecked && !syntax.OperatorToken.IsMissing && !SyntaxFacts.IsCheckedOperator(memberName))) // the operator cannot be checked
{
ambiguityWinner = null;
return ImmutableArray<Symbol>.Empty;
}
ImmutableArray<Symbol> sortedSymbols = ComputeSortedCrefMembers(syntax, containerOpt, memberName, memberNameText: memberName, arity, syntax.Parameters != null, diagnostics);
if (sortedSymbols.IsEmpty)
{
ambiguityWinner = null;
return ImmutableArray<Symbol>.Empty;
}
return ProcessCrefMemberLookupResults(
sortedSymbols,
arity,
syntax,
typeArgumentListSyntax: null,
parameterListSyntax: parameterListSyntax,
ambiguityWinner: out ambiguityWinner,
diagnostics: diagnostics);
}
// NOTE: not guaranteed to be a method (e.g. class op_Implicit)
private ImmutableArray<Symbol> BindConversionOperatorMemberCref(ConversionOperatorMemberCrefSyntax syntax, NamespaceOrTypeSymbol? containerOpt, out Symbol? ambiguityWinner, BindingDiagnosticBag diagnostics)
{
const int arity = 0;
bool isChecked = syntax.CheckedKeyword.IsKind(SyntaxKind.CheckedKeyword);
string memberName;
if (syntax.ImplicitOrExplicitKeyword.Kind() == SyntaxKind.ImplicitKeyword)
{
if (isChecked)
{
// checked form is not supported
ambiguityWinner = null;
return ImmutableArray<Symbol>.Empty;
}
memberName = WellKnownMemberNames.ImplicitConversionName;
}
else if (isChecked)
{
memberName = WellKnownMemberNames.CheckedExplicitConversionName;
}
else
{
memberName = WellKnownMemberNames.ExplicitConversionName;
}
ImmutableArray<Symbol> sortedSymbols = ComputeSortedCrefMembers(syntax, containerOpt, memberName, memberNameText: memberName, arity, syntax.Parameters != null, diagnostics);
if (sortedSymbols.IsEmpty)
{
ambiguityWinner = null;
return ImmutableArray<Symbol>.Empty;
}
TypeSymbol returnType = BindCrefParameterOrReturnType(syntax.Type, syntax, diagnostics);
// Filter out methods with the wrong return type, since overload resolution won't catch these.
sortedSymbols = sortedSymbols.WhereAsArray((symbol, returnType) =>
symbol.Kind != SymbolKind.Method || TypeSymbol.Equals(((MethodSymbol)symbol).ReturnType, returnType, TypeCompareKind.ConsiderEverything2), returnType);
if (!sortedSymbols.Any())
{
ambiguityWinner = null;
return ImmutableArray<Symbol>.Empty;
}
return ProcessCrefMemberLookupResults(
sortedSymbols,
arity,
syntax,
typeArgumentListSyntax: null,
parameterListSyntax: syntax.Parameters,
ambiguityWinner: out ambiguityWinner,
diagnostics: diagnostics);
}
/// <summary>
/// Perform lookup (optionally, in a specified container). If nothing is found and the member name matches the containing type
/// name, then use the instance constructors of the type instead. The resulting symbols are sorted since tie-breaking is based
/// on order and we want cref binding to be repeatable.
/// </summary>
/// <remarks>
/// Never returns null.
/// </remarks>
private ImmutableArray<Symbol> ComputeSortedCrefMembers(CSharpSyntaxNode syntax, NamespaceOrTypeSymbol? containerOpt, string memberName, string memberNameText, int arity, bool hasParameterList, BindingDiagnosticBag diagnostics)
{
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
var result = ComputeSortedCrefMembers(containerOpt, memberName, memberNameText, arity, hasParameterList, syntax, diagnostics, ref useSiteInfo);
diagnostics.Add(syntax, useSiteInfo);
return result;
}
private ImmutableArray<Symbol> ComputeSortedCrefMembers(NamespaceOrTypeSymbol? containerOpt, string memberName, string memberNameText, int arity, bool hasParameterList, CSharpSyntaxNode syntax,
BindingDiagnosticBag diagnostics, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
// Since we may find symbols without going through the lookup API,
// expose the symbols via an ArrayBuilder.
ArrayBuilder<Symbol> builder;
{
LookupResult result = LookupResult.GetInstance();
this.LookupSymbolsOrMembersInternal(
result,
containerOpt,
name: memberName,
arity: arity,
basesBeingResolved: null,
options: LookupOptions.AllMethodsOnArityZero | LookupOptions.MustNotBeParameter,
diagnose: false,
useSiteInfo: ref useSiteInfo);
// CONSIDER: Dev11 also checks for a constructor in the event of an ambiguous result.
if (result.IsMultiViable)
{
// Dev11 doesn't consider members from System.Object when the container is an interface.
// Lookup should already have dropped such members.
builder = ArrayBuilder<Symbol>.GetInstance();
builder.AddRange(result.Symbols);
result.Free();
}
else if (memberNameText is "nint" or "nuint"
&& containerOpt is null
&& arity == 0
&& !hasParameterList)
{
result.Free(); // Won't be using this.
Debug.Assert(memberName == memberNameText);
CheckFeatureAvailability(syntax, MessageID.IDS_FeatureNativeInt, diagnostics);
builder = ArrayBuilder<Symbol>.GetInstance();
builder.Add(this.GetSpecialType(memberName == "nint" ? SpecialType.System_IntPtr : SpecialType.System_UIntPtr, diagnostics, syntax).AsNativeInteger());
}
else
{
result.Free(); // Won't be using this.
// Dev11 has a complicated two-stage process for determining when a cref is really referring to a constructor.
// Under two sets of conditions, XmlDocCommentBinder::bindXMLReferenceName will decide that a name refers
// to a constructor and under one set of conditions, the calling method, XmlDocCommentBinder::bindXMLReference,
// will roll back that decision and return null.
// In XmlDocCommentBinder::bindXMLReferenceName:
// 1) If an unqualified, non-generic name didn't bind to anything and the name matches the name of the type
// to which the doc comment is applied, then bind to a constructor.
// 2) If a qualified, non-generic name didn't bind to anything and the LHS of the qualified name is a type
// with the same name, then bind to a constructor.
// Quoted from XmlDocCommentBinder::bindXMLReference:
// Filtering out the case where specifying the name of a generic type without specifying
// any arity returns a constructor. This case shouldn't return anything. Note that
// returning the constructors was a fix for the wonky constructor behavior, but in order
// to not introduce a regression and breaking change we return NULL in this case.
// e.g.
//
// /// <see cref="Goo"/>
// class Goo<T> { }
//
// This cref used not to bind to anything, because before it was looking for a type and
// since there was no arity, it didn't find Goo<T>. Now however, it finds Goo<T>.ctor,
// which is arguably correct, but would be a breaking change (albeit with minimal impact)
// so we catch this case and chuck out the symbol found.
// In Roslyn, we're doing everything in one pass, rather than guessing and rolling back.
// As in the native compiler, we treat this as a fallback case - something that actually has the
// specified name is preferred.
NamedTypeSymbol? constructorType = null;
if (arity == 0) // Member arity
{
NamedTypeSymbol? containerType = containerOpt as NamedTypeSymbol;
if ((object?)containerType != null)
{
// Case 1: If the name is qualified by a type with the same name, then we want a
// constructor (unless the type is generic, the cref is on/in the type (but not
// on/in a nested type), and there were no parens after the member name).
if (containerType.Name == memberName && (hasParameterList || containerType.Arity == 0 || !TypeSymbol.Equals(this.ContainingType, containerType.OriginalDefinition, TypeCompareKind.ConsiderEverything2)))
{
constructorType = containerType;
}
}
else if ((object?)containerOpt == null && hasParameterList)
{
// Case 2: If the name is not qualified by anything, but we're in the scope
// of a type with the same name (regardless of arity), then we want a constructor,
// as long as there were parens after the member name.
NamedTypeSymbol? binderContainingType = this.ContainingType;
if ((object?)binderContainingType != null && memberName == binderContainingType.Name)
{
constructorType = binderContainingType;
}
}
}
if ((object?)constructorType != null)
{
ImmutableArray<MethodSymbol> instanceConstructors = constructorType.InstanceConstructors;
int numInstanceConstructors = instanceConstructors.Length;
if (numInstanceConstructors == 0)
{
return ImmutableArray<Symbol>.Empty;
}
builder = ArrayBuilder<Symbol>.GetInstance(numInstanceConstructors);
builder.AddRange(instanceConstructors);
}
else
{
return ImmutableArray<Symbol>.Empty;
}
}
}
Debug.Assert(builder != null);
// Since we resolve ambiguities by just picking the first symbol we encounter,
// the order of the symbols matters for repeatability.
if (builder.Count > 1)
{
builder.Sort(ConsistentSymbolOrder.Instance);
}
return builder.ToImmutableAndFree();
}
/// <summary>
/// Given a list of viable lookup results (based on the name, arity, and containing symbol),
/// attempt to select one.
/// </summary>
private ImmutableArray<Symbol> ProcessCrefMemberLookupResults(
ImmutableArray<Symbol> symbols,
int arity,
MemberCrefSyntax memberSyntax,
TypeArgumentListSyntax? typeArgumentListSyntax,
BaseCrefParameterListSyntax? parameterListSyntax,
out Symbol? ambiguityWinner,
BindingDiagnosticBag diagnostics)
{
Debug.Assert(!symbols.IsEmpty);
if (parameterListSyntax == null)
{
return ProcessParameterlessCrefMemberLookupResults(symbols, arity, memberSyntax, typeArgumentListSyntax, out ambiguityWinner, diagnostics);
}
ArrayBuilder<Symbol> candidates = ArrayBuilder<Symbol>.GetInstance();
GetCrefOverloadResolutionCandidates(symbols, arity, typeArgumentListSyntax, candidates);
ImmutableArray<ParameterSymbol> parameterSymbols = BindCrefParameters(parameterListSyntax, diagnostics);
ImmutableArray<Symbol> results = PerformCrefOverloadResolution(candidates, parameterSymbols, arity, memberSyntax, out ambiguityWinner, diagnostics);
candidates.Free();
// NOTE: This diagnostic is just a hint that might help fix a broken cref, so don't do
// any work unless there are no viable candidates.
if (results.Length == 0)
{
for (int i = 0; i < parameterSymbols.Length; i++)
{
if (ContainsNestedTypeOfUnconstructedGenericType(parameterSymbols[i].Type))
{
// This warning is new in Roslyn, because our better-defined semantics for
// cref lookup disallow some things that were possible in dev12.
//
// Consider the following code:
//
// public class C<T>
// {
// public class Inner { }
//
// public void M(Inner i) { }
//
// /// <see cref="M"/>
// /// <see cref="C{T}.M"/>
// /// <see cref="C{Q}.M"/>
// /// <see cref="C{Q}.M(C{Q}.Inner)"/>
// /// <see cref="C{Q}.M(Inner)"/> // WRN_UnqualifiedNestedTypeInCref
// public void N() { }
// }
//
// Dev12 binds all of the crefs as "M:C`1.M(C{`0}.Inner)".
// Roslyn accepts all but the last. The issue is that the context for performing
// the lookup is not C<Q>, but C<T>. Consequently, Inner binds to C<T>.Inner and
// then overload resolution fails because C<T>.Inner does not match C<Q>.Inner,
// the parameter type of C<Q>.M. Since we could not agree that the old behavior
// was desirable (other than for backwards compatibility) and since mimicking it
// would have been expensive, we settled on introducing a new warning that at
// least hints to the user how then can work around the issue (i.e. by qualifying
// Inner as C{Q}.Inner). Additional details are available in DevDiv #743425.
//
// CONSIDER: We could actually put the qualified form in the warning message,
// but that would probably just make it more frustrating (i.e. if the compiler
// knows exactly what I mean, why do I have to type it).
//
// NOTE: This is not a great location (whole parameter instead of problematic type),
// but it's better than nothing.
diagnostics.Add(ErrorCode.WRN_UnqualifiedNestedTypeInCref, parameterListSyntax.Parameters[i].Location);
break;
}
}
}
return results;
}
private static bool ContainsNestedTypeOfUnconstructedGenericType(TypeSymbol type)
{
switch (type.TypeKind)
{
case TypeKind.Array:
return ContainsNestedTypeOfUnconstructedGenericType(((ArrayTypeSymbol)type).ElementType);
case TypeKind.Pointer:
return ContainsNestedTypeOfUnconstructedGenericType(((PointerTypeSymbol)type).PointedAtType);
case TypeKind.FunctionPointer:
MethodSymbol signature = ((FunctionPointerTypeSymbol)type).Signature;
if (ContainsNestedTypeOfUnconstructedGenericType(signature.ReturnType))
{
return true;
}
foreach (var param in signature.Parameters)
{
if (ContainsNestedTypeOfUnconstructedGenericType(param.Type))
{
return true;
}
}
return false;
case TypeKind.Delegate:
case TypeKind.Class:
case TypeKind.Interface:
case TypeKind.Struct:
case TypeKind.Enum:
case TypeKind.Error:
NamedTypeSymbol namedType = (NamedTypeSymbol)type;
if (IsNestedTypeOfUnconstructedGenericType(namedType))
{
return true;
}
foreach (TypeWithAnnotations typeArgument in namedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics)
{
if (ContainsNestedTypeOfUnconstructedGenericType(typeArgument.Type))
{
return true;
}
}
return false;
case TypeKind.Dynamic:
case TypeKind.TypeParameter:
return false;
default:
throw ExceptionUtilities.UnexpectedValue(type.TypeKind);
}
}
private static bool IsNestedTypeOfUnconstructedGenericType(NamedTypeSymbol type)
{
NamedTypeSymbol containing = type.ContainingType;
while ((object)containing != null)
{
if (containing.Arity > 0 && containing.IsDefinition)
{
return true;
}
containing = containing.ContainingType;
}
return false;
}
/// <summary>
/// At this point, we have a list of viable symbols and no parameter list with which to perform
/// overload resolution. We'll just return the first symbol, giving a diagnostic if there are
/// others.
/// Caveat: If there are multiple candidates and only one is from source, then the source symbol
/// wins and no diagnostic is reported.
/// </summary>
private ImmutableArray<Symbol> ProcessParameterlessCrefMemberLookupResults(
ImmutableArray<Symbol> symbols,
int arity,
MemberCrefSyntax memberSyntax,
TypeArgumentListSyntax? typeArgumentListSyntax,
out Symbol? ambiguityWinner,
BindingDiagnosticBag diagnostics)
{
// If the syntax indicates arity zero, then we match methods of any arity.
// However, if there are both generic and non-generic methods, then the
// generic methods should be ignored.
if (symbols.Length > 1 && arity == 0)
{
bool hasNonGenericMethod = false;
bool hasGenericMethod = false;
foreach (Symbol s in symbols)
{
if (s.Kind != SymbolKind.Method)
{
continue;
}
if (((MethodSymbol)s).Arity == 0)
{
hasNonGenericMethod = true;
}
else
{
hasGenericMethod = true;
}
if (hasGenericMethod && hasNonGenericMethod)
{
break; //Nothing else to be learned.
}
}
if (hasNonGenericMethod && hasGenericMethod)
{
symbols = symbols.WhereAsArray(s =>
s.Kind != SymbolKind.Method || ((MethodSymbol)s).Arity == 0);
}
}
Debug.Assert(!symbols.IsEmpty);
Symbol symbol = symbols[0];
// If there's ambiguity, prefer source symbols.
// Logic is similar to ResultSymbol, but separate because the error handling is totally different.
if (symbols.Length > 1)
{
// Size is known, but IndexOfSymbolFromCurrentCompilation expects a builder.
ArrayBuilder<Symbol> unwrappedSymbols = ArrayBuilder<Symbol>.GetInstance(symbols.Length);
foreach (Symbol wrapped in symbols)
{
unwrappedSymbols.Add(UnwrapAliasNoDiagnostics(wrapped));
}
BestSymbolInfo secondBest;
BestSymbolInfo best = GetBestSymbolInfo(unwrappedSymbols, out secondBest);
Debug.Assert(!best.IsNone);
Debug.Assert(!secondBest.IsNone);
unwrappedSymbols.Free();
int symbolIndex = 0;
if (best.IsFromCompilation)
{
symbolIndex = best.Index;
symbol = symbols[symbolIndex]; // NOTE: symbols, not unwrappedSymbols.
}
if (symbol.Kind == SymbolKind.TypeParameter)
{
CrefSyntax crefSyntax = GetRootCrefSyntax(memberSyntax);
diagnostics.Add(ErrorCode.WRN_BadXMLRefTypeVar, crefSyntax.Location, crefSyntax.ToString());
}
else if (secondBest.IsFromCompilation == best.IsFromCompilation)
{
CrefSyntax crefSyntax = GetRootCrefSyntax(memberSyntax);
int otherIndex = symbolIndex == 0 ? 1 : 0;
diagnostics.Add(ErrorCode.WRN_AmbiguousXMLReference, crefSyntax.Location, crefSyntax.ToString(), symbol, symbols[otherIndex]);
ambiguityWinner = ConstructWithCrefTypeParameters(arity, typeArgumentListSyntax, symbol);
return symbols.SelectAsArray(sym => ConstructWithCrefTypeParameters(arity, typeArgumentListSyntax, sym));
}
}
else if (symbol.Kind == SymbolKind.TypeParameter)
{
CrefSyntax crefSyntax = GetRootCrefSyntax(memberSyntax);
diagnostics.Add(ErrorCode.WRN_BadXMLRefTypeVar, crefSyntax.Location, crefSyntax.ToString());
}
ambiguityWinner = null;
return ImmutableArray.Create<Symbol>(ConstructWithCrefTypeParameters(arity, typeArgumentListSyntax, symbol));
}
/// <summary>
/// Replace any named type in the symbol list with its instance constructors.
/// Construct all candidates with the implicitly-declared CrefTypeParameterSymbols.
/// </summary>
private void GetCrefOverloadResolutionCandidates(ImmutableArray<Symbol> symbols, int arity, TypeArgumentListSyntax? typeArgumentListSyntax, ArrayBuilder<Symbol> candidates)
{
foreach (Symbol candidate in symbols)
{
Symbol constructedCandidate = ConstructWithCrefTypeParameters(arity, typeArgumentListSyntax, candidate);
NamedTypeSymbol? constructedCandidateType = constructedCandidate as NamedTypeSymbol;
if ((object?)constructedCandidateType == null)
{
// Construct before overload resolution so the signatures will match.
candidates.Add(constructedCandidate);
}
else
{
candidates.AddRange(constructedCandidateType.InstanceConstructors);
}
}
}
/// <summary>
/// Given a list of method and/or property candidates, choose the first one (if any) with a signature
/// that matches the parameter list in the cref. Return null if there isn't one.
/// </summary>
/// <remarks>
/// Produces a diagnostic for ambiguous matches, but not for unresolved members - WRN_BadXMLRef is
/// handled in BindMemberCref.
/// </remarks>
private static ImmutableArray<Symbol> PerformCrefOverloadResolution(ArrayBuilder<Symbol> candidates, ImmutableArray<ParameterSymbol> parameterSymbols, int arity, MemberCrefSyntax memberSyntax, out Symbol? ambiguityWinner, BindingDiagnosticBag diagnostics)
{
ArrayBuilder<Symbol>? viable = null;
foreach (Symbol candidate in candidates)
{
// BREAK: In dev11, any candidate with the type "dynamic" anywhere in its parameter list would be skipped
// (see XmlDocCommentBinder::bindXmlReference). Apparently, this was because "the params that the xml doc
// comments produce never will." This does not appear to have made sense in dev11 (skipping dropping the
// candidate doesn't cause anything to blow up and may cause resolution to start succeeding) and it almost
// certainly does not in roslyn (the signature comparer ignores the object-dynamic distinction anyway).
Symbol signatureMember;
switch (candidate.Kind)
{
case SymbolKind.Method:
{
MethodSymbol candidateMethod = (MethodSymbol)candidate;
MethodKind candidateMethodKind = candidateMethod.MethodKind;
bool candidateMethodIsVararg = candidateMethod.IsVararg;
// If the arity from the cref is zero, then we accept methods of any arity.
int signatureMemberArity = candidateMethodKind == MethodKind.Constructor
? 0
: (arity == 0 ? candidateMethod.Arity : arity);
// CONSIDER: we might want to reuse this method symbol (as long as the MethodKind and Vararg-ness match).
signatureMember = new SignatureOnlyMethodSymbol(
methodKind: candidateMethodKind,
typeParameters: IndexedTypeParameterSymbol.TakeSymbols(signatureMemberArity),
parameters: parameterSymbols,
// This specific comparer only looks for varargs.
callingConvention: candidateMethodIsVararg ? Microsoft.Cci.CallingConvention.ExtraArguments : Microsoft.Cci.CallingConvention.HasThis,
// These are ignored by this specific MemberSignatureComparer.
containingType: null,
name: null,
refKind: RefKind.None,
isInitOnly: false,
isStatic: false,
returnType: default,
refCustomModifiers: ImmutableArray<CustomModifier>.Empty,
explicitInterfaceImplementations: ImmutableArray<MethodSymbol>.Empty);
break;
}
case SymbolKind.Property:
{
// CONSIDER: we might want to reuse this property symbol.
signatureMember = new SignatureOnlyPropertySymbol(
parameters: parameterSymbols,
// These are ignored by this specific MemberSignatureComparer.
containingType: null,
name: null,
refKind: RefKind.None,
type: default,
refCustomModifiers: ImmutableArray<CustomModifier>.Empty,
isStatic: false,
explicitInterfaceImplementations: ImmutableArray<PropertySymbol>.Empty);
break;
}
case SymbolKind.NamedType:
// Because we replaced them with constructors when we built the candidate list.
throw ExceptionUtilities.UnexpectedValue(candidate.Kind);
default:
continue;
}
if (MemberSignatureComparer.CrefComparer.Equals(signatureMember, candidate))
{
Debug.Assert(candidate.GetMemberArity() != 0 || candidate.Name == WellKnownMemberNames.InstanceConstructorName || arity == 0,
"Can only have a 0-arity, non-constructor candidate if the desired arity is 0.");
if (viable == null)
{
viable = ArrayBuilder<Symbol>.GetInstance();
viable.Add(candidate);
}
else
{
bool oldArityIsZero = viable[0].GetMemberArity() == 0;
bool newArityIsZero = candidate.GetMemberArity() == 0;
// If the cref specified arity 0 and the current candidate has arity 0 but the previous
// match did not, then the current candidate is the unambiguous winner (unless there's
// another match with arity 0 in a subsequent iteration).
if (!oldArityIsZero || newArityIsZero)
{
if (!oldArityIsZero && newArityIsZero)
{
viable.Clear();
}
viable.Add(candidate);
}
}
}
}
if (viable == null)
{
ambiguityWinner = null;
return ImmutableArray<Symbol>.Empty;
}
if (viable.Count > 1)
{
ambiguityWinner = viable[0];
CrefSyntax crefSyntax = GetRootCrefSyntax(memberSyntax);
diagnostics.Add(ErrorCode.WRN_AmbiguousXMLReference, crefSyntax.Location, crefSyntax.ToString(), ambiguityWinner, viable[1]);
}
else
{
ambiguityWinner = null;
}
return viable.ToImmutableAndFree();
}
/// <summary>
/// If the member is generic, construct it with the CrefTypeParameterSymbols that should be in scope.
/// </summary>
private Symbol ConstructWithCrefTypeParameters(int arity, TypeArgumentListSyntax? typeArgumentListSyntax, Symbol symbol)
{
if (arity > 0)
{
Debug.Assert(typeArgumentListSyntax is object);
SeparatedSyntaxList<TypeSyntax> typeArgumentSyntaxes = typeArgumentListSyntax.Arguments;
var typeArgumentsWithAnnotations = ArrayBuilder<TypeWithAnnotations>.GetInstance(arity);
var unusedDiagnostics =
#if DEBUG
BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false);
Debug.Assert(unusedDiagnostics.DiagnosticBag is object);
#else
BindingDiagnosticBag.Discarded;
#endif
for (int i = 0; i < arity; i++)
{
TypeSyntax typeArgumentSyntax = typeArgumentSyntaxes[i];
var typeArgument = BindType(typeArgumentSyntax, unusedDiagnostics);
typeArgumentsWithAnnotations.Add(typeArgument);
// Should be in a WithCrefTypeParametersBinder.
Debug.Assert(typeArgumentSyntax.ContainsDiagnostics || !typeArgumentSyntax.SyntaxTree.ReportDocumentationCommentDiagnostics() ||
(!unusedDiagnostics.HasAnyErrors() && typeArgument.Type is CrefTypeParameterSymbol));
#if DEBUG
unusedDiagnostics.DiagnosticBag.Clear();
#endif
}
#if DEBUG
unusedDiagnostics.Free();
#endif
if (symbol.Kind == SymbolKind.Method)
{
symbol = ((MethodSymbol)symbol).Construct(typeArgumentsWithAnnotations.ToImmutableAndFree());
}
else
{
Debug.Assert(symbol is NamedTypeSymbol);
symbol = ((NamedTypeSymbol)symbol).Construct(typeArgumentsWithAnnotations.ToImmutableAndFree());
}
}
return symbol;
}
private ImmutableArray<ParameterSymbol> BindCrefParameters(BaseCrefParameterListSyntax parameterListSyntax, BindingDiagnosticBag diagnostics)
{
ArrayBuilder<ParameterSymbol> parameterBuilder = ArrayBuilder<ParameterSymbol>.GetInstance(parameterListSyntax.Parameters.Count);
foreach (CrefParameterSyntax parameter in parameterListSyntax.Parameters)
{
RefKind refKind = parameter.RefKindKeyword.Kind().GetRefKind();
if (refKind == RefKind.Ref && parameter.ReadOnlyKeyword.IsKind(SyntaxKind.ReadOnlyKeyword))
{
CheckFeatureAvailability(parameter.ReadOnlyKeyword, MessageID.IDS_FeatureRefReadonlyParameters, diagnostics, forceWarning: true);
refKind = RefKind.RefReadOnlyParameter;
}
Debug.Assert(parameterListSyntax.Parent is object);
TypeSymbol type = BindCrefParameterOrReturnType(parameter.Type, (MemberCrefSyntax)parameterListSyntax.Parent, diagnostics);
parameterBuilder.Add(new SignatureOnlyParameterSymbol(TypeWithAnnotations.Create(type), ImmutableArray<CustomModifier>.Empty, isParamsArray: false, isParamsCollection: false, refKind: refKind));
}
return parameterBuilder.ToImmutableAndFree();
}
/// <remarks>
/// Keep in sync with CSharpSemanticModel.GetSpeculativelyBoundExpressionWithoutNullability.
/// </remarks>
private TypeSymbol BindCrefParameterOrReturnType(TypeSyntax typeSyntax, MemberCrefSyntax memberCrefSyntax, BindingDiagnosticBag diagnostics)
{
// After much deliberation, we eventually decided to suppress lookup of inherited members within
// crefs, in order to match dev11's behavior (Changeset #829014). Unfortunately, it turns out
// that dev11 does not suppress these members when performing lookup within parameter and return
// types, within crefs (DevDiv #586815, #598371).
Debug.Assert(InCrefButNotParameterOrReturnType);
Binder parameterOrReturnTypeBinder = this.WithAdditionalFlags(BinderFlags.CrefParameterOrReturnType);
// It would be nice to pull this binder out of the factory so we wouldn't have to worry about them getting out
// of sync, but this code is also used for included crefs, which don't have BinderFactories.
// As a compromise, we'll assert that the binding locations match in scenarios where we can go through the factory.
Debug.Assert(!this.Compilation.ContainsSyntaxTree(typeSyntax.SyntaxTree) ||
this.Compilation.GetBinderFactory(typeSyntax.SyntaxTree).GetBinder(typeSyntax).Flags ==
(parameterOrReturnTypeBinder.Flags & ~BinderFlags.SemanticModel));
var localDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, // Examined, but not reported.
withDependencies: diagnostics.AccumulatesDependencies);
Debug.Assert(localDiagnostics.DiagnosticBag is object);
TypeSymbol type = parameterOrReturnTypeBinder.BindType(typeSyntax, localDiagnostics).Type;
if (localDiagnostics.HasAnyErrors())
{
if (HasNonObsoleteError(localDiagnostics.DiagnosticBag))
{
Debug.Assert(typeSyntax.Parent is object);
CrefSyntax crefSyntax = GetRootCrefSyntax(memberCrefSyntax);
if (typeSyntax.Parent.Kind() == SyntaxKind.ConversionOperatorMemberCref)
{
diagnostics.Add(ErrorCode.WRN_BadXMLRefReturnType, typeSyntax.Location);
}
else
{
diagnostics.Add(ErrorCode.WRN_BadXMLRefParamType, typeSyntax.Location, typeSyntax.ToString(), crefSyntax.ToString());
}
}
}
else
{
Debug.Assert(type.TypeKind != TypeKind.Error || typeSyntax.ContainsDiagnostics || !typeSyntax.SyntaxTree.ReportDocumentationCommentDiagnostics(), "Why wasn't there a diagnostic?");
}
diagnostics.AddDependencies(localDiagnostics);
localDiagnostics.Free();
return type;
}
private static bool HasNonObsoleteError(DiagnosticBag unusedDiagnostics)
{
foreach (Diagnostic diag in unusedDiagnostics.AsEnumerable())
{
// CONSIDER: If this check is too slow, we could add a helper to DiagnosticBag
// that checks for unrealized diagnostics without expanding them.
switch ((ErrorCode)diag.Code)
{
case ErrorCode.ERR_DeprecatedSymbolStr:
case ErrorCode.ERR_DeprecatedCollectionInitAddStr:
break;
default:
if (diag.Severity == DiagnosticSeverity.Error)
{
return true;
}
break;
}
}
return false;
}
private static CrefSyntax GetRootCrefSyntax(MemberCrefSyntax syntax)
{
SyntaxNode? parentSyntax = syntax.Parent; // Could be null when speculating.
return parentSyntax == null || parentSyntax.IsKind(SyntaxKind.XmlCrefAttribute)
? syntax
: (CrefSyntax)parentSyntax;
}
}
}
|