From 369bc83ac068818f62a3bb069f5ddd37c590e533 Mon Sep 17 00:00:00 2001
From: Charles Stoner <10732005+cston@users.noreply.github.com>
Date: Wed, 23 Mar 2022 21:19:14 -0700
Subject: [PATCH 01/29] Support ref field declarations from source and metadata
---
.../Portable/Binder/Binder.ValueChecks.cs | 20 +-
.../BoundTree/BoundExpressionExtensions.cs | 3 +
.../CSharp/Portable/CSharpResources.resx | 3 +
.../CSharp/Portable/CodeGen/EmitAddress.cs | 4 +-
.../CSharp/Portable/CodeGen/EmitExpression.cs | 50 +-
.../CSharp/Portable/CodeGen/EmitStatement.cs | 3 +-
.../CSharp/Portable/CodeGen/Optimizer.cs | 5 +-
.../Emitter/Model/FieldSymbolAdapter.cs | 5 +
.../Model/SpecializedFieldReference.cs | 5 +
.../CSharp/Portable/Errors/MessageID.cs | 2 +
.../LocalRewriter_AssignmentOperator.cs | 11 +-
.../CSharp/Portable/Parser/LanguageParser.cs | 10 +-
.../SymbolDisplayVisitor.Members.cs | 12 +
.../AnonymousType.FieldSymbol.cs | 4 +
.../CSharp/Portable/Symbols/FieldSymbol.cs | 4 +
.../Metadata/PE/MemberRefMetadataDecoder.cs | 6 +-
.../Symbols/Metadata/PE/PEFieldSymbol.cs | 61 +-
.../Symbols/Metadata/PE/PENamedTypeSymbol.cs | 7 +-
.../Symbols/PublicModel/FieldSymbol.cs | 4 +
.../Retargeting/RetargetingFieldSymbol.cs | 5 +
.../FieldSymbolWithAttributesAndModifiers.cs | 5 +
.../Source/GlobalExpressionVariable.cs | 2 +
.../Source/SourceEnumConstantSymbol.cs | 2 +
.../Symbols/Source/SourceFieldSymbol.cs | 9 +
.../Symbols/Source/SourceMemberFieldSymbol.cs | 42 +-
.../Symbols/SubstitutedFieldSymbol.cs | 5 +
.../SynthesizedBackingFieldSymbol.cs | 5 +
.../Synthesized/SynthesizedFieldSymbolBase.cs | 4 +
.../Symbols/Tuples/TupleFieldSymbol.cs | 4 +
.../Portable/xlf/CSharpResources.cs.xlf | 5 +
.../Portable/xlf/CSharpResources.de.xlf | 5 +
.../Portable/xlf/CSharpResources.es.xlf | 5 +
.../Portable/xlf/CSharpResources.fr.xlf | 5 +
.../Portable/xlf/CSharpResources.it.xlf | 5 +
.../Portable/xlf/CSharpResources.ja.xlf | 5 +
.../Portable/xlf/CSharpResources.ko.xlf | 5 +
.../Portable/xlf/CSharpResources.pl.xlf | 5 +
.../Portable/xlf/CSharpResources.pt-BR.xlf | 5 +
.../Portable/xlf/CSharpResources.ru.xlf | 5 +
.../Portable/xlf/CSharpResources.tr.xlf | 5 +
.../Portable/xlf/CSharpResources.zh-Hans.xlf | 5 +
.../Portable/xlf/CSharpResources.zh-Hant.xlf | 5 +
.../Test/Semantic/Semantics/RefFieldTests.cs | 1891 +++++++++++++++++
.../SymbolDisplay/SymbolDisplayTests.cs | 43 +
.../Syntax/Parsing/RefFieldParsingTests.cs | 208 ++
.../Test/Syntax/Parsing/RefReadonlyTests.cs | 17 +-
.../CodeGen/PrivateImplementationDetails.cs | 4 +
.../Emit/NoPia/CommonEmbeddedField.cs | 4 +
.../MetadataReader/MetadataDecoder.cs | 59 +-
.../Core/Portable/PEWriter/Members.cs | 13 +
.../Core/Portable/PEWriter/MetadataWriter.cs | 6 +
.../Core/Portable/PublicAPI.Unshipped.txt | 2 +
.../Core/Portable/Symbols/IFieldSymbol.cs | 10 +
.../Portable/Emit/FieldSymbolAdapter.vb | 12 +
.../Emit/SpecializedFieldReference.vb | 18 +-
.../Portable/Symbols/FieldSymbol.vb | 18 +
.../Metadata/PE/MemberRefMetadataDecoder.vb | 6 +-
.../Symbols/Metadata/PE/PEFieldSymbol.vb | 10 +-
.../SymbolDisplay/SymbolDisplayTests.vb | 48 +
.../Symbols/DisplayClassVariable.cs | 4 +
.../ExpressionCompiler/SyntaxHelpers.cs | 2 +-
.../ExpressionCompilerTests.cs | 32 +
.../Test/ResultProvider/ExpansionTests.cs | 29 +
...adataAsSourceService.WrappedFieldSymbol.cs | 4 +
.../Symbols/CodeGenerationFieldSymbol.cs | 4 +
65 files changed, 2722 insertions(+), 89 deletions(-)
create mode 100644 src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs
create mode 100644 src/Compilers/CSharp/Test/Syntax/Parsing/RefFieldParsingTests.cs
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
index b61abacd57cd1..3d533c3b4c0ec 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
@@ -829,6 +829,12 @@ private bool CheckFieldValueKind(SyntaxNode node, BoundFieldAccess fieldAccess,
}
}
+ if (fieldSymbol.RefKind == RefKind.RefReadOnly)
+ {
+ ReportReadOnlyError(fieldSymbol, node, valueKind, checkingReceiver, diagnostics);
+ return false;
+ }
+
if (fieldSymbol.IsFixedSizeBuffer)
{
Error(diagnostics, GetStandardLvalueError(valueKind), node);
@@ -838,8 +844,18 @@ private bool CheckFieldValueKind(SyntaxNode node, BoundFieldAccess fieldAccess,
if (RequiresRefAssignableVariable(valueKind))
{
- Error(diagnostics, ErrorCode.ERR_RefLocalOrParamExpected, node);
- return false;
+ switch (fieldSymbol.RefKind)
+ {
+ case RefKind.None:
+ Debug.Assert(fieldSymbol.RefKind == RefKind.None);
+ Error(diagnostics, ErrorCode.ERR_RefLocalOrParamExpected, node);
+ return false;
+ case RefKind.Ref:
+ case RefKind.RefReadOnly:
+ return true;
+ default:
+ throw ExceptionUtilities.UnexpectedValue(fieldSymbol.RefKind);
+ }
}
// r/w fields that are static or belong to reference types are writeable and returnable
diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs
index 34d94060b1d9c..e91703598a061 100644
--- a/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs
+++ b/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs
@@ -26,6 +26,9 @@ public static RefKind GetRefKind(this BoundExpression node)
case BoundKind.Parameter:
return ((BoundParameter)node).ParameterSymbol.RefKind;
+ case BoundKind.FieldAccess:
+ return ((BoundFieldAccess)node).FieldSymbol.RefKind;
+
case BoundKind.Call:
return ((BoundCall)node).Method.RefKind;
diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx
index 3abfac0e83f71..c5408fa46211a 100644
--- a/src/Compilers/CSharp/Portable/CSharpResources.resx
+++ b/src/Compilers/CSharp/Portable/CSharpResources.resx
@@ -6642,6 +6642,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
struct field initializers
+
+ ref fields
+
variance safety for static interface members
diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs
index 8ac52b47e4f9e..e6c9da8ea9f2f 100644
--- a/src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs
+++ b/src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs
@@ -345,7 +345,7 @@ private LocalDefinition EmitSequenceAddress(BoundSequence sequence, AddressKind
return result;
}
- private LocalSymbol DigForValueLocal(BoundSequence topSequence, BoundExpression value)
+ private static LocalSymbol DigForValueLocal(BoundSequence topSequence, BoundExpression value)
{
switch (value.Kind)
{
@@ -546,7 +546,7 @@ private LocalDefinition EmitInstanceFieldAddress(BoundFieldAccess fieldAccess, A
// taking field addresses, so we have to turn Constrained into writeable.
var tempOpt = EmitReceiverRef(fieldAccess.ReceiverOpt, addressKind == AddressKind.Constrained ? AddressKind.Writeable : addressKind);
- _builder.EmitOpCode(ILOpCode.Ldflda);
+ _builder.EmitOpCode(field.RefKind == RefKind.None ? ILOpCode.Ldflda : ILOpCode.Ldfld);
EmitSymbolToken(field, fieldAccess.Syntax);
// when loading an address of a fixed field, we actually
diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs
index f684af2db356c..39b76956cf95b 100644
--- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs
+++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs
@@ -1023,6 +1023,20 @@ private void EmitFieldLoad(BoundFieldAccess fieldAccess, bool used)
Debug.Assert(!field.IsConst || field.ContainingType.SpecialType == SpecialType.System_Decimal,
"rewriter should lower constant fields into constant expressions");
+ EmitFieldLoadNoIndirection(fieldAccess, used);
+
+ if (used && field.RefKind != RefKind.None)
+ {
+ EmitLoadIndirect(field.Type, fieldAccess.Syntax);
+ }
+
+ EmitPopIfUnused(used);
+ }
+
+ private void EmitFieldLoadNoIndirection(BoundFieldAccess fieldAccess, bool used)
+ {
+ var field = fieldAccess.FieldSymbol;
+
// static field access is sideeffecting since it guarantees that ..ctor has run.
// we emit static accesses even if unused.
if (field.IsStatic)
@@ -1062,7 +1076,6 @@ private void EmitFieldLoad(BoundFieldAccess fieldAccess, bool used)
EmitSymbolToken(field, fieldAccess.Syntax);
}
}
- EmitPopIfUnused(used);
}
private LocalDefinition EmitFieldLoadReceiver(BoundExpression receiver)
@@ -1128,10 +1141,10 @@ private bool EmitFieldLoadReceiverAddress(BoundExpression receiver)
return false;
}
- // ldfld can work with structs directly or with their addresses
+ // ldfld can work with structs directly or with their addresses.
// In some cases it results in same native code emitted, but in some cases JIT pushes values for real
// resulting in much worse code (on x64 in particular).
- // So, we will always prefer references here except when receiver is a struct non-ref local or parameter.
+ // So, we will always prefer references here except when receiver is a struct, non-ref local, or parameter.
private bool FieldLoadPrefersRef(BoundExpression receiver)
{
// only fields of structs can be accessed via value
@@ -2089,7 +2102,7 @@ private void EmitAssignmentExpression(BoundAssignmentOperator assignmentOperator
EmitAssignmentPostfix(assignmentOperator, temp, useKind);
}
- // sometimes it is possible and advantageous to get an address of the lHS and
+ // sometimes it is possible and advantageous to get an address of the LHS and
// perform assignment as an in-place initialization via initobj or constructor invocation.
//
// 1) initobj
@@ -2309,10 +2322,15 @@ private bool EmitAssignmentPreamble(BoundAssignmentOperator assignmentOperator)
case BoundKind.FieldAccess:
{
var left = (BoundFieldAccess)assignmentTarget;
- if (!left.FieldSymbol.IsStatic)
+ if (left.FieldSymbol.RefKind != RefKind.None &&
+ !assignmentOperator.IsRef)
+ {
+ EmitFieldLoadNoIndirection(left, used: true);
+ }
+ else if (!left.FieldSymbol.IsStatic)
{
var temp = EmitReceiverRef(left.ReceiverOpt, AddressKind.Writeable);
- Debug.Assert(temp == null, "temp is unexpected when assigning to a field");
+ FreeOptTemp(temp);
lhsUsesStack = true;
}
}
@@ -2586,7 +2604,7 @@ private void EmitStore(BoundAssignmentOperator assignment)
switch (expression.Kind)
{
case BoundKind.FieldAccess:
- EmitFieldStore((BoundFieldAccess)expression);
+ EmitFieldStore((BoundFieldAccess)expression, assignment.IsRef);
break;
case BoundKind.Local:
@@ -2794,7 +2812,7 @@ private void EmitVectorElementStore(ArrayTypeSymbol arrayType, SyntaxNode syntax
}
}
- private void EmitFieldStore(BoundFieldAccess fieldAccess)
+ private void EmitFieldStore(BoundFieldAccess fieldAccess, bool refAssign)
{
var field = fieldAccess.FieldSymbol;
@@ -2803,14 +2821,21 @@ private void EmitFieldStore(BoundFieldAccess fieldAccess)
_builder.EmitOpCode(ILOpCode.Volatile);
}
- _builder.EmitOpCode(field.IsStatic ? ILOpCode.Stsfld : ILOpCode.Stfld);
- EmitSymbolToken(field, fieldAccess.Syntax);
+ if (field.RefKind != RefKind.None && !refAssign)
+ {
+ //NOTE: we should have the actual field already loaded,
+ //now need to do a store to where it points to
+ EmitIndirectStore(field.Type, fieldAccess.Syntax);
+ }
+ else
+ {
+ _builder.EmitOpCode(field.IsStatic ? ILOpCode.Stsfld : ILOpCode.Stfld);
+ EmitSymbolToken(field, fieldAccess.Syntax);
+ }
}
private void EmitParameterStore(BoundParameter parameter, bool refAssign)
{
- int slot = ParameterSlot(parameter);
-
if (parameter.ParameterSymbol.RefKind != RefKind.None && !refAssign)
{
//NOTE: we should have the actual parameter already loaded,
@@ -2819,6 +2844,7 @@ private void EmitParameterStore(BoundParameter parameter, bool refAssign)
}
else
{
+ int slot = ParameterSlot(parameter);
_builder.EmitStoreArgumentOpcode(slot);
}
}
diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs
index 2908e3983433d..8e1be7fbd5144 100644
--- a/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs
+++ b/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs
@@ -1034,6 +1034,7 @@ private void EmitCatchBlock(BoundCatchBlock catchBlock)
var left = (BoundFieldAccess)exceptionSource;
Debug.Assert(!left.FieldSymbol.IsStatic, "Not supported");
Debug.Assert(!left.ReceiverOpt.Type.IsTypeParameter());
+ Debug.Assert(left.FieldSymbol.RefKind == RefKind.None);
var stateMachineField = left.FieldSymbol as StateMachineFieldSymbol;
if (((object)stateMachineField != null) && (stateMachineField.SlotIndex >= 0))
@@ -1052,7 +1053,7 @@ private void EmitCatchBlock(BoundCatchBlock catchBlock)
_builder.EmitLocalLoad(temp);
FreeTemp(temp);
- EmitFieldStore(left);
+ EmitFieldStore(left, refAssign: false);
break;
default:
diff --git a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs
index 0c6ba8d2c5d56..b6efd8b6901fb 100644
--- a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs
+++ b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs
@@ -1022,8 +1022,7 @@ private static bool IsIndirectAssignment(BoundAssignmentOperator node)
var lhs = node.Left;
Debug.Assert(!node.IsRef ||
- (lhs is BoundLocal local && local.LocalSymbol.RefKind != RefKind.None) ||
- (lhs is BoundParameter param && param.ParameterSymbol.RefKind != RefKind.None),
+ (lhs.Kind is BoundKind.Local or BoundKind.Parameter or BoundKind.FieldAccess && lhs.GetRefKind() != RefKind.None),
"only ref symbols can be a target of a ref assignment");
switch (lhs.Kind)
@@ -2055,7 +2054,7 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node)
}
// indirect local store is not special. (operands still could be rewritten)
- // NOTE: if Lhs is a stack local, it will be handled as a read and possibly duped.
+ // NOTE: if lhs is a stack local, it will be handled as a read and possibly duped.
var isIndirectLocalStore = left.LocalSymbol.RefKind != RefKind.None && !node.IsRef;
if (isIndirectLocalStore)
{
diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/FieldSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/FieldSymbolAdapter.cs
index 78c10edb9912a..fdb3762ede5bd 100644
--- a/src/Compilers/CSharp/Portable/Emitter/Model/FieldSymbolAdapter.cs
+++ b/src/Compilers/CSharp/Portable/Emitter/Model/FieldSymbolAdapter.cs
@@ -47,6 +47,11 @@ Cci.ITypeReference Cci.IFieldReference.GetType(EmitContext context)
}
}
+ ImmutableArray Cci.IFieldReference.RefCustomModifiers =>
+ ImmutableArray.CastUp(AdaptedFieldSymbol.RefCustomModifiers);
+
+ bool Cci.IFieldReference.IsByReference => AdaptedFieldSymbol.RefKind != RefKind.None;
+
Cci.IFieldDefinition Cci.IFieldReference.GetResolvedField(EmitContext context)
{
return ResolvedFieldImpl((PEModuleBuilder)context.Module);
diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedFieldReference.cs b/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedFieldReference.cs
index 3952875053d5a..ec96a3aa8613a 100644
--- a/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedFieldReference.cs
+++ b/src/Compilers/CSharp/Portable/Emitter/Model/SpecializedFieldReference.cs
@@ -74,6 +74,11 @@ Cci.ITypeReference Cci.IFieldReference.GetType(EmitContext context)
}
}
+ ImmutableArray Cci.IFieldReference.RefCustomModifiers =>
+ ImmutableArray.CastUp(_underlyingField.RefCustomModifiers);
+
+ bool Cci.IFieldReference.IsByReference => _underlyingField.RefKind != RefKind.None;
+
Cci.IFieldDefinition Cci.IFieldReference.GetResolvedField(EmitContext context)
{
return null;
diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs
index ffa2463e8c13d..5e4841b3ec838 100644
--- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs
+++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs
@@ -245,6 +245,7 @@ internal enum MessageID
IDS_FeatureCacheStaticMethodGroupConversion = MessageBase + 12816,
IDS_FeatureRawStringLiterals = MessageBase + 12817,
IDS_FeatureDisposalPattern = MessageBase + 12818,
+ IDS_FeatureRefFields = MessageBase + 12819,
}
// Message IDs may refer to strings that need to be localized.
@@ -361,6 +362,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
case MessageID.IDS_FeatureListPattern: // semantic check
case MessageID.IDS_FeatureCacheStaticMethodGroupConversion: // lowering check
case MessageID.IDS_ParameterNullChecking: // syntax check
+ case MessageID.IDS_FeatureRefFields: // semantic check
return LanguageVersion.Preview;
// C# 10.0 features.
diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs
index cc988be9fa1c0..654c0671d6cf6 100644
--- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs
+++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs
@@ -221,17 +221,8 @@ private BoundExpression MakeStaticAssignmentOperator(
}
case BoundKind.Local:
- {
- Debug.Assert(!isRef || ((BoundLocal)rewrittenLeft).LocalSymbol.RefKind != RefKind.None);
- return new BoundAssignmentOperator(
- syntax,
- rewrittenLeft,
- rewrittenRight,
- type,
- isRef: isRef);
- }
-
case BoundKind.Parameter:
+ case BoundKind.FieldAccess:
{
Debug.Assert(!isRef || rewrittenLeft.GetRefKind() != RefKind.None);
return new BoundAssignmentOperator(
diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
index b92860f4fa14b..2248ed5abc74f 100644
--- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
+++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
@@ -2874,7 +2874,7 @@ private MemberDeclarationSyntax ParseMemberDeclarationCore(SyntaxKind parentKind
parse_member_name:;
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt;
- // If we've seen the ref keyword, we know we must have an indexer, method, or property.
+ // If we've seen the ref keyword, we know we must have an indexer, method, field, or property.
if (type.Kind != SyntaxKind.RefType)
{
// Check here for operators
@@ -2883,11 +2883,11 @@ private MemberDeclarationSyntax ParseMemberDeclarationCore(SyntaxKind parentKind
{
return this.ParseOperatorDeclaration(attributes, modifiers, type, explicitInterfaceOpt);
}
+ }
- if (IsFieldDeclaration(isEvent: false))
- {
- return this.ParseNormalFieldDeclaration(attributes, modifiers, type, parentKind);
- }
+ if (IsFieldDeclaration(isEvent: false))
+ {
+ return this.ParseNormalFieldDeclaration(attributes, modifiers, type, parentKind);
}
// At this point we can either have indexers, methods, or
diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.cs
index 5b806987fc8cc..989efe828c1a0 100644
--- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.cs
+++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.cs
@@ -35,6 +35,18 @@ public override void VisitField(IFieldSymbol symbol)
this.isFirstSymbolVisited &&
!IsEnumMember(symbol))
{
+ switch (symbol.RefKind)
+ {
+ case RefKind.Ref:
+ AddRefIfRequired();
+ break;
+ case RefKind.RefReadOnly:
+ AddRefReadonlyIfRequired();
+ break;
+ }
+
+ AddCustomModifiersIfRequired(symbol.RefCustomModifiers);
+
VisitFieldType(symbol);
AddSpace();
diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.FieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.FieldSymbol.cs
index 10010ac113676..2088138bf8116 100644
--- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.FieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.FieldSymbol.cs
@@ -32,6 +32,10 @@ internal override TypeWithAnnotations GetFieldType(ConsList fieldsB
return _property.TypeWithAnnotations;
}
+ public override RefKind RefKind => RefKind.None;
+
+ public override ImmutableArray RefCustomModifiers => ImmutableArray.Empty;
+
public override string Name
{
get { return GeneratedNames.MakeAnonymousTypeBackingFieldName(_property.Name); }
diff --git a/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs
index b0b29af001f44..9df43536b764b 100644
--- a/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs
@@ -63,6 +63,10 @@ public TypeWithAnnotations TypeWithAnnotations
}
}
+ public abstract RefKind RefKind { get; }
+
+ public abstract ImmutableArray RefCustomModifiers { get; }
+
public abstract FlowAnalysisAnnotations FlowAnalysisAnnotations { get; }
///
diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/MemberRefMetadataDecoder.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/MemberRefMetadataDecoder.cs
index 027115bf4bc27..b4679a94d94b9 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/MemberRefMetadataDecoder.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/MemberRefMetadataDecoder.cs
@@ -138,9 +138,9 @@ internal Symbol FindMember(MemberReferenceHandle memberRef, bool methodsOnly)
return null;
}
- ImmutableArray> customModifiers;
- TypeSymbol type = this.DecodeFieldSignature(ref signaturePointer, out customModifiers);
- return FindFieldBySignature(_containingType, memberName, customModifiers, type);
+ FieldInfo fieldInfo;
+ this.DecodeFieldSignature(ref signaturePointer, out fieldInfo);
+ return FindFieldBySignature(_containingType, memberName, fieldInfo.CustomModifiers, fieldInfo.Type);
default:
// error: unexpected calling convention
diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEFieldSymbol.cs
index 6895447a75757..27e3a2eab6d8f 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEFieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEFieldSymbol.cs
@@ -29,15 +29,20 @@ internal sealed class PEFieldSymbol : FieldSymbol
private struct PackedFlags
{
// Layout:
- // |..............................|vvvvv|
+ // |..........................|rr|v|fffff|
//
// f = FlowAnalysisAnnotations. 5 bits (4 value bits + 1 completion bit).
+ // v = IsVolatile 1 bit
+ // r = RefKind 2 bits
private const int HasDisallowNullAttribute = 0x1 << 0;
private const int HasAllowNullAttribute = 0x1 << 1;
private const int HasMaybeNullAttribute = 0x1 << 2;
private const int HasNotNullAttribute = 0x1 << 3;
private const int FlowAnalysisAnnotationsCompletionBit = 0x1 << 4;
+ private const int IsVolatileBit = 0x1 << 5;
+ private const int RefKindOffset = 6;
+ private const int RefKindMask = 0x3;
private int _bits;
@@ -67,13 +72,29 @@ public bool TryGetFlowAnalysisAnnotations(out FlowAnalysisAnnotations value)
Debug.Assert(value == 0 || result);
return result;
}
+
+ public void SetIsVolatile(bool isVolatile)
+ {
+ if (isVolatile) ThreadSafeFlagOperations.Set(ref _bits, IsVolatileBit);
+ Debug.Assert(IsVolatile == isVolatile);
+ }
+
+ public bool IsVolatile => (_bits & IsVolatileBit) != 0;
+
+ public void SetRefKind(RefKind refKind)
+ {
+ int bits = ((int)refKind & RefKindMask) << RefKindOffset;
+ if (bits != 0) ThreadSafeFlagOperations.Set(ref _bits, bits);
+ Debug.Assert(RefKind == refKind);
+ }
+
+ public RefKind RefKind => (RefKind)((_bits >> RefKindOffset) & RefKindMask);
}
private readonly FieldDefinitionHandle _handle;
private readonly string _name;
private readonly FieldAttributes _flags;
private readonly PENamedTypeSymbol _containingType;
- private bool _lazyIsVolatile;
private ImmutableArray _lazyCustomAttributes;
private ConstantValue _lazyConstantValue = Microsoft.CodeAnalysis.ConstantValue.Unset; // Indicates an uninitialized ConstantValue
private Tuple _lazyDocComment;
@@ -86,6 +107,7 @@ public bool TryGetFlowAnalysisAnnotations(out FlowAnalysisAnnotations value)
private NamedTypeSymbol _lazyFixedImplementationType;
private PEEventSymbol _associatedEventOpt;
private PackedFlags _packedFlags;
+ private ImmutableArray _lazyRefCustomModifiers;
internal PEFieldSymbol(
PEModuleSymbol moduleSymbol,
@@ -261,9 +283,10 @@ private void EnsureSignatureIsLoaded()
if (_lazyType == null)
{
var moduleSymbol = _containingType.ContainingPEModule;
- ImmutableArray> customModifiers;
- TypeSymbol typeSymbol = (new MetadataDecoder(moduleSymbol, _containingType)).DecodeFieldSignature(_handle, out customModifiers);
- ImmutableArray customModifiersArray = CSharpCustomModifier.Convert(customModifiers);
+ FieldInfo fieldInfo;
+ (new MetadataDecoder(moduleSymbol, _containingType)).DecodeFieldSignature(_handle, out fieldInfo);
+ TypeSymbol typeSymbol = fieldInfo.Type;
+ ImmutableArray customModifiersArray = CSharpCustomModifier.Convert(fieldInfo.CustomModifiers);
typeSymbol = DynamicTypeDecoder.TransformType(typeSymbol, customModifiersArray.Length, _handle, moduleSymbol);
typeSymbol = NativeIntegerTypeDecoder.TransformType(typeSymbol, _handle, moduleSymbol);
@@ -276,7 +299,11 @@ private void EnsureSignatureIsLoaded()
type = NullableTypeDecoder.TransformType(type, _handle, moduleSymbol, accessSymbol: this, nullableContext: _containingType);
type = TupleTypeDecoder.DecodeTupleTypesIfApplicable(type, _handle, moduleSymbol);
- _lazyIsVolatile = customModifiersArray.Any(m => !m.IsOptional && ((CSharpCustomModifier)m).ModifierSymbol.SpecialType == SpecialType.System_Runtime_CompilerServices_IsVolatile);
+ RefKind refKind = fieldInfo.IsByRef ?
+ moduleSymbol.Module.HasIsReadOnlyAttribute(_handle) ? RefKind.RefReadOnly : RefKind.Ref :
+ RefKind.None;
+ _packedFlags.SetRefKind(refKind);
+ _packedFlags.SetIsVolatile(customModifiersArray.Any(m => !m.IsOptional && ((CSharpCustomModifier)m).ModifierSymbol.SpecialType == SpecialType.System_Runtime_CompilerServices_IsVolatile));
TypeSymbol fixedElementType;
int fixedSize;
@@ -287,6 +314,8 @@ private void EnsureSignatureIsLoaded()
type = TypeWithAnnotations.Create(new PointerTypeSymbol(TypeWithAnnotations.Create(fixedElementType)));
}
+ ImmutableInterlocked.InterlockedInitialize(ref _lazyRefCustomModifiers, CSharpCustomModifier.Convert(fieldInfo.RefCustomModifiers));
+
Interlocked.CompareExchange(ref _lazyType, new TypeWithAnnotations.Boxed(type), null);
}
}
@@ -322,6 +351,24 @@ private PEModuleSymbol ContainingPEModule
}
}
+ public override RefKind RefKind
+ {
+ get
+ {
+ EnsureSignatureIsLoaded();
+ return _packedFlags.RefKind;
+ }
+ }
+
+ public override ImmutableArray RefCustomModifiers
+ {
+ get
+ {
+ EnsureSignatureIsLoaded();
+ return _lazyRefCustomModifiers;
+ }
+ }
+
internal override TypeWithAnnotations GetFieldType(ConsList fieldsBeingBound)
{
EnsureSignatureIsLoaded();
@@ -397,7 +444,7 @@ public override bool IsVolatile
get
{
EnsureSignatureIsLoaded();
- return _lazyIsVolatile;
+ return _packedFlags.IsVolatile;
}
}
diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs
index df6df4a607427..dff15bddbac2a 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs
@@ -1151,10 +1151,11 @@ private void EnsureEnumUnderlyingTypeIsLoaded(UncommonProperties uncommon)
if ((fieldFlags & FieldAttributes.Static) == 0)
{
// Instance field used to determine underlying type.
- ImmutableArray> customModifiers;
- TypeSymbol type = decoder.DecodeFieldSignature(fieldDef, out customModifiers);
+ FieldInfo fieldInfo;
+ decoder.DecodeFieldSignature(fieldDef, out fieldInfo);
+ TypeSymbol type = fieldInfo.Type;
- if (type.SpecialType.IsValidEnumUnderlyingType() && !customModifiers.AnyRequired())
+ if (type.SpecialType.IsValidEnumUnderlyingType() && !fieldInfo.CustomModifiers.AnyRequired())
{
if ((object)underlyingType == null)
{
diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/FieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/FieldSymbol.cs
index e277d95d12fd5..cb9ee2caa91b3 100644
--- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/FieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/FieldSymbol.cs
@@ -31,6 +31,10 @@ ISymbol IFieldSymbol.AssociatedSymbol
}
}
+ RefKind IFieldSymbol.RefKind => _underlying.RefKind;
+
+ ImmutableArray IFieldSymbol.RefCustomModifiers => _underlying.RefCustomModifiers;
+
ITypeSymbol IFieldSymbol.Type
{
get
diff --git a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingFieldSymbol.cs
index a087f842c7370..68f9f0b5a1277 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingFieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingFieldSymbol.cs
@@ -70,6 +70,11 @@ public override Symbol ContainingSymbol
}
}
+ public override RefKind RefKind => _underlyingField.RefKind;
+
+ public override ImmutableArray RefCustomModifiers =>
+ this.RetargetingTranslator.RetargetModifiers(_underlyingField.RefCustomModifiers, out _);
+
public override ImmutableArray GetAttributes()
{
return this.RetargetingTranslator.GetRetargetedAttributes(_underlyingField.GetAttributes(), ref _lazyCustomAttributes);
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/FieldSymbolWithAttributesAndModifiers.cs b/src/Compilers/CSharp/Portable/Symbols/Source/FieldSymbolWithAttributesAndModifiers.cs
index e9d21aff2101a..62a5f753e9780 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/FieldSymbolWithAttributesAndModifiers.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/FieldSymbolWithAttributesAndModifiers.cs
@@ -382,6 +382,11 @@ internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, r
{
base.AddSynthesizedAttributes(moduleBuilder, ref attributes);
+ if (this.RefKind == RefKind.RefReadOnly)
+ {
+ AddSynthesizedAttribute(ref attributes, moduleBuilder.SynthesizeIsReadOnlyAttribute(this));
+ }
+
var compilation = this.DeclaringCompilation;
var type = this.TypeWithAnnotations;
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/GlobalExpressionVariable.cs b/src/Compilers/CSharp/Portable/Symbols/Source/GlobalExpressionVariable.cs
index 4eb05ee871838..47deee5399c82 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/GlobalExpressionVariable.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/GlobalExpressionVariable.cs
@@ -64,6 +64,8 @@ protected override ConstantValue MakeConstantValue(
bool earlyDecodingWellKnownAttributes,
BindingDiagnosticBag diagnostics) => null;
+ public sealed override RefKind RefKind => RefKind.None;
+
internal override TypeWithAnnotations GetFieldType(ConsList fieldsBeingBound)
{
Debug.Assert(fieldsBeingBound != null);
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceEnumConstantSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceEnumConstantSymbol.cs
index 79f64f81d2439..d7d45bf347a93 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceEnumConstantSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceEnumConstantSymbol.cs
@@ -56,6 +56,8 @@ protected SourceEnumConstantSymbol(SourceMemberContainerTypeSymbol containingEnu
}
}
+ public sealed override RefKind RefKind => RefKind.None;
+
internal override TypeWithAnnotations GetFieldType(ConsList fieldsBeingBound)
{
return TypeWithAnnotations.Create(this.ContainingType);
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs
index 08ab93bd28469..4fa611fb7de63 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs
@@ -94,6 +94,10 @@ protected ImmutableArray RequiredCustomModifiers
}
}
+ // Currently, source symbols cannot declare RefCustomModifiers. If that changes, and this
+ // property is updated, test retargeting. (Update RefFieldTests.RetargetingField for instance.)
+ public sealed override ImmutableArray RefCustomModifiers => ImmutableArray.Empty;
+
public sealed override Symbol ContainingSymbol
{
get
@@ -135,6 +139,11 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions,
var compilation = DeclaringCompilation;
var location = ErrorLocation;
+ if (RefKind == RefKind.RefReadOnly)
+ {
+ compilation.EnsureIsReadOnlyAttributeExists(diagnostics, location, modifyCompilation: true);
+ }
+
if (Type.ContainsNativeInteger())
{
compilation.EnsureNativeIntegerAttributeExists(diagnostics, location, modifyCompilation: true);
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs
index b2b6cdf5584d1..8129a3fe7eb9e 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs
@@ -279,7 +279,19 @@ internal class SourceMemberFieldSymbolFromDeclarator : SourceMemberFieldSymbol
{
private readonly bool _hasInitializer;
- private TypeWithAnnotations.Boxed _lazyType;
+ private sealed class TypeAndRefKind
+ {
+ internal readonly RefKind RefKind;
+ internal readonly TypeWithAnnotations Type;
+
+ internal TypeAndRefKind(RefKind refKind, TypeWithAnnotations type)
+ {
+ RefKind = refKind;
+ Type = type;
+ }
+ }
+
+ private TypeAndRefKind _lazyTypeAndRefKind;
// Non-zero if the type of the field has been inferred from the type of its initializer expression
// and the errors of binding the initializer have been or are being reported to compilation diagnostics.
@@ -367,13 +379,15 @@ protected override SyntaxList AttributeDeclarationSyntaxLis
}
}
+ public sealed override RefKind RefKind => GetTypeAndRefKind(ConsList.Empty).RefKind;
+
internal override bool HasPointerType
{
get
{
- if (_lazyType != null)
+ if (_lazyTypeAndRefKind?.Type.DefaultType is { } defaultType)
{
- bool isPointerType = _lazyType.Value.DefaultType.Kind switch
+ bool isPointerType = defaultType.Kind switch
{
SymbolKind.PointerType => true,
SymbolKind.FunctionPointerType => true,
@@ -400,12 +414,17 @@ private bool IsPointerFieldSyntactically()
}
internal sealed override TypeWithAnnotations GetFieldType(ConsList fieldsBeingBound)
+ {
+ return GetTypeAndRefKind(fieldsBeingBound).Type;
+ }
+
+ private TypeAndRefKind GetTypeAndRefKind(ConsList fieldsBeingBound)
{
Debug.Assert(fieldsBeingBound != null);
- if (_lazyType != null)
+ if (_lazyTypeAndRefKind != null)
{
- return _lazyType.Value;
+ return _lazyTypeAndRefKind;
}
var declarator = VariableDeclaratorNode;
@@ -415,6 +434,7 @@ internal sealed override TypeWithAnnotations GetFieldType(ConsList
var compilation = this.DeclaringCompilation;
var diagnostics = BindingDiagnosticBag.GetInstance();
+ RefKind refKind = RefKind.None;
TypeWithAnnotations type;
// When we have multiple declarators, we report the type diagnostics on only the first.
@@ -446,7 +466,13 @@ internal sealed override TypeWithAnnotations GetFieldType(ConsList
binder = binder.WithAdditionalFlagsAndContainingMemberOrLambda(BinderFlags.SuppressConstraintChecks, this);
if (!ContainingType.IsScriptClass)
{
- type = binder.BindType(typeSyntax, diagnosticsForFirstDeclarator);
+ var typeOnly = typeSyntax.SkipRef(out refKind);
+ Debug.Assert(refKind is RefKind.None or RefKind.Ref or RefKind.RefReadOnly);
+ if (refKind != RefKind.None)
+ {
+ MessageID.IDS_FeatureRefFields.CheckFeatureAvailability(diagnostics, compilation, typeSyntax.Location);
+ }
+ type = binder.BindType(typeOnly, diagnosticsForFirstDeclarator);
}
else
{
@@ -530,7 +556,7 @@ internal sealed override TypeWithAnnotations GetFieldType(ConsList
Debug.Assert(type.DefaultType.IsPointerOrFunctionPointer() == IsPointerFieldSyntactically());
// update the lazyType only if it contains value last seen by the current thread:
- if (Interlocked.CompareExchange(ref _lazyType, new TypeWithAnnotations.Boxed(type.WithModifiers(this.RequiredCustomModifiers)), null) == null)
+ if (Interlocked.CompareExchange(ref _lazyTypeAndRefKind, new TypeAndRefKind(refKind, type.WithModifiers(this.RequiredCustomModifiers)), null) == null)
{
TypeChecks(type.Type, diagnostics);
@@ -548,7 +574,7 @@ internal sealed override TypeWithAnnotations GetFieldType(ConsList
diagnostics.Free();
diagnosticsForFirstDeclarator.Free();
- return _lazyType.Value;
+ return _lazyTypeAndRefKind;
}
internal bool FieldTypeInferred(ConsList fieldsBeingBound)
diff --git a/src/Compilers/CSharp/Portable/Symbols/SubstitutedFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SubstitutedFieldSymbol.cs
index d957d20bfbe9c..0fb488352ade3 100644
--- a/src/Compilers/CSharp/Portable/Symbols/SubstitutedFieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/SubstitutedFieldSymbol.cs
@@ -103,6 +103,11 @@ internal override NamedTypeSymbol FixedImplementationType(PEModuleBuilder emitMo
return (NamedTypeSymbol)_containingType.TypeSubstitution.SubstituteType(OriginalDefinition.FixedImplementationType(emitModule)).Type;
}
+ public override RefKind RefKind => _underlyingField.RefKind;
+
+ public override ImmutableArray RefCustomModifiers =>
+ _containingType.TypeSubstitution.SubstituteCustomModifiers(_underlyingField.RefCustomModifiers);
+
public override bool Equals(Symbol obj, TypeCompareKind compareKind)
{
if ((object)this == obj)
diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs
index 6001d8b361c51..635eec12c26b7 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs
@@ -31,6 +31,7 @@ public SynthesizedBackingFieldSymbol(
bool hasInitializer)
{
Debug.Assert(!string.IsNullOrEmpty(name));
+ Debug.Assert(property.RefKind is RefKind.None or RefKind.Ref or RefKind.RefReadOnly);
_name = name;
@@ -57,6 +58,10 @@ public override Symbol AssociatedSymbol
public override ImmutableArray Locations
=> _property.Locations;
+ public override RefKind RefKind => _property.RefKind;
+
+ public override ImmutableArray RefCustomModifiers => _property.RefCustomModifiers;
+
internal override TypeWithAnnotations GetFieldType(ConsList fieldsBeingBound)
=> _property.TypeWithAnnotations;
diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedFieldSymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedFieldSymbolBase.cs
index 73a6dacbcf7ce..7355017307232 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedFieldSymbolBase.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedFieldSymbolBase.cs
@@ -86,6 +86,10 @@ internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, r
internal abstract override TypeWithAnnotations GetFieldType(ConsList fieldsBeingBound);
+ public sealed override RefKind RefKind => RefKind.None;
+
+ public sealed override ImmutableArray RefCustomModifiers => ImmutableArray.Empty;
+
public override FlowAnalysisAnnotations FlowAnalysisAnnotations
=> FlowAnalysisAnnotations.None;
diff --git a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs
index 44a1763685642..2ed15d863ba7c 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs
@@ -121,6 +121,10 @@ public sealed override Symbol ContainingSymbol
}
}
+ public sealed override RefKind RefKind => RefKind.None;
+
+ public sealed override ImmutableArray RefCustomModifiers => ImmutableArray.Empty;
+
internal override TypeWithAnnotations GetFieldType(ConsList fieldsBeingBound)
{
return _underlyingField.GetFieldType(fieldsBeingBound);
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
index f68dcd3be5136..4196abcb9fbec 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
@@ -1482,6 +1482,11 @@
struktury záznamů
'record structs' is not localizable.
+
+
+ ref fields
+
+
zapečetěný ToString v záznamu
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
index 508a996c17c0b..ea3d8c9d865c5 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
@@ -1482,6 +1482,11 @@
Datensatzstrukturen
'record structs' is not localizable.
+
+
+ ref fields
+
+
versiegelte "ToString" im Datensatz
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
index 2908ff4afb81a..17cac02bfb70f 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
@@ -1482,6 +1482,11 @@
registros
'record structs' is not localizable.
+
+
+ ref fields
+
+
ToString sellado en el registro
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
index bbca3ed9d5fdd..7dba4dfa3e488 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
@@ -1482,6 +1482,11 @@
structs d’enregistrement
'record structs' is not localizable.
+
+
+ ref fields
+
+
ToString scellé dans l’enregistrement
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
index cbab7dce34dbc..52fe2b9f2b6cc 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
@@ -1482,6 +1482,11 @@
struct di record
'record structs' is not localizable.
+
+
+ ref fields
+
+
ToString sealed nel record
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
index 794537811a0e8..f4918e2d875c3 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
@@ -1482,6 +1482,11 @@
レコード構造体
'record structs' is not localizable.
+
+
+ ref fields
+
+
レコードでシールされた ToString
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
index 6e2f95eade78b..42bbe0e3fe2e1 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
@@ -1482,6 +1482,11 @@
레코드 구조체
'record structs' is not localizable.
+
+
+ ref fields
+
+
레코드의 봉인된 ToString
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
index c18e47055be97..b093e2e5a6ebc 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
@@ -1482,6 +1482,11 @@
struktury rekordów
'record structs' is not localizable.
+
+
+ ref fields
+
+
zapieczętowany obiekt ToString w rekordzie
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
index fd93d2f204ded..4592073c49f76 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
@@ -1482,6 +1482,11 @@
registrar structs
'record structs' is not localizable.
+
+
+ ref fields
+
+
ToString selado no registro
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
index 1a91c5e5625ec..0c0a1b4fb69ec 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
@@ -1482,6 +1482,11 @@
структуры записей
'record structs' is not localizable.
+
+
+ ref fields
+
+
запечатанный ToString в записи
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
index 9f7b4fb609c52..4b0410d40011d 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
@@ -1482,6 +1482,11 @@
kayıt yapıları
'record structs' is not localizable.
+
+
+ ref fields
+
+
kayıtta mühürlü ToString
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
index 01d54adcd19b0..2b33dbfc20ed1 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
@@ -1482,6 +1482,11 @@
记录结构
'record structs' is not localizable.
+
+
+ ref fields
+
+
记录的密封 ToString
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
index 85cdecf84ae38..e3dc711f68e6d 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
@@ -1482,6 +1482,11 @@
記錄結構
'record structs' is not localizable.
+
+
+ ref fields
+
+
記錄中有密封的 ToString
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs
new file mode 100644
index 0000000000000..175be0159d53e
--- /dev/null
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs
@@ -0,0 +1,1891 @@
+// 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.
+
+#nullable disable
+
+using Microsoft.CodeAnalysis.CSharp.Symbols;
+using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting;
+using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Roslyn.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.CSharp.UnitTests
+{
+ public class RefFieldTests : CSharpTestBase
+ {
+ private static string IncludeExpectedOutput(string expectedOutput)
+ {
+ // PROTOTYPE: Enable.
+#if RuntimeSupport
+ return expectedOutput;
+#else
+ return null;
+#endif
+ }
+
+ [CombinatorialData]
+ [Theory]
+ public void RefField(bool useCompilationReference)
+ {
+ var sourceA =
+@"public ref struct S
+{
+ public ref T F;
+ public S(ref T t) { F = ref t; }
+}";
+ var comp = CreateCompilation(sourceA, parseOptions: TestOptions.Regular10);
+ comp.VerifyEmitDiagnostics(
+ // (3,12): error CS8652: The feature 'ref fields' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public ref T F;
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "ref T").WithArguments("ref fields").WithLocation(3, 12));
+
+ var field = comp.GetMember("S.F");
+ Assert.Equal(RefKind.Ref, field.RefKind);
+ Assert.Equal("ref T S.F", field.ToTestDisplayString());
+
+ comp = CreateCompilation(sourceA);
+ comp.VerifyEmitDiagnostics();
+ var refA = AsReference(comp, useCompilationReference);
+
+ field = comp.GetMember("S.F");
+ Assert.Equal(RefKind.Ref, field.RefKind);
+ Assert.Equal("ref T S.F", field.ToTestDisplayString());
+
+ var sourceB =
+@"using System;
+class Program
+{
+ static void Main()
+ {
+ int x = 1;
+ var s = new S(ref x);
+ s.F = 2;
+ Console.WriteLine(s.F);
+ Console.WriteLine(x);
+ x = 3;
+ Console.WriteLine(s.F);
+ Console.WriteLine(x);
+ }
+}";
+ // PROTOTYPE: Use of ref field should be tied to -langversion:preview,
+ // for field from metadata or compilation reference.
+ var verifier = CompileAndVerify(sourceB, references: new[] { refA }, parseOptions: TestOptions.RegularNext, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput(
+@"2
+2
+3
+3
+"));
+ comp = (CSharpCompilation)verifier.Compilation;
+
+ field = comp.GetMember("S.F");
+ Assert.Equal(RefKind.Ref, field.RefKind);
+ Assert.Equal("ref T S.F", field.ToTestDisplayString());
+ }
+
+ [CombinatorialData]
+ [Theory]
+ public void RefReadonlyField(bool useCompilationReference)
+ {
+ var sourceA =
+@"public ref struct S
+{
+ public ref readonly T F;
+ public S(in T t)
+ {
+ F = ref t;
+ }
+}";
+ var comp = CreateCompilation(sourceA, parseOptions: TestOptions.Regular10);
+ comp.VerifyEmitDiagnostics(
+ // (3,12): error CS8652: The feature 'ref fields' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public ref readonly T F;
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "ref readonly T").WithArguments("ref fields").WithLocation(3, 12));
+
+ var field = comp.GetMember("S.F");
+ Assert.Equal(RefKind.RefReadOnly, field.RefKind);
+ Assert.Equal("ref readonly T S.F", field.ToTestDisplayString());
+
+ comp = CreateCompilation(sourceA);
+ comp.VerifyEmitDiagnostics();
+ var refA = AsReference(comp, useCompilationReference);
+
+ field = comp.GetMember("S.F");
+ Assert.Equal(RefKind.RefReadOnly, field.RefKind);
+ Assert.Equal("ref readonly T S.F", field.ToTestDisplayString());
+
+ var sourceB =
+@"using System;
+class A
+{
+ internal int G;
+}
+class Program
+{
+ static void Main()
+ {
+ A a = new A();
+ a.G = 1;
+ var s = new S(in a);
+ s.F.G = 2;
+ Console.WriteLine(s.F.G);
+ Console.WriteLine(a.G);
+ a.G = 3;
+ Console.WriteLine(s.F.G);
+ Console.WriteLine(a.G);
+ }
+}";
+ // PROTOTYPE: Use of ref field should be tied to -langversion:preview,
+ // for field from metadata or compilation reference.
+ var verifier = CompileAndVerify(sourceB, references: new[] { refA }, parseOptions: TestOptions.RegularNext, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput(
+@"2
+2
+3
+3
+"));
+ comp = (CSharpCompilation)verifier.Compilation;
+
+ field = comp.GetMember("S.F");
+ Assert.Equal(RefKind.RefReadOnly, field.RefKind);
+ Assert.Equal("ref readonly T S.F", field.ToTestDisplayString());
+ }
+
+ [Fact]
+ public void SubstitutedField()
+ {
+ var sourceA =
+@".class public A
+{
+ .field public !0& modopt(object) modopt(int8) F
+}";
+ var refA = CompileIL(sourceA);
+
+ var sourceB =
+@"#pragma warning disable 169
+class B
+{
+ static A A;
+}";
+ var comp = CreateCompilation(sourceB, new[] { refA });
+ comp.VerifyEmitDiagnostics();
+ CompileAndVerify(comp);
+
+ var field = (SubstitutedFieldSymbol)comp.GetMember("B.A").Type.GetMember("F");
+ Assert.Equal(RefKind.Ref, field.RefKind);
+ Assert.Equal(new[] { "System.SByte", "System.Object" }, field.RefCustomModifiers.SelectAsArray(m => m.Modifier.ToTestDisplayString()));
+ Assert.Equal("ref modopt(System.SByte) modopt(System.Object) System.Int32 A.F", field.ToTestDisplayString());
+ }
+
+ [Fact]
+ public void RetargetingField()
+ {
+ var sourceA =
+@"public ref struct A
+{
+ public ref readonly int F;
+}
+";
+ var comp = CreateCompilation(sourceA, targetFramework: TargetFramework.Mscorlib40);
+ var refA = comp.ToMetadataReference();
+
+ var sourceB =
+@"#pragma warning disable 169
+ref struct B
+{
+ A A;
+}";
+ comp = CreateCompilation(sourceB, new[] { refA }, targetFramework: TargetFramework.Mscorlib45);
+ comp.VerifyEmitDiagnostics();
+ CompileAndVerify(comp, verify: Verification.Skipped);
+
+ var field = (RetargetingFieldSymbol)comp.GetMember("B.A").Type.GetMember("F");
+ Assert.Equal(RefKind.RefReadOnly, field.RefKind);
+ // Currently, source symbols cannot declare RefCustomModifiers. If that
+ // changes, update this test to verify retargeting of RefCutomModifiers.
+ Assert.Equal(new string[0], field.RefCustomModifiers.SelectAsArray(m => m.Modifier.ToTestDisplayString()));
+ Assert.Equal("ref readonly System.Int32 A.F", field.ToTestDisplayString());
+ }
+
+ [Fact]
+ public void DefiniteAssignment_01()
+ {
+ var source =
+@"ref struct S1
+{
+ public ref T F;
+}
+ref struct S2
+{
+ public ref T F;
+ public S2(ref T t) { }
+}
+ref struct S3
+{
+ public ref T F;
+ public S3(ref T t) : this() { }
+}
+ref struct S4
+{
+ public ref T F;
+ public S4(ref T t)
+ {
+ this = default;
+ }
+}
+class Program
+{
+ static void Main()
+ {
+ int i = 0;
+ new S1().F = ref i;
+ new S2().F = ref i;
+ new S3().F = ref i;
+ new S4().F = ref i;
+ }
+}";
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics(
+ // (8,12): error CS0171: Field 'S2.F' must be fully assigned before control is returned to the caller
+ // public S2(ref T t) { }
+ Diagnostic(ErrorCode.ERR_UnassignedThis, "S2").WithArguments("S2.F").WithLocation(8, 12));
+ }
+
+ // PROTOTYPE: Should we report an error (or warning?) regardless of whether fields
+ // are auto-defaulted because it will result in a NullReferenceException (verify that's true).
+ [Fact]
+ public void DefiniteAssignment_02()
+ {
+ var source =
+@"ref struct S
+{
+ public ref T F;
+ public S(T t)
+ {
+ F = t;
+ }
+}";
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics();
+ }
+
+ [Fact]
+ public void Assignment_Ref()
+ {
+ var source =
+@"ref struct S
+{
+ public ref T F;
+ public S(T tValue, ref T tRef, out T tOut, in T tIn)
+ {
+ tOut = default;
+ F = ref tValue;
+ F = ref tRef;
+ F = ref tOut;
+ F = ref tIn;
+ F = tValue;
+ F = tRef;
+ F = tOut;
+ F = tIn;
+ }
+ object P
+ {
+ init
+ {
+ F = ref GetValue();
+ F = ref GetRef();
+ F = ref GetRefReadonly();
+ F = GetValue();
+ F = GetRef();
+ F = GetRefReadonly();
+ }
+ }
+ static T GetValue() => throw null;
+ static ref T GetRef() => throw null;
+ static ref readonly T GetRefReadonly() => throw null;
+}
+class Program
+{
+ static void Assign(S s, T tValue, ref T tRef, out T tOut, in T tIn)
+ {
+ tOut = default;
+ s.F = ref tValue;
+ s.F = ref tRef;
+ s.F = ref tOut;
+ s.F = ref tIn;
+ s.F = tValue;
+ s.F = tRef;
+ s.F = tOut;
+ s.F = tIn;
+ }
+}";
+ // PROTOTYPE: Report a ref-safe-to-escape error for: F = ref tValue;
+ var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition });
+ comp.VerifyEmitDiagnostics(
+ // (10,17): error CS8331: Cannot assign to variable 'in T' because it is a readonly variable
+ // F = ref tIn;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "tIn").WithArguments("variable", "in T").WithLocation(10, 17),
+ // (20,21): error CS1510: A ref or out value must be an assignable variable
+ // F = ref GetValue();
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "GetValue()").WithLocation(20, 21),
+ // (22,21): error CS8331: Cannot assign to method 'S.GetRefReadonly()' because it is a readonly variable
+ // F = ref GetRefReadonly();
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "GetRefReadonly()").WithArguments("method", "S.GetRefReadonly()").WithLocation(22, 21),
+ // (40,20): error CS8331: Cannot assign to variable 'in T' because it is a readonly variable
+ // s.F = ref tIn;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "tIn").WithArguments("variable", "in T").WithLocation(40, 20));
+ }
+
+ [Fact]
+ public void Assignment_RefReadonly()
+ {
+ var source =
+@"ref struct S
+{
+ public ref readonly T F;
+ public S(T tValue, ref T tRef, out T tOut, in T tIn)
+ {
+ tOut = default;
+ F = ref tValue;
+ F = ref tRef;
+ F = ref tOut;
+ F = ref tIn;
+ F = tValue;
+ F = tRef;
+ F = tOut;
+ F = tIn;
+ }
+ object P
+ {
+ init
+ {
+ F = ref GetValue();
+ F = ref GetRef();
+ F = ref GetRefReadonly();
+ F = GetValue();
+ F = GetRef();
+ F = GetRefReadonly();
+ }
+ }
+ static T GetValue() => throw null;
+ static ref T GetRef() => throw null;
+ static ref readonly T GetRefReadonly() => throw null;
+}
+class Program
+{
+ static void Assign(S s, T tValue, ref T tRef, out T tOut, in T tIn)
+ {
+ tOut = default;
+ s.F = ref tValue;
+ s.F = ref tRef;
+ s.F = ref tOut;
+ s.F = ref tIn;
+ s.F = tValue;
+ s.F = tRef;
+ s.F = tOut;
+ s.F = tIn;
+ }
+}";
+ // PROTOTYPE: Report a ref-safe-to-escape error for: F = ref tValue;
+ var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition });
+ comp.VerifyEmitDiagnostics(
+ // (11,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = tValue;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(11, 9),
+ // (12,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = tRef;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(12, 9),
+ // (13,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = tOut;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(13, 9),
+ // (14,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = tIn;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(14, 9),
+ // (20,21): error CS1510: A ref or out value must be an assignable variable
+ // F = ref GetValue();
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "GetValue()").WithLocation(20, 21),
+ // (23,13): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = GetValue();
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(23, 13),
+ // (24,13): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = GetRef();
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(24, 13),
+ // (25,13): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = GetRefReadonly();
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(25, 13),
+ // (41,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // s.F = tValue;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "s.F").WithArguments("field", "S.F").WithLocation(41, 9),
+ // (42,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // s.F = tRef;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "s.F").WithArguments("field", "S.F").WithLocation(42, 9),
+ // (43,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // s.F = tOut;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "s.F").WithArguments("field", "S.F").WithLocation(43, 9),
+ // (44,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // s.F = tIn;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "s.F").WithArguments("field", "S.F").WithLocation(44, 9));
+ }
+
+ [Fact]
+ public void Assignment_ReadonlyRef()
+ {
+ var source =
+@"ref struct S
+{
+ public readonly ref T F;
+ public S(T tValue, ref T tRef, out T tOut, in T tIn)
+ {
+ tOut = default;
+ F = ref tValue;
+ F = ref tRef;
+ F = ref tOut;
+ F = ref tIn;
+ F = tValue;
+ F = tRef;
+ F = tOut;
+ F = tIn;
+ }
+ object P
+ {
+ init
+ {
+ F = ref GetValue();
+ F = ref GetRef();
+ F = ref GetRefReadonly();
+ F = GetValue();
+ F = GetRef();
+ F = GetRefReadonly();
+ }
+ }
+ static T GetValue() => throw null;
+ static ref T GetRef() => throw null;
+ static ref readonly T GetRefReadonly() => throw null;
+}
+class Program
+{
+ static void Assign(S s, T tValue, ref T tRef, out T tOut, in T tIn)
+ {
+ tOut = default;
+ s.F = ref tValue;
+ s.F = ref tRef;
+ s.F = ref tOut;
+ s.F = ref tIn;
+ s.F = tValue;
+ s.F = tRef;
+ s.F = tOut;
+ s.F = tIn;
+ }
+}";
+ // PROTOTYPE: Report a ref-safe-to-escape error for: F = ref tValue;
+ var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition });
+ comp.VerifyEmitDiagnostics(
+ // (10,17): error CS8331: Cannot assign to variable 'in T' because it is a readonly variable
+ // F = ref tIn;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "tIn").WithArguments("variable", "in T").WithLocation(10, 17),
+ // (20,21): error CS1510: A ref or out value must be an assignable variable
+ // F = ref GetValue();
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "GetValue()").WithLocation(20, 21),
+ // (22,21): error CS8331: Cannot assign to method 'S.GetRefReadonly()' because it is a readonly variable
+ // F = ref GetRefReadonly();
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "GetRefReadonly()").WithArguments("method", "S.GetRefReadonly()").WithLocation(22, 21),
+ // (40,20): error CS8331: Cannot assign to variable 'in T' because it is a readonly variable
+ // s.F = ref tIn;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "tIn").WithArguments("variable", "in T").WithLocation(40, 20),
+ // (41,9): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer)
+ // s.F = tValue;
+ Diagnostic(ErrorCode.ERR_AssgReadonly, "s.F").WithLocation(41, 9),
+ // (42,9): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer)
+ // s.F = tRef;
+ Diagnostic(ErrorCode.ERR_AssgReadonly, "s.F").WithLocation(42, 9),
+ // (43,9): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer)
+ // s.F = tOut;
+ Diagnostic(ErrorCode.ERR_AssgReadonly, "s.F").WithLocation(43, 9),
+ // (44,9): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer)
+ // s.F = tIn;
+ Diagnostic(ErrorCode.ERR_AssgReadonly, "s.F").WithLocation(44, 9));
+ }
+
+ [Fact]
+ public void Assignment_ReadonlyRefReadonly()
+ {
+ var source =
+@"ref struct S
+{
+ public ref readonly T F;
+ public S(T tValue, ref T tRef, out T tOut, in T tIn)
+ {
+ tOut = default;
+ F = ref tValue;
+ F = ref tRef;
+ F = ref tOut;
+ F = ref tIn;
+ F = tValue;
+ F = tRef;
+ F = tOut;
+ F = tIn;
+ }
+ object P
+ {
+ init
+ {
+ F = ref GetValue();
+ F = ref GetRef();
+ F = ref GetRefReadonly();
+ F = GetValue();
+ F = GetRef();
+ F = GetRefReadonly();
+ }
+ }
+ static T GetValue() => throw null;
+ static ref T GetRef() => throw null;
+ static ref readonly T GetRefReadonly() => throw null;
+}
+class Program
+{
+ static void Assign(S s, T tValue, ref T tRef, out T tOut, in T tIn)
+ {
+ tOut = default;
+ s.F = ref tValue;
+ s.F = ref tRef;
+ s.F = ref tOut;
+ s.F = ref tIn;
+ s.F = tValue;
+ s.F = tRef;
+ s.F = tOut;
+ s.F = tIn;
+ }
+}";
+ // PROTOTYPE: Report a ref-safe-to-escape error for: F = ref tValue;
+ var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition });
+ comp.VerifyEmitDiagnostics(
+ // (11,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = tValue;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(11, 9),
+ // (12,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = tRef;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(12, 9),
+ // (13,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = tOut;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(13, 9),
+ // (14,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = tIn;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(14, 9),
+ // (20,21): error CS1510: A ref or out value must be an assignable variable
+ // F = ref GetValue();
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "GetValue()").WithLocation(20, 21),
+ // (23,13): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = GetValue();
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(23, 13),
+ // (24,13): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = GetRef();
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(24, 13),
+ // (25,13): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // F = GetRefReadonly();
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(25, 13),
+ // (41,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // s.F = tValue;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "s.F").WithArguments("field", "S.F").WithLocation(41, 9),
+ // (42,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // s.F = tRef;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "s.F").WithArguments("field", "S.F").WithLocation(42, 9),
+ // (43,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // s.F = tOut;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "s.F").WithArguments("field", "S.F").WithLocation(43, 9),
+ // (44,9): error CS8331: Cannot assign to field 'S.F' because it is a readonly variable
+ // s.F = tIn;
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "s.F").WithArguments("field", "S.F").WithLocation(44, 9));
+ }
+
+ [Fact]
+ public void RefReturn_Ref()
+ {
+ var source =
+@"ref struct S
+{
+ public ref T F;
+ public ref T F1() => ref F;
+ public ref readonly T F2() => ref F;
+}
+class Program
+{
+ static ref T F1(S s) => ref s.F;
+ static ref T F2(ref S s) => ref s.F;
+ static ref T F3(out S s) { s = default; return ref s.F; }
+ static ref T F4(in S s) => ref s.F;
+ static ref readonly T F5(S s) => ref s.F;
+ static ref readonly T F6(ref S s) => ref s.F;
+ static ref readonly T F7(out S s) { s = default; return ref s.F; }
+ static ref readonly T F8(in S s) => ref s.F;
+}";
+ var comp = CreateCompilation(source);
+ // PROTOTYPE: Should not report ERR_RefReturnStructThis.
+ comp.VerifyEmitDiagnostics(
+ // (4,30): error CS8170: Struct members cannot return 'this' or other instance members by reference
+ // public ref T F1() => ref F;
+ Diagnostic(ErrorCode.ERR_RefReturnStructThis, "F").WithArguments("this").WithLocation(4, 30),
+ // (5,39): error CS8170: Struct members cannot return 'this' or other instance members by reference
+ // public ref readonly T F2() => ref F;
+ Diagnostic(ErrorCode.ERR_RefReturnStructThis, "F").WithArguments("this").WithLocation(5, 39),
+ // (9,39): error CS8167: Cannot return by reference a member of parameter 's' because it is not a ref or out parameter
+ // static ref T F1(S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnParameter2, "s").WithArguments("s").WithLocation(9, 39),
+ // (12,42): error CS8334: Members of variable 'in S' cannot be returned by writable reference because it is a readonly variable
+ // static ref T F4(in S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnReadonlyNotField2, "s.F").WithArguments("variable", "in S").WithLocation(12, 42),
+ // (13,48): error CS8167: Cannot return by reference a member of parameter 's' because it is not a ref or out parameter
+ // static ref readonly T F5(S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnParameter2, "s").WithArguments("s").WithLocation(13, 48));
+ }
+
+ [Fact]
+ public void RefReturn_RefReadonly()
+ {
+ var source =
+@"ref struct S
+{
+ public ref readonly T F;
+ public ref T F1() => ref F;
+ public ref readonly T F2() => ref F;
+}
+class Program
+{
+ static ref T F1(S s) => ref s.F;
+ static ref T F2(ref S s) => ref s.F;
+ static ref T F3(out S s) { s = default; return ref s.F; }
+ static ref T F4(in S s) => ref s.F;
+ static ref readonly T F5(S s) => ref s.F;
+ static ref readonly T F6(ref S s) => ref s.F;
+ static ref readonly T F7(out S s) { s = default; return ref s.F; }
+ static ref readonly T F8(in S s) => ref s.F;
+}";
+ var comp = CreateCompilation(source);
+ // PROTOTYPE: Should not report ERR_RefReturnStructThis.
+ comp.VerifyEmitDiagnostics(
+ // (4,30): error CS8333: Cannot return field 'S.F' by writable reference because it is a readonly variable
+ // public ref T F1() => ref F;
+ Diagnostic(ErrorCode.ERR_RefReturnReadonlyNotField, "F").WithArguments("field", "S.F").WithLocation(4, 30),
+ // (5,39): error CS8170: Struct members cannot return 'this' or other instance members by reference
+ // public ref readonly T F2() => ref F;
+ Diagnostic(ErrorCode.ERR_RefReturnStructThis, "F").WithArguments("this").WithLocation(5, 39),
+ // (9,39): error CS8333: Cannot return field 'S.F' by writable reference because it is a readonly variable
+ // static ref T F1(S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnReadonlyNotField, "s.F").WithArguments("field", "S.F").WithLocation(9, 39),
+ // (10,43): error CS8333: Cannot return field 'S.F' by writable reference because it is a readonly variable
+ // static ref T F2(ref S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnReadonlyNotField, "s.F").WithArguments("field", "S.F").WithLocation(10, 43),
+ // (11,62): error CS8333: Cannot return field 'S.F' by writable reference because it is a readonly variable
+ // static ref T F3(out S s) { s = default; return ref s.F; }
+ Diagnostic(ErrorCode.ERR_RefReturnReadonlyNotField, "s.F").WithArguments("field", "S.F").WithLocation(11, 62),
+ // (12,42): error CS8333: Cannot return field 'S.F' by writable reference because it is a readonly variable
+ // static ref T F4(in S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnReadonlyNotField, "s.F").WithArguments("field", "S.F").WithLocation(12, 42),
+ // (13,48): error CS8167: Cannot return by reference a member of parameter 's' because it is not a ref or out parameter
+ // static ref readonly T F5(S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnParameter2, "s").WithArguments("s").WithLocation(13, 48));
+ }
+
+
+ [Fact]
+ public void RefReturn_ReadonlyRef()
+ {
+ var source =
+@"ref struct S
+{
+ public readonly ref T F;
+ public ref T F1() => ref F;
+ public ref readonly T F2() => ref F;
+}
+class Program
+{
+ static ref T F1(S s) => ref s.F;
+ static ref T F2(ref S s) => ref s.F;
+ static ref T F3(out S s) { s = default; return ref s.F; }
+ static ref T F4(in S s) => ref s.F;
+ static ref readonly T F5(S s) => ref s.F;
+ static ref readonly T F6(ref S s) => ref s.F;
+ static ref readonly T F7(out S s) { s = default; return ref s.F; }
+ static ref readonly T F8(in S s) => ref s.F;
+}";
+ var comp = CreateCompilation(source);
+ // PROTOTYPE: Should not report ERR_RefReturnStructThis.
+ comp.VerifyEmitDiagnostics(
+ // (4,30): error CS8160: A readonly field cannot be returned by writable reference
+ // public ref T F1() => ref F;
+ Diagnostic(ErrorCode.ERR_RefReturnReadonly, "F").WithLocation(4, 30),
+ // (5,39): error CS8170: Struct members cannot return 'this' or other instance members by reference
+ // public ref readonly T F2() => ref F;
+ Diagnostic(ErrorCode.ERR_RefReturnStructThis, "F").WithArguments("this").WithLocation(5, 39),
+ // (9,39): error CS8160: A readonly field cannot be returned by writable reference
+ // static ref T F1(S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnReadonly, "s.F").WithLocation(9, 39),
+ // (10,43): error CS8160: A readonly field cannot be returned by writable reference
+ // static ref T F2(ref S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnReadonly, "s.F").WithLocation(10, 43),
+ // (11,62): error CS8160: A readonly field cannot be returned by writable reference
+ // static ref T F3(out S s) { s = default; return ref s.F; }
+ Diagnostic(ErrorCode.ERR_RefReturnReadonly, "s.F").WithLocation(11, 62),
+ // (12,42): error CS8160: A readonly field cannot be returned by writable reference
+ // static ref T F4(in S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnReadonly, "s.F").WithLocation(12, 42),
+ // (13,48): error CS8167: Cannot return by reference a member of parameter 's' because it is not a ref or out parameter
+ // static ref readonly T F5(S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnParameter2, "s").WithArguments("s").WithLocation(13, 48));
+ }
+
+ [Fact]
+ public void RefReturn_ReadonlyRefReadonly()
+ {
+ var source =
+@"ref struct S
+{
+ public readonly ref readonly T F;
+ public ref T F1() => ref F;
+ public ref readonly T F2() => ref F;
+}
+class Program
+{
+ static ref T F1(S s) => ref s.F;
+ static ref T F2(ref S s) => ref s.F;
+ static ref T F3(out S s) { s = default; return ref s.F; }
+ static ref T F4(in S s) => ref s.F;
+ static ref readonly T F5(S s) => ref s.F;
+ static ref readonly T F6(ref S s) => ref s.F;
+ static ref readonly T F7(out S s) { s = default; return ref s.F; }
+ static ref readonly T F8(in S s) => ref s.F;
+}";
+ var comp = CreateCompilation(source);
+ // PROTOTYPE: Should not report ERR_RefReturnStructThis.
+ comp.VerifyEmitDiagnostics(
+ // (4,30): error CS8160: A readonly field cannot be returned by writable reference
+ // public ref T F1() => ref F;
+ Diagnostic(ErrorCode.ERR_RefReturnReadonly, "F").WithLocation(4, 30),
+ // (5,39): error CS8170: Struct members cannot return 'this' or other instance members by reference
+ // public ref readonly T F2() => ref F;
+ Diagnostic(ErrorCode.ERR_RefReturnStructThis, "F").WithArguments("this").WithLocation(5, 39),
+ // (9,39): error CS8160: A readonly field cannot be returned by writable reference
+ // static ref T F1(S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnReadonly, "s.F").WithLocation(9, 39),
+ // (10,43): error CS8160: A readonly field cannot be returned by writable reference
+ // static ref T F2(ref S s) => ref s.F;
+ Diagnostic(ErrorCode.ERR_RefReturnReadonly, "s.F").WithLocation(10, 43),
+ // (11,62): error CS8160: A readonly field cannot be returned by writable reference
+ // static ref T F3(out S s) { s = default; return ref s.F; }
+ Diagnostic(ErrorCode.ERR_RefReturnReadonly, "s.F").WithLocation(11, 62),
+ // (12,42): error CS8160: A readonly field cannot be returned by writable reference
+ // static ref T F4