Skip to content

Commit af2c09b

Browse files
committed
Extensions: check inferrability
1 parent 39479f2 commit af2c09b

21 files changed

+267
-64
lines changed

src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7854,7 +7854,7 @@ BoundExpression tryBindMemberAccessWithBoundNamespaceLeft(
78547854
Error(diagnostics, lookupResult.Error, right);
78557855

78567856
return new BoundTypeExpression(node, null,
7857-
new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(symbols[0]), symbols.ToImmutable(), lookupResult.Kind, lookupResult.Error, rightArity));
7857+
new ExtendedErrorTypeSymbol(GetContainingNamespaceOrNonExtensionType(symbols[0]), symbols.ToImmutable(), lookupResult.Kind, lookupResult.Error, rightArity));
78587858
}
78597859
else if (lookupResult.Kind == LookupResultKind.Empty)
78607860
{

src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -338,16 +338,24 @@ internal NamespaceOrTypeOrAliasSymbolWithAnnotations BindTypeOrAlias(ExpressionS
338338
}
339339

340340
var diagnosticInfo = diagnostics.Add(ErrorCode.ERR_BadSKknown, syntax.Location, syntax, symbol.Symbol.GetKindText(), MessageID.IDS_SK_TYPE.Localize());
341-
return TypeWithAnnotations.Create(new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(symbol.Symbol), symbol.Symbol, LookupResultKind.NotATypeOrNamespace, diagnosticInfo));
341+
return TypeWithAnnotations.Create(new ExtendedErrorTypeSymbol(GetContainingNamespaceOrNonExtensionType(symbol.Symbol), symbol.Symbol, LookupResultKind.NotATypeOrNamespace, diagnosticInfo));
342342
}
343343

344344
/// <summary>
345345
/// The immediately containing namespace or named type, or the global
346346
/// namespace if containing symbol is neither a namespace or named type.
347+
/// We don't want to use an extension declaration as the containing type
348+
/// for error type symbols, as that causes cycles.
347349
/// </summary>
348-
private NamespaceOrTypeSymbol GetContainingNamespaceOrType(Symbol symbol)
350+
private NamespaceOrTypeSymbol GetContainingNamespaceOrNonExtensionType(Symbol symbol)
349351
{
350-
return symbol.ContainingNamespaceOrType() ?? this.Compilation.Assembly.GlobalNamespace;
352+
if (symbol.ContainingNamespaceOrType() is { } containing
353+
&& containing is not TypeSymbol { IsExtension: true })
354+
{
355+
return containing;
356+
}
357+
358+
return this.Compilation.Assembly.GlobalNamespace;
351359
}
352360

353361
internal Symbol BindNamespaceAliasSymbol(IdentifierNameSyntax node, BindingDiagnosticBag diagnostics)
@@ -1305,7 +1313,7 @@ private NamedTypeSymbol LookupGenericTypeName(
13051313
// for us.
13061314
Debug.Assert(lookupResult.Error != null);
13071315
type = new ExtendedErrorTypeSymbol(
1308-
GetContainingNamespaceOrType(lookupResultSymbol),
1316+
GetContainingNamespaceOrNonExtensionType(lookupResultSymbol),
13091317
ImmutableArray.Create<Symbol>(lookupResultSymbol),
13101318
lookupResult.Kind,
13111319
lookupResult.Error,
@@ -2234,7 +2242,7 @@ Symbol resultSymbol(
22342242
}
22352243

22362244
return new ExtendedErrorTypeSymbol(
2237-
GetContainingNamespaceOrType(originalSymbols[0]),
2245+
GetContainingNamespaceOrNonExtensionType(originalSymbols[0]),
22382246
originalSymbols,
22392247
LookupResultKind.Ambiguous,
22402248
info,
@@ -2252,7 +2260,7 @@ Symbol resultSymbol(
22522260
wasError = true;
22532261
var errorInfo = new CSDiagnosticInfo(ErrorCode.ERR_SystemVoid);
22542262
diagnostics.Add(errorInfo, where.Location);
2255-
singleResult = new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(singleResult), singleResult, LookupResultKind.NotReferencable, errorInfo); // UNDONE: Review resultkind.
2263+
singleResult = new ExtendedErrorTypeSymbol(GetContainingNamespaceOrNonExtensionType(singleResult), singleResult, LookupResultKind.NotReferencable, errorInfo); // UNDONE: Review resultkind.
22562264
}
22572265
// Check for bad symbol.
22582266
else
@@ -2285,7 +2293,7 @@ Symbol resultSymbol(
22852293
{
22862294
wasError = true;
22872295
diagnostics.Add(errorInfo, where.Location);
2288-
singleResult = new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(errorType), errorType.Name, errorType.Arity, errorInfo, unreported: false);
2296+
singleResult = new ExtendedErrorTypeSymbol(GetContainingNamespaceOrNonExtensionType(errorType), errorType.Name, errorType.Arity, errorInfo, unreported: false);
22892297
}
22902298
}
22912299
}
@@ -2341,7 +2349,7 @@ Symbol resultSymbol(
23412349
// Bad type or namespace (or things expected as types/namespaces) are packaged up as error types, preserving the symbols and the result kind.
23422350
// We do this if there are multiple symbols too, because just returning one would be losing important information, and they might
23432351
// be of different kinds.
2344-
return new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(symbols[0]), symbols.ToImmutable(), result.Kind, result.Error, arity);
2352+
return new ExtendedErrorTypeSymbol(GetContainingNamespaceOrNonExtensionType(symbols[0]), symbols.ToImmutable(), result.Kind, result.Error, arity);
23452353
}
23462354
else
23472355
{

src/Compilers/CSharp/Portable/CSharpResources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8116,4 +8116,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
81168116
<data name="ERR_ValueParameterSameNameAsExtensionTypeParameter" xml:space="preserve">
81178117
<value>'value': an automatically-generated parameter name conflicts with an extension type parameter name</value>
81188118
</data>
8119+
<data name="ERR_UnderspecifiedExtension" xml:space="preserve">
8120+
<value>The extended type '{0}' must reference all the type parameters declared by the extension, but type parameter '{1}' is missing.</value>
8121+
</data>
81198122
</root>

src/Compilers/CSharp/Portable/Errors/ErrorCode.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2384,6 +2384,7 @@ internal enum ErrorCode
23842384
ERR_TypeParameterSameNameAsExtensionParameter = 9511,
23852385
ERR_InvalidExtensionParameterReference = 9512,
23862386
ERR_ValueParameterSameNameAsExtensionTypeParameter = 9513,
2387+
ERR_UnderspecifiedExtension = 9514,
23872388

23882389
// Note: you will need to do the following after adding errors:
23892390
// 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs)

src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2501,6 +2501,7 @@ or ErrorCode.ERR_ValueParameterSameNameAsExtensionParameter
25012501
or ErrorCode.ERR_TypeParameterSameNameAsExtensionParameter
25022502
or ErrorCode.ERR_InvalidExtensionParameterReference
25032503
or ErrorCode.ERR_ValueParameterSameNameAsExtensionTypeParameter
2504+
or ErrorCode.ERR_UnderspecifiedExtension
25042505
=> false,
25052506
};
25062507
#pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.

