Skip to content

Commit 4df2321

Browse files
committed
Support scoped keyword in declaration expressions.
Related to #62039. https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md#syntax Also fixes #64259.
1 parent 523b135 commit 4df2321

29 files changed

+7408
-142
lines changed

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

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ private DeconstructionVariable BindDeconstructionVariables(
750750
bool isVar;
751751
bool isConst = false;
752752
AliasSymbol alias;
753-
var declType = BindVariableTypeWithAnnotations(component.Designation, diagnostics, component.Type, ref isConst, out isVar, out alias);
753+
var declType = BindVariableTypeWithAnnotations(component.Designation, diagnostics, component.Type.SkipScoped(out _).SkipRef(out _), ref isConst, out isVar, out alias);
754754
Debug.Assert(isVar == !declType.HasType);
755755
if (component.Designation.Kind() == SyntaxKind.ParenthesizedVariableDesignation && !isVar)
756756
{
@@ -804,6 +804,23 @@ private DeconstructionVariable BindDeconstructionVariables(
804804
case SyntaxKind.DiscardDesignation:
805805
{
806806
var discarded = (DiscardDesignationSyntax)node;
807+
808+
if (discarded.Parent is DeclarationExpressionSyntax declExpr && declExpr.Designation == discarded)
809+
{
810+
TypeSyntax typeSyntax = declExpr.Type;
811+
812+
if (typeSyntax is ScopedTypeSyntax scopedType)
813+
{
814+
diagnostics.Add(ErrorCode.ERR_ScopedDiscard, scopedType.ScopedKeyword.GetLocation());
815+
typeSyntax = scopedType.Type;
816+
}
817+
818+
if (typeSyntax is RefTypeSyntax refType)
819+
{
820+
diagnostics.Add(ErrorCode.ERR_DeconstructVariableCannotBeByRef, refType.RefKeyword.GetLocation());
821+
}
822+
}
823+
807824
return new DeconstructionVariable(BindDiscardExpression(syntax, declTypeWithAnnotations), syntax);
808825
}
809826
case SyntaxKind.ParenthesizedVariableDesignation:
@@ -845,6 +862,29 @@ private BoundExpression BindDeconstructionVariable(
845862
// is this a local?
846863
if ((object)localSymbol != null)
847864
{
865+
if (designation.Parent is DeclarationExpressionSyntax declExpr && declExpr.Designation == designation)
866+
{
867+
TypeSyntax typeSyntax = declExpr.Type;
868+
869+
if (typeSyntax is ScopedTypeSyntax scopedType)
870+
{
871+
// Check for support for 'scoped'.
872+
ModifierUtils.CheckScopedModifierAvailability(typeSyntax, scopedType.ScopedKeyword, diagnostics);
873+
typeSyntax = scopedType.Type;
874+
}
875+
876+
if (typeSyntax is RefTypeSyntax refType)
877+
{
878+
diagnostics.Add(ErrorCode.ERR_DeconstructVariableCannotBeByRef, refType.RefKeyword.GetLocation());
879+
}
880+
881+
if (declTypeWithAnnotations.HasType &&
882+
localSymbol.Scope == DeclarationScope.ValueScoped && !declTypeWithAnnotations.Type.IsErrorTypeOrRefLikeType())
883+
{
884+
diagnostics.Add(ErrorCode.ERR_ScopedRefAndRefStructOnly, typeSyntax.Location);
885+
}
886+
}
887+
848888
// Check for variable declaration errors.
849889
// Use the binder that owns the scope for the local because this (the current) binder
850890
// might own nested scope.
@@ -857,33 +897,51 @@ private BoundExpression BindDeconstructionVariable(
857897

858898
return new DeconstructionVariablePendingInference(syntax, localSymbol, receiverOpt: null);
859899
}
900+
else
901+
{
902+
// Is this a field?
903+
GlobalExpressionVariable field = LookupDeclaredField(designation);
860904

861-
// Is this a field?
862-
GlobalExpressionVariable field = LookupDeclaredField(designation);
905+
if ((object)field == null)
906+
{
907+
// We should have the right binder in the chain, cannot continue otherwise.
908+
throw ExceptionUtilities.Unreachable;
909+
}
863910

864-
if ((object)field == null)
865-
{
866-
// We should have the right binder in the chain, cannot continue otherwise.
867-
throw ExceptionUtilities.Unreachable;
868-
}
911+
if (designation.Parent is DeclarationExpressionSyntax declExpr && declExpr.Designation == designation)
912+
{
913+
TypeSyntax typeSyntax = declExpr.Type;
869914

870-
BoundThisReference receiver = ThisReference(designation, this.ContainingType, hasErrors: false,
871-
wasCompilerGenerated: true);
915+
if (typeSyntax is ScopedTypeSyntax scopedType)
916+
{
917+
diagnostics.Add(ErrorCode.ERR_UnexpectedToken, scopedType.ScopedKeyword.GetLocation(), scopedType.ScopedKeyword.ValueText);
918+
typeSyntax = scopedType.Type;
919+
}
872920

873-
if (declTypeWithAnnotations.HasType)
874-
{
875-
var fieldType = field.GetFieldType(this.FieldsBeingBound);
876-
Debug.Assert(TypeSymbol.Equals(declTypeWithAnnotations.Type, fieldType.Type, TypeCompareKind.ConsiderEverything2));
877-
return new BoundFieldAccess(syntax,
878-
receiver,
879-
field,
880-
constantValueOpt: null,
881-
resultKind: LookupResultKind.Viable,
882-
isDeclaration: true,
883-
type: fieldType.Type);
884-
}
921+
if (typeSyntax is RefTypeSyntax refType)
922+
{
923+
diagnostics.Add(ErrorCode.ERR_UnexpectedToken, refType.RefKeyword.GetLocation(), refType.RefKeyword.ValueText);
924+
}
925+
}
926+
927+
BoundThisReference receiver = ThisReference(designation, this.ContainingType, hasErrors: false,
928+
wasCompilerGenerated: true);
885929

886-
return new DeconstructionVariablePendingInference(syntax, field, receiver);
930+
if (declTypeWithAnnotations.HasType)
931+
{
932+
var fieldType = field.GetFieldType(this.FieldsBeingBound);
933+
Debug.Assert(TypeSymbol.Equals(declTypeWithAnnotations.Type, fieldType.Type, TypeCompareKind.ConsiderEverything2));
934+
return new BoundFieldAccess(syntax,
935+
receiver,
936+
field,
937+
constantValueOpt: null,
938+
resultKind: LookupResultKind.Viable,
939+
isDeclaration: true,
940+
type: fieldType.Type);
941+
}
942+
943+
return new DeconstructionVariablePendingInference(syntax, field, receiver);
944+
}
887945
}
888946
}
889947
}

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

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ private BoundExpression BindDeclarationExpressionAsError(DeclarationExpressionSy
889889
bool isVar;
890890
bool isConst = false;
891891
AliasSymbol alias;
892-
var declType = BindVariableTypeWithAnnotations(node.Designation, diagnostics, node.Type, ref isConst, out isVar, out alias);
892+
var declType = BindVariableTypeWithAnnotations(node.Designation, diagnostics, node.Type.SkipScoped(out _).SkipRef(out _), ref isConst, out isVar, out alias);
893893
Error(diagnostics, ErrorCode.ERR_DeclarationExpressionNotPermitted, node);
894894
return BindDeclarationVariablesForErrorRecovery(declType, node.Designation, node, diagnostics);
895895
}
@@ -2794,16 +2794,23 @@ private BoundExpression BindOutDeclarationArgument(DeclarationExpressionSyntax d
27942794
TypeSyntax typeSyntax = declarationExpression.Type;
27952795
VariableDesignationSyntax designation = declarationExpression.Designation;
27962796

2797-
Debug.Assert(typeSyntax is not ScopedTypeSyntax);
2798-
if (typeSyntax.SkipScoped(out _).GetRefKind() != RefKind.None)
2799-
{
2800-
diagnostics.Add(ErrorCode.ERR_OutVariableCannotBeByRef, declarationExpression.Type.Location);
2801-
}
28022797

28032798
switch (designation.Kind())
28042799
{
28052800
case SyntaxKind.DiscardDesignation:
28062801
{
2802+
if (typeSyntax is ScopedTypeSyntax scopedType)
2803+
{
2804+
diagnostics.Add(ErrorCode.ERR_ScopedDiscard, scopedType.ScopedKeyword.GetLocation());
2805+
typeSyntax = scopedType.Type;
2806+
}
2807+
2808+
if (typeSyntax is RefTypeSyntax refType)
2809+
{
2810+
diagnostics.Add(ErrorCode.ERR_OutVariableCannotBeByRef, refType.Location);
2811+
typeSyntax = refType.Type;
2812+
}
2813+
28072814
bool isVar;
28082815
bool isConst = false;
28092816
AliasSymbol alias;
@@ -2833,6 +2840,19 @@ private BoundExpression BindOutVariableDeclarationArgument(
28332840
SourceLocalSymbol localSymbol = this.LookupLocal(designation.Identifier);
28342841
if ((object)localSymbol != null)
28352842
{
2843+
if (typeSyntax is ScopedTypeSyntax scopedType)
2844+
{
2845+
// Check for support for 'scoped'.
2846+
ModifierUtils.CheckScopedModifierAvailability(typeSyntax, scopedType.ScopedKeyword, diagnostics);
2847+
typeSyntax = scopedType.Type;
2848+
}
2849+
2850+
if (typeSyntax is RefTypeSyntax refType)
2851+
{
2852+
diagnostics.Add(ErrorCode.ERR_OutVariableCannotBeByRef, refType.Location);
2853+
typeSyntax = refType.Type;
2854+
}
2855+
28362856
Debug.Assert(localSymbol.DeclarationKind == LocalDeclarationKind.OutVariable);
28372857
if ((InConstructorInitializer || InFieldInitializer) && ContainingMemberOrLambda.ContainingSymbol.Kind == SymbolKind.NamedType)
28382858
{
@@ -2852,38 +2872,57 @@ private BoundExpression BindOutVariableDeclarationArgument(
28522872

28532873
CheckRestrictedTypeInAsyncMethod(this.ContainingMemberOrLambda, declType.Type, diagnostics, typeSyntax);
28542874

2875+
if (localSymbol.Scope == DeclarationScope.ValueScoped && !declType.Type.IsErrorTypeOrRefLikeType())
2876+
{
2877+
diagnostics.Add(ErrorCode.ERR_ScopedRefAndRefStructOnly, typeSyntax.Location);
2878+
}
2879+
28552880
return new BoundLocal(declarationExpression, localSymbol, BoundLocalDeclarationKind.WithExplicitType, constantValueOpt: null, isNullableUnknown: false, type: declType.Type);
28562881
}
2882+
else
2883+
{
2884+
// Is this a field?
2885+
GlobalExpressionVariable expressionVariableField = LookupDeclaredField(designation);
28572886

2858-
// Is this a field?
2859-
GlobalExpressionVariable expressionVariableField = LookupDeclaredField(designation);
2887+
if ((object)expressionVariableField == null)
2888+
{
2889+
// We should have the right binder in the chain, cannot continue otherwise.
2890+
throw ExceptionUtilities.Unreachable;
2891+
}
28602892

2861-
if ((object)expressionVariableField == null)
2862-
{
2863-
// We should have the right binder in the chain, cannot continue otherwise.
2864-
throw ExceptionUtilities.Unreachable;
2865-
}
2893+
BoundExpression receiver = SynthesizeReceiver(designation, expressionVariableField, diagnostics);
28662894

2867-
BoundExpression receiver = SynthesizeReceiver(designation, expressionVariableField, diagnostics);
2895+
if (typeSyntax is ScopedTypeSyntax scopedType)
2896+
{
2897+
diagnostics.Add(ErrorCode.ERR_UnexpectedToken, scopedType.ScopedKeyword.GetLocation(), scopedType.ScopedKeyword.ValueText);
2898+
typeSyntax = scopedType.Type;
2899+
}
28682900

2869-
if (typeSyntax.IsVar)
2870-
{
2871-
BindTypeOrAliasOrVarKeyword(typeSyntax, BindingDiagnosticBag.Discarded, out isVar);
2901+
if (typeSyntax is RefTypeSyntax refType)
2902+
{
2903+
diagnostics.Add(ErrorCode.ERR_UnexpectedToken, refType.RefKeyword.GetLocation(), refType.RefKeyword.ValueText);
2904+
typeSyntax = refType.Type;
2905+
}
28722906

2873-
if (isVar)
2907+
if (typeSyntax.IsVar)
28742908
{
2875-
return new OutVariablePendingInference(declarationExpression, expressionVariableField, receiver);
2909+
BindTypeOrAliasOrVarKeyword(typeSyntax, BindingDiagnosticBag.Discarded, out isVar);
2910+
2911+
if (isVar)
2912+
{
2913+
return new OutVariablePendingInference(declarationExpression, expressionVariableField, receiver);
2914+
}
28762915
}
2877-
}
28782916

2879-
TypeSymbol fieldType = expressionVariableField.GetFieldType(this.FieldsBeingBound).Type;
2880-
return new BoundFieldAccess(declarationExpression,
2881-
receiver,
2882-
expressionVariableField,
2883-
null,
2884-
LookupResultKind.Viable,
2885-
isDeclaration: true,
2886-
type: fieldType);
2917+
TypeSymbol fieldType = expressionVariableField.GetFieldType(this.FieldsBeingBound).Type;
2918+
return new BoundFieldAccess(declarationExpression,
2919+
receiver,
2920+
expressionVariableField,
2921+
null,
2922+
LookupResultKind.Viable,
2923+
isDeclaration: true,
2924+
type: fieldType);
2925+
}
28872926
}
28882927

28892928
/// <summary>

src/Compilers/CSharp/Portable/BoundTree/VariablePendingInference.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ internal BoundExpression SetInferredTypeWithAnnotations(TypeWithAnnotations type
6363
this.Syntax;
6464

6565
Binder.CheckRestrictedTypeInAsyncMethod(localSymbol.ContainingSymbol, type.Type, diagnosticsOpt, typeOrDesignationSyntax);
66+
67+
if (localSymbol.Scope == DeclarationScope.ValueScoped && !type.Type.IsErrorTypeOrRefLikeType())
68+
{
69+
diagnosticsOpt.Add(ErrorCode.ERR_ScopedRefAndRefStructOnly,
70+
(typeOrDesignationSyntax is TypeSyntax typeSyntax ? typeSyntax.SkipScoped(out _).SkipRef(out _) : typeOrDesignationSyntax).Location);
71+
}
6672
}
6773
}
6874

src/Compilers/CSharp/Portable/CSharpResources.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7367,4 +7367,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
73677367
<data name="ERR_ReadOnlyNotSuppAsParamModDidYouMeanIn" xml:space="preserve">
73687368
<value>'readonly' is not supported as a parameter modifier. Did you mean 'in'?</value>
73697369
</data>
7370+
<data name="ERR_ScopedDiscard" xml:space="preserve">
7371+
<value>The 'scoped' modifier cannot be used with discard.</value>
7372+
</data>
7373+
<data name="ERR_DeconstructVariableCannotBeByRef" xml:space="preserve">
7374+
<value>A deconstruction variable cannot be declared as a ref local</value>
7375+
</data>
73707376
</root>

src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ private Symbol GetSemanticInfoSymbolInNonMemberContext(CSharpSyntaxNode node, bo
366366
// However, we can return fieldSymbol.Type for implicitly typed field symbols in both cases.
367367
// Note that for regular C#, fieldSymbol.Type would be an error type.
368368

369-
var variableDecl = type.Parent as VariableDeclarationSyntax;
369+
var variableDecl = type.ModifyingScopedOrRefTypeOrSelf().Parent as VariableDeclarationSyntax;
370370
if (variableDecl != null && variableDecl.Variables.Any())
371371
{
372372
var fieldSymbol = GetDeclaredFieldSymbol(variableDecl.Variables.First());

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2112,7 +2112,7 @@ internal enum ErrorCode
21122112
ERR_FeatureNotAvailableInVersion11 = 9058,
21132113
ERR_RefFieldInNonRefStruct = 9059,
21142114
ERR_CannotMatchOnINumberBase = 9060,
2115-
// Available 9061,
2115+
ERR_ScopedDiscard = 9061,
21162116
ERR_ScopedTypeNameDisallowed = 9062,
21172117
ERR_UnscopedRefAttributeUnsupportedTarget = 9063,
21182118
ERR_RuntimeDoesNotSupportRefFields = 9064,
@@ -2123,7 +2123,7 @@ internal enum ErrorCode
21232123
ERR_FilePathCannotBeConvertedToUtf8 = 9069,
21242124
ERR_ReadOnlyNotSuppAsParamModDidYouMeanIn = 9070,
21252125
ERR_FileLocalDuplicateNameInNS = 9071,
2126-
// Available 9072,
2126+
ERR_DeconstructVariableCannotBeByRef = 9072,
21272127
WRN_ScopedMismatchInParameterOfTarget = 9073,
21282128
WRN_ScopedMismatchInParameterOfOverrideOrImplementation = 9074,
21292129
ERR_RefReturnScopedParameter = 9075,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,6 +1819,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code)
18191819
case ErrorCode.ERR_InvalidObjectCreation:
18201820
case ErrorCode.WRN_TypeParameterSameAsOuterMethodTypeParameter:
18211821
case ErrorCode.ERR_OutVariableCannotBeByRef:
1822+
case ErrorCode.ERR_DeconstructVariableCannotBeByRef:
18221823
case ErrorCode.ERR_OmittedTypeArgument:
18231824
case ErrorCode.ERR_FeatureNotAvailableInVersion8:
18241825
case ErrorCode.ERR_AltInterpolatedVerbatimStringsNotAvailable:
@@ -2218,6 +2219,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code)
22182219
case ErrorCode.ERR_BadAbstractEqualityOperatorSignature:
22192220
case ErrorCode.ERR_BadBinaryReadOnlySpanConcatenation:
22202221
case ErrorCode.ERR_ScopedRefAndRefStructOnly:
2222+
case ErrorCode.ERR_ScopedDiscard:
22212223
case ErrorCode.ERR_FixedFieldMustNotBeRef:
22222224
case ErrorCode.ERR_RefFieldCannotReferToRefStruct:
22232225
case ErrorCode.ERR_FileTypeDisallowedInSignature:

0 commit comments

Comments
 (0)