Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve unbound identifiers as target-typed enum members #21844

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ protected BoundExpression CreateConversion(
return CreateAnonymousFunctionConversion(syntax, source, conversion, isCast, destination, diagnostics);
}

if (conversion.IsTargetEnumeration && source.Kind == BoundKind.UnboundIdentifier)
{
return CreateTargetEnumerationConversion(syntax, source, conversion, isCast, destination, diagnostics);
}

if (conversion.IsTupleLiteralConversion ||
(conversion.IsNullable && conversion.UnderlyingConversions[0].IsTupleLiteralConversion))
{
Expand Down Expand Up @@ -291,6 +296,29 @@ private static BoundExpression CreateAnonymousFunctionConversion(SyntaxNode synt
{ WasCompilerGenerated = source.WasCompilerGenerated };
}

private BoundExpression CreateTargetEnumerationConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, TypeSymbol destination, DiagnosticBag diagnostics)
{
var isNullable = destination.IsNullableType();
var enumType = isNullable ? destination.GetNullableUnderlyingType() : destination;
var fieldSymbol = (FieldSymbol)enumType.GetMembers(((UnboundIdentifier)source).Name)[0];
var fieldAccess = BindFieldAccess(syntax, null, fieldSymbol, diagnostics, LookupResultKind.Viable, false);

if (isNullable)
{
conversion = new Conversion(ConversionKind.ImplicitNullable, Conversion.IdentityUnderlying);
}

return new BoundConversion(
syntax,
fieldAccess,
conversion,
@checked: false,
explicitCastInCode: isCast,
constantValueOpt: fieldAccess.ConstantValue,
type: destination)
{ WasCompilerGenerated = source.WasCompilerGenerated };
}