src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ private void AddNameAndTypeArgumentsOrParameters(INamedTypeSymbol symbol)
455455

456456
return;
457457

458-
void addExtensionParameter(NamedTypeSymbol? underlyingTypeSymbol) // PROTOTYPE use public API once it's available
458+
void addExtensionParameter(NamedTypeSymbol? underlyingTypeSymbol)
459459
{
460460
if (!Format.CompilerInternalOptions.HasFlag(SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames)
461461
&& underlyingTypeSymbol!.ExtensionParameter is { } extensionParameter)

src/Compilers/CSharp/Portable/Symbols/Extensions/SynthesizedExtensionMarker.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Immutable;
66
using System.Diagnostics;
77
using Microsoft.CodeAnalysis.CSharp.Syntax;
8+
using Microsoft.CodeAnalysis.PooledObjects;
89

910
namespace Microsoft.CodeAnalysis.CSharp.Symbols
1011
{
@@ -74,6 +75,11 @@ protected override (TypeWithAnnotations ReturnType, ImmutableArray<ParameterSymb
7475

7576
ParameterSymbol? parameter = ParameterHelpers.MakeExtensionReceiverParameter(withTypeParametersBinder: signatureBinder, owner: this, parameterList, diagnostics);
7677

78+
if (parameter is { })
79+
{
80+
checkUnderspecifiedGenericExtension(parameter.Type, ContainingType.TypeParameters, diagnostics, parameter.GetFirstLocation());
81+
}
82+
7783
if (parameter is { Name: var name } && name != "" &&
7884
ContainingType.TypeParameters.Any(static (p, name) => p.Name == name, name))
7985
{
@@ -82,6 +88,37 @@ protected override (TypeWithAnnotations ReturnType, ImmutableArray<ParameterSymb
8288

8389
return parameter;
8490
}
91+
92+
static bool checkUnderspecifiedGenericExtension(TypeSymbol underlyingType, ImmutableArray<TypeParameterSymbol> typeParameters,
93+
BindingDiagnosticBag diagnostics, Location location)
94+
{
95+
var usedTypeParameters = PooledHashSet<TypeParameterSymbol>.GetInstance();
96+
underlyingType.VisitType(collectTypeParameters, arg: usedTypeParameters);
97+
98+
bool anyUnusedTypeParameter = false;
99+
foreach (var typeParameter in typeParameters)
100+
{
101+
if (!usedTypeParameters.Contains(typeParameter))
102+
{
103+
anyUnusedTypeParameter = true;
104+
diagnostics.Add(ErrorCode.ERR_UnderspecifiedExtension, location, underlyingType, typeParameter);
105+
}
106+
}
107+
108+
usedTypeParameters.Free();
109+
return anyUnusedTypeParameter;
110+
111+
static bool collectTypeParameters(TypeSymbol type, PooledHashSet<TypeParameterSymbol> typeParameters, bool ignored)
112+
{
113+
if (type is TypeParameterSymbol typeParameter)
114+
{
115+
typeParameters.Add(typeParameter);
116+
}
117+
118+
return false;
119+
}
120+
}
121+
85122
}
86123
}
87124
}

src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)