Skip to content
Merged
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
25 changes: 24 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
Expand Down Expand Up @@ -1445,7 +1446,7 @@ private BoundExpression BindSizeOf(SizeOfExpressionSyntax node, BindingDiagnosti
private BoundExpression BindFieldExpression(FieldExpressionSyntax node, BindingDiagnosticBag diagnostics)
{
Debug.Assert(ContainingType is { });
SynthesizedBackingFieldSymbolBase? field = null;
FieldSymbol? field = null;

if (hasOtherFieldSymbolInScope())
{
Expand All @@ -1460,6 +1461,12 @@ private BoundExpression BindFieldExpression(FieldExpressionSyntax node, BindingD
case MethodSymbol { AssociatedSymbol: SourcePropertySymbol property }:
field = property.BackingField;
break;
case MethodSymbol { AssociatedSymbol.OriginalDefinition: PEPropertySymbol property } method when
(Flags & BinderFlags.InEEMethodBinder) != 0 &&
IsPropertyWithBackingField(property, out FieldSymbol? backingField):

field = backingField.AsMember(method.ContainingType);
break;
default:
{
Debug.Assert((this.Flags & BinderFlags.InContextualAttributeBinder) != 0);
Expand Down Expand Up @@ -1495,6 +1502,22 @@ bool hasOtherFieldSymbolInScope()
}
}

internal static bool IsPropertyWithBackingField(PEPropertySymbol property, [NotNullWhen(true)] out FieldSymbol? backingField)
{
if (!property.GetIsNewExtensionMember() &&
property.ContainingType.GetMembers(GeneratedNames.MakeBackingFieldName(property.Name)) is [FieldSymbol candidateField] &&
candidateField.RefKind == property.RefKind &&
candidateField.IsStatic == property.IsStatic &&
candidateField.Type.Equals(property.Type, TypeCompareKind.AllIgnoreOptions))
{
backingField = candidateField;
return true;
}

backingField = null;
return false;
}

/// <summary>
/// Report diagnostic for variable declared with name 'field' within an accessor.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14054,7 +14054,7 @@ private bool IsInQuery
set => _syntaxFactoryContext.IsInQuery = value;
}

private bool IsInFieldKeywordContext
internal bool IsInFieldKeywordContext
{
get => _syntaxFactoryContext.IsInFieldKeywordContext;
set => _syntaxFactoryContext.IsInFieldKeywordContext = value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,17 @@ internal CompilationContext(
_displayClassVariables.Values.Any(v => v.Kind == DisplayClassVariableKind.This));
}

internal bool IsInFieldKeywordContext()
{
if (_currentFrame is not PEMethodSymbol)
{
return false;
}

return (TryFindUserDefinedMethod(_currentFrame, out _) ?? _currentFrame).OriginalDefinition.AssociatedSymbol is PEPropertySymbol property &&
Binder.IsPropertyWithBackingField(property, out _);
}

internal bool TryCompileExpressions(
ImmutableArray<CSharpSyntaxNode> syntaxNodes,
string typeNameBase,
Expand Down Expand Up @@ -209,13 +220,11 @@ private EENamedTypeSymbol CreateSynthesizedType(
this,
(EEMethodSymbol method, DiagnosticBag diags, out ImmutableArray<LocalSymbol> declaredLocals, out ResultProperties properties) =>
{
var hasDisplayClassThis = GetThisProxy(_displayClassVariables) != null;
var binder = ExtendBinderChain(
syntax,
aliases,
method,
NamespaceBinder,
hasDisplayClassThis,
_methodNotType,
out declaredLocals);

Expand Down Expand Up @@ -248,13 +257,11 @@ internal bool TryCompileAssignment(
this,
(EEMethodSymbol method, DiagnosticBag diags, out ImmutableArray<LocalSymbol> declaredLocals, out ResultProperties properties) =>
{
var hasDisplayClassThis = GetThisProxy(_displayClassVariables) != null;
var binder = ExtendBinderChain(
syntax,
aliases,
method,
NamespaceBinder,
hasDisplayClassThis,
methodNotType: true,
out declaredLocals);

Expand Down Expand Up @@ -954,16 +961,15 @@ private static int IndexOfMatchingAssembly(AssemblyIdentity referenceIdentity, I
return -1;
}

private static Binder ExtendBinderChain(
private Binder ExtendBinderChain(
CSharpSyntaxNode syntax,
ImmutableArray<Alias> aliases,
EEMethodSymbol method,
Binder binder,
bool hasDisplayClassThis,
bool methodNotType,
out ImmutableArray<LocalSymbol> declaredLocals)
{
var substitutedSourceMethod = GetSubstitutedSourceMethod(method.SubstitutedSourceMethod, hasDisplayClassThis);
var substitutedSourceMethod = GetSubstitutedUserDefinedSourceMethod(method.SubstitutedSourceMethod);
var substitutedSourceType = substitutedSourceMethod.ContainingType;

var stack = ArrayBuilder<NamedTypeSymbol>.GetInstance();
Expand Down Expand Up @@ -1804,9 +1810,6 @@ private static NamedTypeSymbol GetNonDisplayClassContainer(NamedTypeSymbol type)
/// The symbol of the method that is currently on top of the callstack, with
/// EE type parameters substituted in place of the original type parameters.
/// </param>
/// <param name="sourceMethodMustBeInstance">
/// True if "this" is available via a display class in the current context.
/// </param>
/// <returns>
/// If <paramref name="candidateSubstitutedSourceMethod"/> is compiler-generated,
/// then we will attempt to determine which user-defined method caused it to be
Expand All @@ -1826,19 +1829,39 @@ private static NamedTypeSymbol GetNonDisplayClassContainer(NamedTypeSymbol type)
/// properties that are used during binding (e.g. static-ness, generic arity, type parameter
/// constraints).
/// </remarks>
internal static MethodSymbol GetSubstitutedSourceMethod(
MethodSymbol candidateSubstitutedSourceMethod,
bool sourceMethodMustBeInstance)
internal MethodSymbol GetSubstitutedUserDefinedSourceMethod(
MethodSymbol candidateSubstitutedSourceMethod)
{
Debug.Assert(candidateSubstitutedSourceMethod.DeclaringCompilation is not null);

MethodSymbol? userDefinedMethod = TryFindUserDefinedMethod(candidateSubstitutedSourceMethod, out ImmutableArray<TypeWithAnnotations> displayClassTypeArguments);

if (userDefinedMethod is not null)
{
MethodSymbol sourceMethod = new EECompilationContextMethod(candidateSubstitutedSourceMethod.DeclaringCompilation!, userDefinedMethod.OriginalDefinition);
sourceMethod = sourceMethod.AsMember(userDefinedMethod.ContainingType);

return displayClassTypeArguments.Length == 0
? sourceMethod
: sourceMethod.Construct(displayClassTypeArguments);
}

return candidateSubstitutedSourceMethod;
}

private MethodSymbol? TryFindUserDefinedMethod(MethodSymbol candidateSubstitutedSourceMethod, out ImmutableArray<TypeWithAnnotations> displayClassTypeArguments)
{
displayClassTypeArguments = default;

var candidateSubstitutedSourceType = candidateSubstitutedSourceMethod.ContainingType;

string? desiredMethodName;
if (GeneratedNameParser.TryParseSourceMethodNameFromGeneratedName(candidateSubstitutedSourceType.Name, GeneratedNameKind.StateMachineType, out desiredMethodName) ||
GeneratedNameParser.TryParseSourceMethodNameFromGeneratedName(candidateSubstitutedSourceMethod.Name, GeneratedNameKind.LambdaMethod, out desiredMethodName) ||
GeneratedNameParser.TryParseSourceMethodNameFromGeneratedName(candidateSubstitutedSourceMethod.Name, GeneratedNameKind.LocalFunction, out desiredMethodName))
{
bool sourceMethodMustBeInstance = GetThisProxy(_displayClassVariables) != null;

// We could be in the MoveNext method of an async lambda.
string? tempMethodName;
if (GeneratedNameParser.TryParseSourceMethodNameFromGeneratedName(desiredMethodName, GeneratedNameKind.LambdaMethod, out tempMethodName) ||
Expand All @@ -1865,19 +1888,15 @@ internal static MethodSymbol GetSubstitutedSourceMethod(
{
if (IsViableSourceMethod(candidateMethod, desiredMethodName, desiredTypeParameters, sourceMethodMustBeInstance))
{
MethodSymbol sourceMethod = new EECompilationContextMethod(candidateSubstitutedSourceMethod.DeclaringCompilation!, candidateMethod.OriginalDefinition);
sourceMethod = sourceMethod.AsMember(substitutedSourceType);

return desiredTypeParameters.Length == 0
? sourceMethod
: sourceMethod.Construct(candidateSubstitutedSourceType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics);
displayClassTypeArguments = candidateSubstitutedSourceType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics;
return candidateMethod;
}
}

Debug.Fail("Why didn't we find a substituted source method for " + candidateSubstitutedSourceMethod + "?");
}

return candidateSubstitutedSourceMethod;
return null;
}

private static bool IsViableSourceMethod(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,15 @@ internal CompilationContext CreateCompilationContext()
out ImmutableArray<int> methodTokens,
out ImmutableArray<string> errorMessages)
{
var context = CreateCompilationContext();
bool isInFieldKeywordContext = context.IsInFieldKeywordContext();
var diagnostics = DiagnosticBag.GetInstance();
var syntaxNodes = expressions.SelectAsArray(expr => Parse(expr, treatAsExpression: true, diagnostics, out var formatSpecifiers));
var syntaxNodes = expressions.SelectAsArray(expr => Parse(expr, isInFieldKeywordContext, treatAsExpression: true, diagnostics, out var formatSpecifiers));
byte[]? assembly = null;
if (!diagnostics.HasAnyErrors())
{
RoslynDebug.Assert(syntaxNodes.All(s => s != null));

var context = CreateCompilationContext();
if (context.TryCompileExpressions(syntaxNodes!, TypeName, MethodName, diagnostics, out var moduleBuilder))
{
using var stream = new MemoryStream();
Expand Down Expand Up @@ -280,14 +281,15 @@ internal CompilationContext CreateCompilationContext()
out ResultProperties resultProperties,
CompilationTestData? testData)
{
var syntax = Parse(expr, (compilationFlags & DkmEvaluationFlags.TreatAsExpression) != 0, diagnostics, out var formatSpecifiers);
var context = CreateCompilationContext();

var syntax = Parse(expr, context.IsInFieldKeywordContext(), (compilationFlags & DkmEvaluationFlags.TreatAsExpression) != 0, diagnostics, out var formatSpecifiers);
if (syntax == null)
{
resultProperties = default;
return null;
}

var context = CreateCompilationContext();
if (!context.TryCompileExpression(syntax, TypeName, MethodName, aliases, testData, diagnostics, out var moduleBuilder, out var synthesizedMethod))
{
resultProperties = default;
Expand Down Expand Up @@ -325,8 +327,9 @@ internal CompilationContext CreateCompilationContext()
formatSpecifiers: formatSpecifiers);
}

private static CSharpSyntaxNode? Parse(
private CSharpSyntaxNode? Parse(
string expr,
bool isInFieldKeywordContext,
bool treatAsExpression,
DiagnosticBag diagnostics,
out ReadOnlyCollection<string>? formatSpecifiers)
Expand All @@ -335,7 +338,7 @@ internal CompilationContext CreateCompilationContext()
{
// Try to parse as a statement. If that fails, parse as an expression.
var statementDiagnostics = DiagnosticBag.GetInstance();
var statementSyntax = expr.ParseStatement(statementDiagnostics);
var statementSyntax = expr.ParseStatement(isInFieldKeywordContext, statementDiagnostics);
Debug.Assert((statementSyntax == null) || !statementDiagnostics.HasAnyErrors());
statementDiagnostics.Free();
var isExpressionStatement = statementSyntax.IsKind(SyntaxKind.ExpressionStatement);
Expand All @@ -353,7 +356,7 @@ internal CompilationContext CreateCompilationContext()
}
}

return expr.ParseExpression(diagnostics, allowFormatSpecifiers: true, out formatSpecifiers);
return expr.ParseExpression(isInFieldKeywordContext, diagnostics, allowFormatSpecifiers: true, out formatSpecifiers);
}

internal override CompileResult? CompileAssignment(
Expand All @@ -364,14 +367,14 @@ internal CompilationContext CreateCompilationContext()
out ResultProperties resultProperties,
CompilationTestData? testData)
{
var assignment = target.ParseAssignment(expr, diagnostics);
var context = CreateCompilationContext();
var assignment = target.ParseAssignment(expr, context.IsInFieldKeywordContext(), diagnostics);
if (assignment == null)
{
resultProperties = default;
return null;
}

var context = CreateCompilationContext();
if (!context.TryCompileAssignment(assignment, TypeName, MethodName, aliases, testData, diagnostics, out var moduleBuilder, out var synthesizedMethod))
{
resultProperties = default;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal static class SyntaxHelpers
/// </summary>
internal static ExpressionSyntax? ParseExpression(
this string expr,
bool isInFieldKeywordContext,
DiagnosticBag diagnostics,
bool allowFormatSpecifiers,
out ReadOnlyCollection<string>? formatSpecifiers)
Expand All @@ -35,7 +36,7 @@ internal static class SyntaxHelpers
allowFormatSpecifiers = false;
}

var syntax = ParseDebuggerExpression(expr, consumeFullText: !allowFormatSpecifiers);
var syntax = ParseDebuggerExpression(expr, consumeFullText: !allowFormatSpecifiers, isInFieldKeywordContext);
diagnostics.AddRange(syntax.GetDiagnostics());
formatSpecifiers = null;

Expand All @@ -56,10 +57,11 @@ internal static class SyntaxHelpers
internal static ExpressionSyntax? ParseAssignment(
this string target,
string expr,
bool isInFieldKeywordContext,
DiagnosticBag diagnostics)
{
var text = SourceText.From(expr, encoding: null, SourceHashAlgorithms.Default);
var expression = ParseDebuggerExpressionInternal(text, consumeFullText: true);
var expression = ParseDebuggerExpressionInternal(text, consumeFullText: true, isInFieldKeywordContext);
// We're creating a SyntaxTree for just the RHS so that the Diagnostic spans for parse errors
// will be correct (with respect to the original input text). If we ever expose a SemanticModel
// for debugger expressions, we should use this SyntaxTree.
Expand All @@ -72,7 +74,7 @@ internal static class SyntaxHelpers

// Any Diagnostic spans produced in binding will be offset by the length of the "target" expression text.
// If we want to support live squiggles in debugger windows, SemanticModel, etc, we'll want to address this.
var targetSyntax = ParseDebuggerExpressionInternal(SourceText.From(target, encoding: null, SourceHashAlgorithms.Default), consumeFullText: true);
var targetSyntax = ParseDebuggerExpressionInternal(SourceText.From(target, encoding: null, SourceHashAlgorithms.Default), consumeFullText: true, isInFieldKeywordContext);
Debug.Assert(!targetSyntax.GetDiagnostics().Any(), "The target of an assignment should never contain Diagnostics if we're being allowed to assign to it in the debugger.");

var assignment = InternalSyntax.SyntaxFactory.AssignmentExpression(
Expand All @@ -88,9 +90,10 @@ internal static class SyntaxHelpers
/// </summary>
internal static StatementSyntax? ParseStatement(
this string expr,
bool isInFieldKeywordContext,
DiagnosticBag diagnostics)
{
var syntax = ParseDebuggerStatement(expr);
var syntax = ParseDebuggerStatement(expr, isInFieldKeywordContext);
diagnostics.AddRange(syntax.GetDiagnostics());
return diagnostics.HasAnyErrors() ? null : syntax;
}
Expand Down Expand Up @@ -194,29 +197,31 @@ private static bool RemoveSemicolonIfAny(ref string str)
return false;
}

private static ExpressionSyntax ParseDebuggerExpression(string text, bool consumeFullText)
private static ExpressionSyntax ParseDebuggerExpression(string text, bool consumeFullText, bool isInFieldKeywordContext)
{
var source = SourceText.From(text, encoding: null, SourceHashAlgorithms.Default);
var expression = ParseDebuggerExpressionInternal(source, consumeFullText);
var expression = ParseDebuggerExpressionInternal(source, consumeFullText, isInFieldKeywordContext);
return expression.MakeDebuggerExpression(source);
}

private static InternalSyntax.ExpressionSyntax ParseDebuggerExpressionInternal(SourceText source, bool consumeFullText)
private static InternalSyntax.ExpressionSyntax ParseDebuggerExpressionInternal(SourceText source, bool consumeFullText, bool isInFieldKeywordContext)
{
using var lexer = new InternalSyntax.Lexer(source, PreviewParseOptions, allowPreprocessorDirectives: false);
using var parser = new InternalSyntax.LanguageParser(lexer, oldTree: null, changes: null, lexerMode: InternalSyntax.LexerMode.DebuggerSyntax);
parser.IsInFieldKeywordContext = isInFieldKeywordContext;

var node = parser.ParseExpression();
if (consumeFullText)
node = parser.ConsumeUnexpectedTokens(node);
return node;
}

private static StatementSyntax ParseDebuggerStatement(string text)
private static StatementSyntax ParseDebuggerStatement(string text, bool isInFieldKeywordContext)
{
var source = SourceText.From(text, encoding: null, SourceHashAlgorithms.Default);
using var lexer = new InternalSyntax.Lexer(source, PreviewParseOptions);
using var parser = new InternalSyntax.LanguageParser(lexer, oldTree: null, changes: null, lexerMode: InternalSyntax.LexerMode.DebuggerSyntax);
parser.IsInFieldKeywordContext = isInFieldKeywordContext;

var statement = parser.ParseStatement();
var syntaxTree = statement.CreateSyntaxTree(source);
Expand Down
Loading