private BoundExpression CreateMethodGroupConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, TypeSymbol destination, DiagnosticBag diagnostics)
{
BoundMethodGroup group = FixMethodGroupWithTypeOrValue((BoundMethodGroup)source, conversion, diagnostics);
Expand Down
187 changes: 109 additions & 78 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1239,106 +1239,137 @@ private BoundExpression BindIdentifier(
default(ImmutableArray<TypeSymbol>);

var lookupResult = LookupResult.GetInstance();
LookupOptions options = LookupOptions.AllMethodsOnArityZero;
if (invoked)
try
{
options |= LookupOptions.MustBeInvocableIfMember;
}
LookupOptions options = LookupOptions.AllMethodsOnArityZero;
if (invoked)
{
options |= LookupOptions.MustBeInvocableIfMember;
}

if (!IsInMethodBody && this.EnclosingNameofArgument == null)
{
Debug.Assert((options & LookupOptions.NamespacesOrTypesOnly) == 0);
options |= LookupOptions.MustNotBeMethodTypeParameter;
}
if (!IsInMethodBody && this.EnclosingNameofArgument == null)
{
Debug.Assert((options & LookupOptions.NamespacesOrTypesOnly) == 0);
options |= LookupOptions.MustNotBeMethodTypeParameter;
}

var name = node.Identifier.ValueText;
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
this.LookupSymbolsWithFallback(lookupResult, name, arity: arity, useSiteDiagnostics: ref useSiteDiagnostics, options: options);
diagnostics.Add(node, useSiteDiagnostics);
var name = node.Identifier.ValueText;
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
this.LookupSymbolsWithFallback(lookupResult, name, arity: arity, useSiteDiagnostics: ref useSiteDiagnostics, options: options);
diagnostics.Add(node, useSiteDiagnostics);

if (lookupResult.Kind != LookupResultKind.Empty)
{
// have we detected an error with the current node?
bool isError = false;
bool wasError;
var members = ArrayBuilder<Symbol>.GetInstance();
Symbol symbol = GetSymbolOrMethodOrPropertyGroup(lookupResult, node, name, node.Arity, members, diagnostics, out wasError); // reports diagnostics in result.
if (lookupResult.Kind != LookupResultKind.Empty)
{
// have we detected an error with the current node?
bool isError = false;
bool wasError;
var members = ArrayBuilder<Symbol>.GetInstance();
Symbol symbol = GetSymbolOrMethodOrPropertyGroup(lookupResult, node, name, node.Arity, members, diagnostics, out wasError); // reports diagnostics in result.

isError |= wasError;
isError |= wasError;

if ((object)symbol == null)
{
Debug.Assert(members.Count > 0);
if ((object)symbol == null)
{
Debug.Assert(members.Count > 0);

var receiver = SynthesizeMethodGroupReceiver(node, members);
expression = ConstructBoundMemberGroupAndReportOmittedTypeArguments(
node,
typeArgumentList,
typeArguments,
receiver,
name,
members,
lookupResult,
receiver != null ? BoundMethodGroupFlags.HasImplicitReceiver : BoundMethodGroupFlags.None,
isError,
diagnostics);
}
else
{
bool isNamedType = (symbol.Kind == SymbolKind.NamedType) || (symbol.Kind == SymbolKind.ErrorType);

var receiver = SynthesizeMethodGroupReceiver(node, members);
expression = ConstructBoundMemberGroupAndReportOmittedTypeArguments(
node,
typeArgumentList,
typeArguments,
receiver,
name,
members,
lookupResult,
receiver != null ? BoundMethodGroupFlags.HasImplicitReceiver : BoundMethodGroupFlags.None,
isError,
diagnostics);
if (hasTypeArguments && isNamedType)
{
symbol = ConstructNamedTypeUnlessTypeArgumentOmitted(node, (NamedTypeSymbol)symbol, typeArgumentList, typeArguments, diagnostics);
}

expression = BindNonMethod(node, symbol, diagnostics, lookupResult.Kind, isError);

if (!isNamedType && (hasTypeArguments || node.Kind() == SyntaxKind.GenericName))
{
Debug.Assert(isError); // Should have been reported by GetSymbolOrMethodOrPropertyGroup.
expression = new BoundBadExpression(
syntax: node,
resultKind: LookupResultKind.WrongArity,
symbols: ImmutableArray.Create(symbol),
childBoundNodes: ImmutableArray.Create(expression),
type: expression.Type,
hasErrors: isError);
}
}

members.Free();
}
else
{
bool isNamedType = (symbol.Kind == SymbolKind.NamedType) || (symbol.Kind == SymbolKind.ErrorType);

if (hasTypeArguments && isNamedType)
if (node.Kind() == SyntaxKind.IdentifierName)
{
symbol = ConstructNamedTypeUnlessTypeArgumentOmitted(node, (NamedTypeSymbol)symbol, typeArgumentList, typeArguments, diagnostics);
}
if (FallBackOnDiscard((IdentifierNameSyntax)node, diagnostics))
{
return new BoundDiscardExpression(node, type: null);
}

expression = BindNonMethod(node, symbol, diagnostics, lookupResult.Kind, isError);
if (IsInTargetTypeContext(node))
{
return new UnboundIdentifier(node, name);
}
}

if (!isNamedType && (hasTypeArguments || node.Kind() == SyntaxKind.GenericName))
// Otherwise, the simple-name is undefined and a compile-time error occurs.
expression = BadExpression(node);
if (lookupResult.Error != null)
{
Debug.Assert(isError); // Should have been reported by GetSymbolOrMethodOrPropertyGroup.
expression = new BoundBadExpression(
syntax: node,
resultKind: LookupResultKind.WrongArity,
symbols: ImmutableArray.Create(symbol),
childBoundNodes: ImmutableArray.Create(expression),
type: expression.Type,
hasErrors: isError);
Error(diagnostics, lookupResult.Error, node);
}
else if (IsJoinRangeVariableInLeftKey(node))
{
Error(diagnostics, ErrorCode.ERR_QueryOuterKey, node, name);
}
else if (IsInJoinRightKey(node))
{
Error(diagnostics, ErrorCode.ERR_QueryInnerKey, node, name);
}
else
{
Error(diagnostics, ErrorCode.ERR_NameNotInContext, node, name);
}
}

members.Free();
return expression;
}
else
finally
{
if (node.IsKind(SyntaxKind.IdentifierName) && FallBackOnDiscard((IdentifierNameSyntax)node, diagnostics))
{
lookupResult.Free();
return new BoundDiscardExpression(node, type: null);
}
lookupResult.Free();
}
}

// Otherwise, the simple-name is undefined and a compile-time error occurs.
expression = BadExpression(node);
if (lookupResult.Error != null)
{
Error(diagnostics, lookupResult.Error, node);
}
else if (IsJoinRangeVariableInLeftKey(node))
{
Error(diagnostics, ErrorCode.ERR_QueryOuterKey, node, name);
}
else if (IsInJoinRightKey(node))
{
Error(diagnostics, ErrorCode.ERR_QueryInnerKey, node, name);
}
else
{
Error(diagnostics, ErrorCode.ERR_NameNotInContext, node, name);
}
private static bool IsInTargetTypeContext(SyntaxNode node)
{
// TODO(target-enum): where do we want to support this?

switch (node.Parent?.Kind())
{
case SyntaxKind.CaseSwitchLabel:
case SyntaxKind.ConstantPattern:
case SyntaxKind.EqualsExpression:
case SyntaxKind.NotEqualsExpression:
return true;
default:
return false;
}

lookupResult.Free();
return expression;
// TODO(target-enum): check feature flag
}

/// <summary>
Expand Down
18 changes: 18 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,24 @@ private static void ReportBinaryOperatorError(ExpressionSyntax node, DiagnosticB
return;
}

bool hasError = false;
if (left.Kind == BoundKind.UnboundIdentifier)
{
Error(diagnostics, ErrorCode.ERR_NameNotInContext, left.Syntax, left.Syntax);
hasError = true;
}

if (right.Kind == BoundKind.UnboundIdentifier)
{
Error(diagnostics, ErrorCode.ERR_NameNotInContext, right.Syntax, right.Syntax);
hasError = true;
}

if (hasError)
{
return;
}

ErrorCode errorCode = resultKind == LookupResultKind.Ambiguous ?
ErrorCode.ERR_AmbigBinaryOps : // Operator '{0}' is ambiguous on operands of type '{1}' and '{2}'
ErrorCode.ERR_BadBinaryOps; // Operator '{0}' cannot be applied to operands of type '{1}' and '{2}'
Expand Down
8 changes: 7 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,13 @@ internal BoundConstantPattern BindConstantPattern(
ConstantValue constantValueOpt = null;
var convertedExpression = ConvertPatternExpression(operandType, patternExpression, expression, ref constantValueOpt, diagnostics);
wasExpression = expression.Type?.IsErrorType() != true;
if (!convertedExpression.HasErrors && constantValueOpt == null)

if (convertedExpression.Kind == BoundKind.UnboundIdentifier)
{
diagnostics.Add(ErrorCode.ERR_NameNotInContext, patternExpression.Location, patternExpression);
hasErrors = true;
}
else if (!convertedExpression.HasErrors && constantValueOpt == null)
{
diagnostics.Add(ErrorCode.ERR_ConstantExpected, patternExpression.Location);
hasErrors = true;
Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2805,6 +2805,12 @@ protected void GenerateImplicitConversionError(
return;
}

if (operand.Kind == BoundKind.UnboundIdentifier)
{
Error(diagnostics, ErrorCode.ERR_NameNotInContext, syntax, syntax);
return;
}

if (operand.Kind == BoundKind.TupleLiteral)
{
var tuple = (BoundTupleLiteral)operand;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ private static void AssertTrivialConversion(ConversionKind kind)
case ConversionKind.ImplicitDynamic:
case ConversionKind.ExplicitDynamic:
case ConversionKind.InterpolatedString:
case ConversionKind.TargetEnumeration:
isTrivial = true;
break;

Expand Down Expand Up @@ -237,6 +238,7 @@ internal static Conversion GetTrivialConversion(ConversionKind kind)
internal static Conversion Deconstruction => new Conversion(ConversionKind.Deconstruction);
internal static Conversion IdentityValue => new Conversion(ConversionKind.IdentityValue);
internal static Conversion PinnedObjectToPointer => new Conversion(ConversionKind.PinnedObjectToPointer);
internal static Conversion TargetEnumeration => new Conversion(ConversionKind.TargetEnumeration);

// trivial conversions that could be underlying in nullable conversion
// NOTE: tuple conversions can be underlying as well, but they are not trivial
Expand Down Expand Up @@ -650,6 +652,14 @@ public bool IsAnonymousFunction
}
}

internal bool IsTargetEnumeration
{
get
{
return Kind == ConversionKind.TargetEnumeration;
}
}

/// <summary>
/// Returns true if the conversion is an implicit method group conversion.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,7 @@ internal enum ConversionKind : byte
// It is used by lowering of "fixed" statements to represent conversion of an object reference (O) to an unmanaged pointer (*)
// The conversion is unsafe and makes sense only if (O) is pinned.
PinnedObjectToPointer,

TargetEnumeration,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public static bool IsImplicitConversion(this ConversionKind conversionKind)
case ConversionKind.NullToPointer:
case ConversionKind.InterpolatedString:
case ConversionKind.Deconstruction:
case ConversionKind.TargetEnumeration:
return true;

case ConversionKind.ExplicitNumeric:
Expand Down
Loading