diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index a545b67913f04..cf0ff3930aaf1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -4298,7 +4298,13 @@ private BoundObjectInitializerExpressionBase BindInitializerExpression( switch (syntax.Kind()) { case SyntaxKind.ObjectInitializerExpression: - return BindObjectInitializerExpression(syntax, type, diagnostics, implicitReceiver); + // Uses a special binder to produce customized diagnostics for the object initializer + return BindObjectInitializerExpression( + syntax, type, diagnostics, implicitReceiver, useObjectInitDiagnostics: true); + + case SyntaxKind.WithInitializerExpression: + return BindObjectInitializerExpression( + syntax, type, diagnostics, implicitReceiver, useObjectInitDiagnostics: false); case SyntaxKind.CollectionInitializerExpression: return BindCollectionInitializerExpression(syntax, type, diagnostics, implicitReceiver); @@ -4321,6 +4327,7 @@ private BoundExpression BindInitializerExpressionOrValue( { case SyntaxKind.ObjectInitializerExpression: case SyntaxKind.CollectionInitializerExpression: + Debug.Assert(syntax.Parent.Parent.Kind() != SyntaxKind.WithInitializerExpression); return BindInitializerExpression((InitializerExpressionSyntax)syntax, type, typeSyntax, diagnostics); default: return BindValue(syntax, diagnostics, BindValueKind.RValue); @@ -4331,7 +4338,8 @@ private BoundObjectInitializerExpression BindObjectInitializerExpression( InitializerExpressionSyntax initializerSyntax, TypeSymbol initializerType, DiagnosticBag diagnostics, - BoundObjectOrCollectionValuePlaceholder implicitReceiver) + BoundObjectOrCollectionValuePlaceholder implicitReceiver, + bool useObjectInitDiagnostics) { // SPEC: 7.6.10.2 Object initializers // @@ -4339,36 +4347,42 @@ private BoundObjectInitializerExpression BindObjectInitializerExpression( // SPEC: Each member initializer must name an accessible field or property of the object being initialized, followed by an equals sign and // SPEC: an expression or an object initializer or collection initializer. - Debug.Assert(initializerSyntax.Kind() == SyntaxKind.ObjectInitializerExpression); + Debug.Assert(initializerSyntax.Kind() == SyntaxKind.ObjectInitializerExpression || + initializerSyntax.Kind() == SyntaxKind.WithInitializerExpression); Debug.Assert((object)initializerType != null); - var initializerBuilder = ArrayBuilder.GetInstance(); - - // Member name map to report duplicate assignments to a field/property. - var memberNameMap = new HashSet(); - // We use a location specific binder for binding object initializer field/property access to generate object initializer specific diagnostics: // 1) CS1914 (ERR_StaticMemberInObjectInitializer) // 2) CS1917 (ERR_ReadonlyValueTypeInObjectInitializer) // 3) CS1918 (ERR_ValueTypePropertyInObjectInitializer) // Note that this is only used for the LHS of the assignment - these diagnostics do not apply on the RHS. // For this reason, we will actually need two binders: this and this.WithAdditionalFlags. - var objectInitializerMemberBinder = this.WithAdditionalFlags(BinderFlags.ObjectInitializerMember); + var objectInitializerMemberBinder = useObjectInitDiagnostics + ? this.WithAdditionalFlags(BinderFlags.ObjectInitializerMember) + : this; + + var initializers = ArrayBuilder.GetInstance(initializerSyntax.Expressions.Count); + // Member name map to report duplicate assignments to a field/property. + var memberNameMap = PooledHashSet.GetInstance(); foreach (var memberInitializer in initializerSyntax.Expressions) { - BoundExpression boundMemberInitializer = BindObjectInitializerMemberAssignment( + BoundExpression boundMemberInitializer = BindInitializerMemberAssignment( memberInitializer, initializerType, objectInitializerMemberBinder, diagnostics, implicitReceiver); - initializerBuilder.Add(boundMemberInitializer); + initializers.Add(boundMemberInitializer); ReportDuplicateObjectMemberInitializers(boundMemberInitializer, memberNameMap, diagnostics); } - return new BoundObjectInitializerExpression(initializerSyntax, implicitReceiver, initializerBuilder.ToImmutableAndFree(), initializerType); + return new BoundObjectInitializerExpression( + initializerSyntax, + implicitReceiver, + initializers.ToImmutableAndFree(), + initializerType); } - private BoundExpression BindObjectInitializerMemberAssignment( + private BoundExpression BindInitializerMemberAssignment( ExpressionSyntax memberInitializer, TypeSymbol initializerType, Binder objectInitializerMemberBinder, @@ -4402,7 +4416,6 @@ private BoundExpression BindObjectInitializerMemberAssignment( // See comments in BindObjectInitializerExpression for more details. Debug.Assert(objectInitializerMemberBinder != null); - Debug.Assert(objectInitializerMemberBinder.Flags.Includes(BinderFlags.ObjectInitializerMember)); boundLeft = objectInitializerMemberBinder.BindObjectInitializerMember(initializer, implicitReceiver, diagnostics); } @@ -4572,8 +4585,6 @@ private BoundExpression BindObjectInitializerMember( // CheckValueKind to generate possible diagnostics for invalid initializers non-viable member lookup result: // 1) CS0154 (ERR_PropertyLacksGet) // 2) CS0200 (ERR_AssgReadonlyProp) - - Debug.Assert(Flags.Includes(CSharp.BinderFlags.ObjectInitializerMember)); if (!CheckValueKind(boundMember.Syntax, boundMember, valueKind, checkingReceiver: false, diagnostics: diagnostics)) { hasErrors = true; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs b/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs index 430ce5356bb97..b74a571140e26 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs @@ -21,7 +21,6 @@ internal partial class Binder { private BoundExpression BindWithExpression(WithExpressionSyntax syntax, DiagnosticBag diagnostics) { - // PROTOTYPE: this entire method is likely to change var receiver = BindRValueWithoutTargetType(syntax.Receiver, diagnostics); var receiverType = receiver.Type; @@ -95,94 +94,20 @@ private BoundExpression BindWithExpression(WithExpressionSyntax syntax, Diagnost } var cloneReturnType = cloneMethod?.ReturnType; + var initializer = BindInitializerExpression( + syntax.Initializer, + cloneReturnType ?? receiverType, + syntax.Receiver, + diagnostics); - var args = ArrayBuilder<(Symbol?, BoundExpression)>.GetInstance(); - // Bind with expression arguments - foreach (var expr in syntax.Initializer.Expressions) - { - Symbol? member = null; - BoundExpression boundRight; - // We're expecting a simple assignment only, with an ID on the left - if (!(expr is AssignmentExpressionSyntax assignment) || - !(assignment.Left is IdentifierNameSyntax left)) - { - boundRight = BindExpression(expr, diagnostics); - hasErrors = true; - diagnostics.Add(ErrorCode.ERR_BadWithExpressionArgument, expr.Location); - } - else - { - var propName = left.Identifier.Text; - if (!(cloneReturnType is null)) - { - var location = left.Location; - this.LookupMembersInType( - lookupResult, - cloneReturnType, - propName, - arity: 0, - basesBeingResolved: null, - options: LookupOptions.Default, - originalBinder: this, - diagnose: false, - useSiteDiagnostics: ref useSiteDiagnostics); - // PROTOTYPE: Should handle hiding/overriding and bind like regular accesses - if (lookupResult.IsSingleViable && - lookupResult.SingleSymbolOrDefault is var sym) - { - switch (sym.Kind) - { - case SymbolKind.Property: - member = sym; - // PROTOTYPE: this should check for init-only, but that isn't a separate feature yet - // It also will not work in metadata. - if (!(sym is SynthesizedRecordPropertySymbol)) - { - goto default; - } - break; - - default: - hasErrors = true; - diagnostics.Add( - ErrorCode.ERR_WithMemberIsNotRecordProperty, - location); - break; - } - } - - if (!hasErrors && member is null) - { - hasErrors = true; - Error( - diagnostics, - ErrorCode.ERR_NoSuchMemberOrExtension, - location, - cloneReturnType, - propName); - } - } - - boundRight = BindValue(assignment.Right, diagnostics, BindValueKind.RValue); - if (!(member is null)) - { - boundRight = GenerateConversionForAssignment( - member.GetTypeOrReturnType().Type, - boundRight, - diagnostics); - } - lookupResult.Clear(); - } - args.Add((member, boundRight)); - } - - lookupResult.Free(); + // N.B. Since we only don't parse nested initializers in syntax there should be no extra + // errors we need to check for here. return new BoundWithExpression( syntax, receiver, cloneMethod, - args.ToImmutableAndFree(), + initializer, cloneReturnType ?? receiverType, hasErrors: hasErrors); } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 074906ffe45b6..2f0a067b9b404 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -2131,7 +2131,7 @@ - + diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 0a73fd5c21437..b8ff88ef1dcad 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -6130,9 +6130,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Init-only property or indexer '{0}' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor. - - Arguments to a `with` expression must be simple assignments - A variable may not be declared within a 'not' or 'or' pattern. diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 922f1746ef68a..1a2c020f2ff6c 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1803,7 +1803,6 @@ internal enum ErrorCode ERR_NoSingleCloneMethod = 8858, ERR_ContainingTypeMustDeriveFromWithReturnType = 8859, ERR_WithMemberIsNotRecordProperty = 8860, - ERR_BadWithExpressionArgument = 8861, #endregion diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 8ac783c614d46..b42aac9435709 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -7734,17 +7734,17 @@ public BoundExpressionWithNullability Update(BoundExpression expression, Nullabl internal sealed partial class BoundWithExpression : BoundExpression { - public BoundWithExpression(SyntaxNode syntax, BoundExpression receiver, MethodSymbol? cloneMethod, ImmutableArray<(Symbol? Member, BoundExpression Expression)> arguments, TypeSymbol type, bool hasErrors = false) - : base(BoundKind.WithExpression, syntax, type, hasErrors || receiver.HasErrors()) + public BoundWithExpression(SyntaxNode syntax, BoundExpression receiver, MethodSymbol? cloneMethod, BoundObjectInitializerExpressionBase initializerExpression, TypeSymbol type, bool hasErrors = false) + : base(BoundKind.WithExpression, syntax, type, hasErrors || receiver.HasErrors() || initializerExpression.HasErrors()) { RoslynDebug.Assert(receiver is object, "Field 'receiver' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); - RoslynDebug.Assert(!arguments.IsDefault, "Field 'arguments' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + RoslynDebug.Assert(initializerExpression is object, "Field 'initializerExpression' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); this.Receiver = receiver; this.CloneMethod = cloneMethod; - this.Arguments = arguments; + this.InitializerExpression = initializerExpression; } @@ -7754,15 +7754,15 @@ public BoundWithExpression(SyntaxNode syntax, BoundExpression receiver, MethodSy public MethodSymbol? CloneMethod { get; } - public ImmutableArray<(Symbol? Member, BoundExpression Expression)> Arguments { get; } + public BoundObjectInitializerExpressionBase InitializerExpression { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitWithExpression(this); - public BoundWithExpression Update(BoundExpression receiver, MethodSymbol? cloneMethod, ImmutableArray<(Symbol? Member, BoundExpression Expression)> arguments, TypeSymbol type) + public BoundWithExpression Update(BoundExpression receiver, MethodSymbol? cloneMethod, BoundObjectInitializerExpressionBase initializerExpression, TypeSymbol type) { - if (receiver != this.Receiver || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(cloneMethod, this.CloneMethod) || arguments != this.Arguments || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (receiver != this.Receiver || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(cloneMethod, this.CloneMethod) || initializerExpression != this.InitializerExpression || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundWithExpression(this.Syntax, receiver, cloneMethod, arguments, type, this.HasErrors); + var result = new BoundWithExpression(this.Syntax, receiver, cloneMethod, initializerExpression, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -9473,6 +9473,7 @@ internal abstract partial class BoundTreeWalker: BoundTreeVisitor public override BoundNode? VisitWithExpression(BoundWithExpression node) { this.Visit(node.Receiver); + this.Visit(node.InitializerExpression); return null; } } @@ -10638,8 +10639,9 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitWithExpression(BoundWithExpression node) { BoundExpression receiver = (BoundExpression)this.Visit(node.Receiver); + BoundObjectInitializerExpressionBase initializerExpression = (BoundObjectInitializerExpressionBase)this.Visit(node.InitializerExpression); TypeSymbol? type = this.VisitType(node.Type); - return node.Update(receiver, node.CloneMethod, node.Arguments, type); + return node.Update(receiver, node.CloneMethod, initializerExpression, type); } } @@ -12930,16 +12932,17 @@ public NullabilityRewriter(ImmutableDictionary? dynamicSiteInitializers = null; @@ -183,7 +211,7 @@ public override BoundNode VisitNewT(BoundNewT node) return rewrittenNewT; } - return MakeObjectCreationWithInitializer(node.Syntax, rewrittenNewT, node.InitializerExpressionOpt, rewrittenNewT.Type!); + return MakeExpressionWithInitializer(node.Syntax, rewrittenNewT, node.InitializerExpressionOpt, rewrittenNewT.Type!); } private BoundExpression MakeNewT(SyntaxNode syntax, TypeParameterSymbol typeParameter) @@ -289,7 +317,7 @@ public override BoundNode VisitNoPiaObjectCreationExpression(BoundNoPiaObjectCre return rewrittenObjectCreation; } - return MakeObjectCreationWithInitializer(node.Syntax, rewrittenObjectCreation, node.InitializerExpressionOpt, node.Type); + return MakeExpressionWithInitializer(node.Syntax, rewrittenObjectCreation, node.InitializerExpressionOpt, node.Type); } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_WithExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_WithExpression.cs deleted file mode 100644 index 3f6b91f7b9540..0000000000000 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_WithExpression.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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 enable - -using System.Collections.Immutable; -using System.Diagnostics; -using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.CSharp -{ - internal sealed partial class LocalRewriter - { - public override BoundNode VisitWithExpression(BoundWithExpression withExpr) - { - RoslynDebug.AssertNotNull(withExpr.CloneMethod); - Debug.Assert(withExpr.CloneMethod.ParameterCount == 0); - - // for a with expression of the form - // - // receiver with { P1 = e1, P2 = e2 } - // - // we want to lower it to a call to the receiver's `Clone` method, then - // set the given record properties. i.e. - // - // var tmp = receiver.Clone(); - // tmp.P1 = e1; - // tmp.P2 = e2; - // tmp - var F = _factory; - var stores = ArrayBuilder.GetInstance(withExpr.Arguments.Length + 1); - - // var tmp = receiver.Clone(); - var receiverLocal = F.StoreToTemp( - F.InstanceCall(VisitExpression(withExpr.Receiver), "Clone"), - out var receiverStore); - stores.Add(receiverStore); - - // tmp.Pn = En; - foreach (var arg in withExpr.Arguments) - { - RoslynDebug.AssertNotNull(arg.Member); - // PROTOTYPE: only works for source symbols - var prop = (SynthesizedRecordPropertySymbol)arg.Member; - stores.Add(F.AssignmentExpression( - (BoundExpression)F.Field((BoundExpression)receiverLocal, (FieldSymbol)prop.BackingField), - (BoundExpression)VisitExpression((BoundExpression)arg.Expression) - )); - } - - return new BoundSequence( - withExpr.Syntax, - ImmutableArray.Create(receiverLocal.LocalSymbol), - stores.ToImmutableAndFree(), - receiverLocal, - withExpr.Type); - } - } -} \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs b/src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs index 3838e4b0e8252..87193c5cf81b7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs +++ b/src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs @@ -44,8 +44,8 @@ public int Position public static readonly LexicalSortKey NotInitialized = new LexicalSortKey() { _treeOrdinal = -1, _position = -1 }; // Put Record Equals right before synthesized constructors. - public static LexicalSortKey SynthesizedRecordEquals => new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 4 }; - public static LexicalSortKey SynthesizedRecordObjEquals => new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 3 }; + public static LexicalSortKey SynthesizedRecordEquals => new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 5 }; + public static LexicalSortKey SynthesizedRecordObjEquals => new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 4 }; // Dev12 compiler adds synthetic constructors to the child list after adding all other members. @@ -53,6 +53,7 @@ public int Position // until later when it is known if they can be optimized or not. // As a result the last emitted method tokens are synthetic ctor and then synthetic cctor (if not optimized) // Since it is not too hard, we will try keeping the same order just to be easy on metadata diffing tools and such. + public static readonly LexicalSortKey SynthesizedRecordCopyCtor = new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 3 }; public static readonly LexicalSortKey SynthesizedRecordCtor = new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 2 }; public static readonly LexicalSortKey SynthesizedCtor = new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 1 }; public static readonly LexicalSortKey SynthesizedCCtor = new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue }; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index b74be3f6d0441..d556fe26138e2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -2946,6 +2946,7 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde BinderFactory binderFactory = this.DeclaringCompilation.GetBinderFactory(paramList.SyntaxTree); var binder = binderFactory.GetBinder(paramList); + // PROTOTYPE: need to check base members as well var memberSignatures = s_duplicateMemberSignatureDictionary.Allocate(); foreach (var member in members) { @@ -2953,6 +2954,8 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde } var ctor = addCtor(paramList); + addCopyCtor(); + addCloneMethod(); addProperties(ctor.Parameters); var thisEquals = addThisEquals(); addObjectEquals(thisEquals); @@ -2977,15 +2980,34 @@ SynthesizedRecordConstructor addCtor(ParameterListSyntax paramList) return ctor; } + void addCopyCtor() + { + var ctor = new SynthesizedRecordCopyCtor(this, diagnostics); + if (!memberSignatures.ContainsKey(ctor)) + { + members.Add(ctor); + } + } + + void addCloneMethod() + { + var clone = new SynthesizedRecordClone(this); + if (!memberSignatures.ContainsKey(clone)) + { + members.Add(clone); + } + } + void addProperties(ImmutableArray recordParameters) { foreach (ParameterSymbol param in ctor.Parameters) { - var property = new SynthesizedRecordPropertySymbol(this, param); + var property = new SynthesizedRecordPropertySymbol(this, param, diagnostics); if (!memberSignatures.ContainsKey(property)) { members.Add(property); members.Add(property.GetMethod); + members.Add(property.SetMethod); members.Add(property.BackingField); } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordClone.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordClone.cs new file mode 100644 index 0000000000000..d04cd5cb5646b --- /dev/null +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordClone.cs @@ -0,0 +1,133 @@ +// 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 enable + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection; +using Microsoft.Cci; +using Microsoft.CodeAnalysis.CSharp.Emit; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + internal sealed class SynthesizedRecordClone : SynthesizedInstanceMethodSymbol + { + public override NamedTypeSymbol ContainingType { get; } + + public SynthesizedRecordClone(NamedTypeSymbol containingType) + { + ContainingType = containingType; + } + + public override string Name => WellKnownMemberNames.CloneMethodName; + + public override MethodKind MethodKind => MethodKind.Ordinary; + + public override int Arity => 0; + + public override bool IsExtensionMethod => false; + + public override bool HidesBaseMethodsByName => false; + + public override bool IsVararg => false; + + public override bool ReturnsVoid => false; + + public override bool IsAsync => false; + + public override RefKind RefKind => RefKind.None; + + public override ImmutableArray Parameters => ImmutableArray.Empty; + + public override TypeWithAnnotations ReturnTypeWithAnnotations => TypeWithAnnotations.Create( + isNullableEnabled: true, + ContainingType); + + public override FlowAnalysisAnnotations ReturnTypeFlowAnalysisAnnotations => FlowAnalysisAnnotations.None; + + public override ImmutableHashSet ReturnNotNullIfParameterNotNull => ImmutableHashSet.Empty; + + public override ImmutableArray TypeArgumentsWithAnnotations + => ImmutableArray.Empty; + + public override ImmutableArray TypeParameters => ImmutableArray.Empty; + + public override ImmutableArray ExplicitInterfaceImplementations => ImmutableArray.Empty; + + public override ImmutableArray RefCustomModifiers => ImmutableArray.Empty; + + public override Symbol? AssociatedSymbol => null; + + public override Symbol ContainingSymbol => ContainingType; + + public override ImmutableArray Locations => ContainingType.Locations; + + public override Accessibility DeclaredAccessibility => Accessibility.Public; + + public override bool IsStatic => false; + + // PROTOTYPE: Inheritance is not handled + public override bool IsVirtual => true; + + public override bool IsOverride => false; + + public override bool IsAbstract => false; + + public override bool IsSealed => false; + + public override bool IsExtern => false; + + internal override bool HasSpecialName => true; + + internal override MethodImplAttributes ImplementationAttributes => MethodImplAttributes.Managed; + + internal override bool HasDeclarativeSecurity => false; + + internal override MarshalPseudoCustomAttributeData? ReturnValueMarshallingInformation => null; + + internal override bool RequiresSecurityObject => false; + + internal override CallingConvention CallingConvention => CallingConvention.HasThis; + + internal override bool GenerateDebugInfo => false; + + public override DllImportData? GetDllImportData() => null; + + internal override ImmutableArray GetAppliedConditionalSymbols() + => ImmutableArray.Empty; + + internal override IEnumerable GetSecurityInformation() + => Array.Empty(); + + internal override bool IsMetadataNewSlot(bool ignoreInterfaceImplementationChanges = false) => false; + + internal override bool IsMetadataVirtual(bool ignoreInterfaceImplementationChanges = false) => false; + + internal override bool SynthesizesLoweredBoundBody => true; + + internal override void GenerateMethodBody(TypeCompilationState compilationState, DiagnosticBag diagnostics) + { + var F = new SyntheticBoundNodeFactory(this, ContainingType.GetNonNullSyntaxNode(), compilationState, diagnostics); + + // PROTOTYPE: what about base fields? + var members = ContainingType.GetMembers(WellKnownMemberNames.InstanceConstructorName); + foreach (var member in members) + { + var ctor = (MethodSymbol)member; + if (ctor.ParameterCount == 1 && + ctor.Parameters[0].Type.Equals(ContainingType, TypeCompareKind.ConsiderEverything)) + { + F.CloseMethod(F.Return(F.New(ctor, F.This()))); + return; + } + } + + throw ExceptionUtilities.Unreachable; + } + } +} diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs new file mode 100644 index 0000000000000..b0a7adbac98af --- /dev/null +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs @@ -0,0 +1,56 @@ +// 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 enable + +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + internal sealed class SynthesizedRecordCopyCtor : SynthesizedInstanceConstructor + { + public SynthesizedRecordCopyCtor( + SourceMemberContainerTypeSymbol containingType, + DiagnosticBag diagnostics) + : base(containingType) + { + Parameters = ImmutableArray.Create(SynthesizedParameterSymbol.Create( + this, + TypeWithAnnotations.Create( + isNullableEnabled: true, + ContainingType), + ordinal: 0, + RefKind.None)); + } + + public override ImmutableArray Parameters { get; } + + internal override LexicalSortKey GetLexicalSortKey() + { + // We need a separate sort key because struct records will have two synthesized + // constructors: the record constructor, and the parameterless constructor + return LexicalSortKey.SynthesizedRecordCopyCtor; + } + + internal override void GenerateMethodBodyStatements(SyntheticBoundNodeFactory F, ArrayBuilder statements, DiagnosticBag diagnostics) + { + // PROTOTYPE: Handle inheritance + // Write assignments to fields + // + // { + // this.field1 = parameter.field1 + // ... + // this.fieldN = parameter.fieldN + // } + var param = F.Parameter(Parameters[0]); + foreach (var field in ContainingType.GetFieldsToEmit()) + { + statements.Add(F.Assignment(F.Field(F.This(), field), F.Field(param, field))); + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs index 832ec997babf4..2c0badf5b1107 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs @@ -21,11 +21,13 @@ internal sealed class SynthesizedRecordPropertySymbol : SourceOrRecordPropertySy private readonly ParameterSymbol _backingParameter; internal override SynthesizedBackingFieldSymbol BackingField { get; } public override MethodSymbol GetMethod { get; } + public override MethodSymbol SetMethod { get; } public override NamedTypeSymbol ContainingType { get; } public SynthesizedRecordPropertySymbol( NamedTypeSymbol containingType, - ParameterSymbol backingParameter) + ParameterSymbol backingParameter, + DiagnosticBag diagnostics) : base(backingParameter.Locations[0]) { ContainingType = containingType; @@ -38,6 +40,7 @@ public SynthesizedRecordPropertySymbol( isStatic: false, hasInitializer: backingParameter.HasExplicitDefaultValue); GetMethod = new GetAccessorSymbol(this, name); + SetMethod = new InitAccessorSymbol(this, name, diagnostics); } internal override bool IsAutoProperty => true; @@ -52,8 +55,6 @@ public SynthesizedRecordPropertySymbol( public override bool IsIndexer => false; - public override MethodSymbol? SetMethod => null; - public override ImmutableArray ExplicitInterfaceImplementations => ImmutableArray.Empty; public override Symbol ContainingSymbol => ContainingType; @@ -207,5 +208,137 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, F.CloseMethod(F.Block(F.Return(F.Field(F.This(), _property.BackingField)))); } } + + private sealed class InitAccessorSymbol : SynthesizedInstanceMethodSymbol + { + private readonly SynthesizedRecordPropertySymbol _property; + + public override TypeWithAnnotations ReturnTypeWithAnnotations { get; } + public override string Name { get; } + + public InitAccessorSymbol( + SynthesizedRecordPropertySymbol property, + string paramName, + DiagnosticBag diagnostics) + { + _property = property; + Name = SourcePropertyAccessorSymbol.GetAccessorName( + paramName, + getNotSet: false, + isWinMdOutput: false /* PROTOTYPE */); + + var comp = property.DeclaringCompilation; + var type = TypeWithAnnotations.Create(comp.GetSpecialType(SpecialType.System_Void)); + var initOnlyType = Binder.GetWellKnownType( + comp, + WellKnownType.System_Runtime_CompilerServices_IsExternalInit, + diagnostics, + property.Location); + var modifiers = ImmutableArray.Create(CSharpCustomModifier.CreateRequired(initOnlyType)); + + ReturnTypeWithAnnotations = type.WithModifiers(modifiers); + } + + internal override bool IsInitOnly => true; + + public override MethodKind MethodKind => MethodKind.PropertySet; + + public override int Arity => 0; + + public override bool IsExtensionMethod => false; + + public override bool HidesBaseMethodsByName => false; + + public override bool IsVararg => false; + + public override bool ReturnsVoid => true; + + public override bool IsAsync => false; + + public override RefKind RefKind => RefKind.None; + + public override FlowAnalysisAnnotations ReturnTypeFlowAnalysisAnnotations => FlowAnalysisAnnotations.None; + + public override ImmutableArray TypeArgumentsWithAnnotations => ImmutableArray.Empty; + + public override ImmutableArray TypeParameters => ImmutableArray.Empty; + + public override ImmutableArray Parameters => ImmutableArray.Create(SynthesizedParameterSymbol.Create( + this, + _property.TypeWithAnnotations, + ordinal: 0, + RefKind.None, + name: ParameterSymbol.ValueParameterName)); + + public override ImmutableArray ExplicitInterfaceImplementations => ImmutableArray.Empty; + + public override ImmutableArray RefCustomModifiers => _property.RefCustomModifiers; + + public override Symbol AssociatedSymbol => _property; + + public override Symbol ContainingSymbol => _property.ContainingSymbol; + + public override ImmutableArray Locations => _property.Locations; + + public override Accessibility DeclaredAccessibility => _property.DeclaredAccessibility; + + public override bool IsStatic => _property.IsStatic; + + public override bool IsVirtual => _property.IsVirtual; + + public override bool IsOverride => _property.IsOverride; + + public override bool IsAbstract => _property.IsAbstract; + + public override bool IsSealed => _property.IsSealed; + + public override bool IsExtern => _property.IsExtern; + + public override ImmutableHashSet ReturnNotNullIfParameterNotNull => ImmutableHashSet.Empty; + + internal override bool HasSpecialName => _property.HasSpecialName; + + internal override MethodImplAttributes ImplementationAttributes => MethodImplAttributes.Managed; + + internal override bool HasDeclarativeSecurity => false; + + internal override MarshalPseudoCustomAttributeData? ReturnValueMarshallingInformation => null; + + internal override bool RequiresSecurityObject => false; + + internal override CallingConvention CallingConvention => CallingConvention.HasThis; + + internal override bool GenerateDebugInfo => false; + + public override DllImportData? GetDllImportData() => null; + + internal override ImmutableArray GetAppliedConditionalSymbols() + => ImmutableArray.Empty; + + internal override IEnumerable GetSecurityInformation() + => Array.Empty(); + + internal override bool IsMetadataNewSlot(bool ignoreInterfaceImplementationChanges = false) => false; + + internal override bool IsMetadataVirtual(bool ignoreInterfaceImplementationChanges = false) => false; + + internal override bool SynthesizesLoweredBoundBody => true; + + internal override void GenerateMethodBody(TypeCompilationState compilationState, DiagnosticBag diagnostics) + { + // Method body: + // + // { + // this.<>backingField = value; + // } + + var F = new SyntheticBoundNodeFactory(this, this.GetNonNullSyntaxNode(), compilationState, diagnostics); + + F.CurrentFunction = this; + F.CloseMethod(F.Block( + F.Assignment(F.Field(F.This(), _property.BackingField), F.Parameter(Parameters[0])), + F.Return())); + } + } } } diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 68a77cb4977ec..79eb95ca4f3aa 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -117,11 +117,6 @@ Chyba syntaxe příkazového řádku: {0} není platná hodnota možnosti {1}. Hodnota musí mít tvar {2}. - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 6a85fc1c26792..163ab4e5ad7db 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -117,11 +117,6 @@ Fehler in der Befehlszeilensyntax: "{0}" ist kein gültiger Wert für die Option "{1}". Der Wert muss im Format "{2}" vorliegen. - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index c4f9226750a65..b23b86329e30f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -117,11 +117,6 @@ Error de sintaxis de la línea de comandos: "{0}" no es un valor válido para la opción "{1}". El valor debe tener el formato "{2}". - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 43cd031fcbac5..5923b85bc54e7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -117,11 +117,6 @@ Erreur de syntaxe de ligne de commande : '{0}' est une valeur non valide pour l'option '{1}'. La valeur doit se présenter sous la forme '{2}'. - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index f484dcb66f513..f9e5a2ac6b335 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -117,11 +117,6 @@ Errore di sintassi della riga di comando: '{0}' non è un valore valido per l'opzione '{1}'. Il valore deve essere espresso nel formato '{2}'. - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 6dd05687c857f..7bad32af6aa7a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -117,11 +117,6 @@ コマンドライン構文エラー: '{0}' は、'{1}' オプションの有効な値ではありません。値は '{2}' の形式にする必要があります。 - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 59dd4611400fe..d3978b4759eef 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -117,11 +117,6 @@ 명령줄 구문 오류: '{0}'은(는) '{1}' 옵션에 유효한 값이 아닙니다. 값은 '{2}' 형식이어야 합니다. - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index e9535bb5da2a9..45a76d7d0dd32 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -117,11 +117,6 @@ Błąd składni wiersza polecenia: „{0}” nie jest prawidłową wartością dla opcji „{1}”. Wartość musi mieć postać „{2}”. - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 78b7ed3e5de66..759dbdff0882e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -117,11 +117,6 @@ Erro de sintaxe de linha de comando: '{0}' não é um valor válido para a opção '{1}'. O valor precisa estar no formato '{2}'. - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 9f423d11ca893..c193947399c47 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -117,11 +117,6 @@ Ошибка в синтаксисе командной строки: "{0}" не является допустимым значением для параметра "{1}". Значение должно иметь форму "{2}". - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index c5ecfe140264f..a16b0f13453e6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -117,11 +117,6 @@ Komut satırı söz dizimi hatası: '{0}', '{1}' seçeneği için geçerli bir değer değil. Değer '{2}' biçiminde olmalıdır. - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 5d35ee58d6efb..82659ca002b9d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -117,11 +117,6 @@ 命令行语法错误:“{0}”不是“{1}”选项的有效值。值的格式必须为 "{2}"。 - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 5a545f942c6dd..0223ca0ae8fbd 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -117,11 +117,6 @@ 命令列語法錯誤: '{0}' 對 '{1}' 選項而言不是有效的值。此值的格式必須是 '{2}'。 - - Arguments to a `with` expression must be simple assignments - Arguments to a `with` expression must be simple assignments - - '{0}' must match by init-only of overridden member '{1}' '{0}' must match by init-only of overridden member '{1}' diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs index f4943988233d7..babfae1022098 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs @@ -2171,15 +2171,17 @@ void M() { } comp.VerifyDiagnostics(); var cMembers = comp.GlobalNamespace.GetMember("C").GetMembers(); - // PROTOTYPE(init-only): expecting an 'init' setter AssertEx.SetEqual(new[] { + "C C.Clone()", "System.Int32 C.k__BackingField", "System.Int32 C.i.get", - "System.Int32 C.i { get; }", + "void modreq(System.Runtime.CompilerServices.IsExternalInit) C.i.init", + "System.Int32 C.i { get; init; }", "void C.M()", "System.Boolean C.Equals(C? )", "System.Boolean C.Equals(System.Object? )", "System.Int32 C.GetHashCode()", + "C..ctor(C )", "C..ctor(System.Int32 i)" }, cMembers.ToTestDisplayStrings()); foreach (var member in cMembers) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs index 6144cf466a7f5..f0a39ce51fc29 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs @@ -51,8 +51,9 @@ public void PositionalRecord2() Add( // C Type parameters "T"), Add( // Members - "System.Int32 C.x { get; }", - "T C.t { get; }", + "C C.Clone()", + "System.Int32 C.x { get; init; }", + "T C.t { get; init; }", "System.Boolean C.Equals(C? )", "System.Boolean C.Equals(System.Object? )", "System.Boolean System.Object.Equals(System.Object obj)", diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index 960d9d182f93c..1452f13a56343 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -19,13 +19,18 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics public class RecordTests : CompilingTestBase { private static CSharpCompilation CreateCompilation(CSharpTestSource source) - => CSharpTestBase.CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + => CSharpTestBase.CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, + parseOptions: TestOptions.RegularPreview); - private CompilationVerifier CompileAndVerify(CSharpTestSource src, string? expectedOutput = null) + private CompilationVerifier CompileAndVerify( + CSharpTestSource src, + string? expectedOutput = null, + IEnumerable? references = null) => base.CompileAndVerify( - src, + new[] { src, IsExternalInitTypeDefinition }, expectedOutput: expectedOutput, parseOptions: TestOptions.RegularPreview, + references: references, // init-only is unverifiable verify: Verification.Skipped); @@ -46,9 +51,15 @@ data class Point { } // (2,12): error CS8652: The feature 'records' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // class Point(int x, int y); Diagnostic(ErrorCode.ERR_FeatureInPreview, "(int x, int y)").WithArguments("records").WithLocation(2, 12), - // (2,12): error CS8761: Records must have both a 'data' modifier and parameter list + // (2,12): error CS8800: Records must have both a 'data' modifier and non-empty parameter list // class Point(int x, int y); Diagnostic(ErrorCode.ERR_BadRecordDeclaration, "(int x, int y)").WithLocation(2, 12), + // (2,17): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported + // class Point(int x, int y); + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "x").WithArguments("System.Runtime.CompilerServices.IsExternalInit").WithLocation(2, 17), + // (2,24): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported + // class Point(int x, int y); + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "y").WithArguments("System.Runtime.CompilerServices.IsExternalInit").WithLocation(2, 24), // (2,26): error CS8652: The feature 'records' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // class Point(int x, int y); Diagnostic(ErrorCode.ERR_FeatureInPreview, ";").WithArguments("records").WithLocation(2, 26) @@ -70,6 +81,12 @@ data class Point { } // (2,17): error CS8652: The feature 'records' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // data class Point(int x, int y); Diagnostic(ErrorCode.ERR_FeatureInPreview, "(int x, int y)").WithArguments("records").WithLocation(2, 17), + // (2,22): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported + // data class Point(int x, int y); + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "x").WithArguments("System.Runtime.CompilerServices.IsExternalInit").WithLocation(2, 22), + // (2,29): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported + // data class Point(int x, int y); + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "y").WithArguments("System.Runtime.CompilerServices.IsExternalInit").WithLocation(2, 29), // (2,31): error CS8652: The feature 'records' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // data class Point(int x, int y); Diagnostic(ErrorCode.ERR_FeatureInPreview, ";").WithArguments("records").WithLocation(2, 31) @@ -471,21 +488,20 @@ public static void Main() public void WithExpr2() { var src = @" +using System; data class C(int X) { public static void Main() { - var c = new C(0); - c = c with { }; + var c1 = new C(1); + c1 = c1 with { }; + var c2 = c1 with { X = 11 }; + Console.WriteLine(c1.X); + Console.WriteLine(c2.X); } }"; - var comp = CreateCompilation(src); - // PROTOTYPE: records don't auto-generate Clone at the moment - comp.VerifyDiagnostics( - // (7,13): error CS8803: The receiver type 'C' does not have an accessible parameterless instance method named "Clone". - // c = c with { }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c").WithArguments("C").WithLocation(7, 13) - ); + var verifier = CompileAndVerify(src, expectedOutput: @"1 +11"); } [Fact] @@ -534,14 +550,14 @@ public void WithExpr6() var src = @" class B { - public int X { get; } + public int X { get; init; } public B Clone() => null; } -data class C(int X) : B +class C : B { public static void Main() { - var c = new C(0); + var c = new C(); c = c with { }; } }"; @@ -562,11 +578,12 @@ class B public int X { get; } public virtual B Clone() => null; } -data class C(int X) : B +class C : B { + public new int X { get; init; } public static void Main() { - var c = new C(0); + var c = new C(); B b = c; b = b with { X = 0 }; var b2 = c with { X = 0 }; @@ -575,12 +592,12 @@ public static void Main() }"; var comp = CreateCompilation(src); comp.VerifyDiagnostics( - // (13,22): error CS8808: All arguments to a `with` expression must be compiler-generated record properties. + // (14,22): error CS0200: Property or indexer 'B.X' cannot be assigned to -- it is read only // b = b with { X = 0 }; - Diagnostic(ErrorCode.ERR_WithMemberIsNotRecordProperty, "X").WithLocation(13, 22), - // (14,27): error CS8808: All arguments to a `with` expression must be compiler-generated record properties. + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "X").WithArguments("B.X").WithLocation(14, 22), + // (15,27): error CS0200: Property or indexer 'B.X' cannot be assigned to -- it is read only // var b2 = c with { X = 0 }; - Diagnostic(ErrorCode.ERR_WithMemberIsNotRecordProperty, "X").WithLocation(14, 27) + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "X").WithArguments("B.X").WithLocation(15, 27) ); } @@ -681,7 +698,7 @@ .maxstack 3 IL_0011: callvirt ""C C.Clone()"" IL_0016: dup IL_0017: ldc.i4.5 - IL_0018: stfld ""int C.k__BackingField"" + IL_0018: callvirt ""void C.X.init"" IL_001d: callvirt ""int C.X.get"" IL_0022: call ""void System.Console.WriteLine(int)"" IL_0027: ret @@ -719,7 +736,7 @@ .maxstack 3 IL_000d: callvirt ""C C.Clone()"" IL_0012: dup IL_0013: ldc.i4.5 - IL_0014: stfld ""int C.k__BackingField"" + IL_0014: callvirt ""void C.X.init"" IL_0019: call ""void System.Console.WriteLine(object)"" IL_001e: ret }"); @@ -759,13 +776,13 @@ .maxstack 3 IL_000d: callvirt ""C C.Clone()"" IL_0012: dup IL_0013: ldc.i4.5 - IL_0014: stfld ""int C.k__BackingField"" + IL_0014: callvirt ""void C.X.init"" IL_0019: dup IL_001a: call ""void System.Console.WriteLine(object)"" IL_001f: callvirt ""C C.Clone()"" IL_0024: dup IL_0025: ldc.i4.2 - IL_0026: stfld ""int C.k__BackingField"" + IL_0026: callvirt ""void C.Y.init"" IL_002b: call ""void System.Console.WriteLine(object)"" IL_0030: ret }"); @@ -788,10 +805,7 @@ public static void Main() comp.VerifyDiagnostics( // (8,22): error CS1525: Invalid expression term '=' // c = c with { = 5 }; - Diagnostic(ErrorCode.ERR_InvalidExprTerm, "=").WithArguments("=").WithLocation(8, 22), - // (8,22): error CS1061: 'C' does not contain a definition for '' and no accessible extension method '' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?) - // c = c with { = 5 }; - Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "").WithArguments("C", "").WithLocation(8, 22) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "=").WithArguments("=").WithLocation(8, 22) ); } @@ -911,10 +925,7 @@ public static void Main() comp.VerifyDiagnostics( // (5,25): warning CS0067: The event 'C.X' is never used // public event Action X; - Diagnostic(ErrorCode.WRN_UnreferencedEvent, "X").WithArguments("C.X").WithLocation(5, 25), - // (10,22): error CS8808: All arguments to a `with` expression must be compiler-generated record properties. - // c = c with { X = null }; - Diagnostic(ErrorCode.ERR_WithMemberIsNotRecordProperty, "X").WithLocation(10, 22) + Diagnostic(ErrorCode.WRN_UnreferencedEvent, "X").WithArguments("C.X").WithLocation(5, 25) ); } @@ -937,9 +948,12 @@ public static void Main() }"; var comp = CreateCompilation(src); comp.VerifyDiagnostics( - // (12,22): error CS8808: All arguments to a `with` expression must be compiler-generated record properties. + // (12,22): error CS0572: 'X': cannot reference a type through an expression; try 'B.X' instead // b = b with { X = 0 }; - Diagnostic(ErrorCode.ERR_WithMemberIsNotRecordProperty, "X").WithLocation(12, 22) + Diagnostic(ErrorCode.ERR_BadTypeReference, "X").WithArguments("X", "B.X").WithLocation(12, 22), + // (12,22): error CS1913: Member 'X' cannot be initialized. It is not a field or property. + // b = b with { X = 0 }; + Diagnostic(ErrorCode.ERR_MemberCannotBeInitialized, "X").WithArguments("X").WithLocation(12, 22) ); } @@ -983,9 +997,9 @@ public static void Main() }"; var comp = CreateCompilation(src); comp.VerifyDiagnostics( - // (12,22): error CS1061: 'B' does not contain a definition for 'Y' and no accessible extension method 'Y' accepting a first argument of type 'B' could be found (are you missing a using directive or an assembly reference?) + // (12,22): error CS0117: 'B' does not contain a definition for 'Y' // b = b with { Y = 2 }; - Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Y").WithArguments("B", "Y").WithLocation(12, 22) + Diagnostic(ErrorCode.ERR_NoSuchMember, "Y").WithArguments("B", "Y").WithLocation(12, 22) ); } @@ -993,23 +1007,24 @@ public static void Main() public void WithExprNestedErrors() { var src = @" -data class C(int X) +class C { + public int X { get; init; } public static void Main() { - var c = new C(0); + var c = new C(); c = c with { X = """"-3 }; } } "; var comp = CreateCompilation(src); comp.VerifyDiagnostics( - // (7,13): error CS8803: The 'with' expression requires the receiver type 'C' to have a single accessible non-inherited instance method named "Clone". + // (8,13): error CS8808: The receiver type 'C' does not have an accessible parameterless instance method named "Clone". // c = c with { X = ""-3 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c").WithArguments("C").WithLocation(7, 13), - // (7,26): error CS0019: Operator '-' cannot be applied to operands of type 'string' and 'int' + Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c").WithArguments("C").WithLocation(8, 13), + // (8,26): error CS0019: Operator '-' cannot be applied to operands of type 'string' and 'int' // c = c with { X = ""-3 }; - Diagnostic(ErrorCode.ERR_BadBinaryOps, @"""""-3").WithArguments("-", "string", "int").WithLocation(7, 26) + Diagnostic(ErrorCode.ERR_BadBinaryOps, @"""""-3").WithArguments("-", "string", "int").WithLocation(8, 26) ); } @@ -1036,27 +1051,27 @@ public static void Main() } [Fact] - public void WithExprPropertyInaccessibleGet() + public void WithExprPropertyInaccessibleSet() { var src = @" -data class C(int X) +class C { - public int X { private get; set; } + public int X { get; private set; } public C Clone() => null; } class D { public static void Main() { - var c = new C(0); + var c = new C(); c = c with { X = 0 }; } }"; var comp = CreateCompilation(src); comp.VerifyDiagnostics( - // (12,22): error CS8808: All arguments to a `with` expression must be compiler-generated record properties. + // (12,22): error CS0272: The property or indexer 'C.X' cannot be used in this context because the set accessor is inaccessible // c = c with { X = 0 }; - Diagnostic(ErrorCode.ERR_WithMemberIsNotRecordProperty, "X").WithLocation(12, 22) + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "X").WithArguments("C.X").WithLocation(12, 22) ); } @@ -1096,11 +1111,11 @@ .maxstack 3 IL_000d: dup IL_000e: ldstr ""Y"" IL_0013: call ""int C.W(string)"" - IL_0018: stfld ""int C.k__BackingField"" + IL_0018: callvirt ""void C.Y.init"" IL_001d: dup IL_001e: ldstr ""X"" IL_0023: call ""int C.W(string)"" - IL_0028: stfld ""int C.k__BackingField"" + IL_0028: callvirt ""void C.X.init"" IL_002d: pop IL_002e: ret }"); @@ -1132,7 +1147,7 @@ .maxstack 3 IL_000c: dup IL_000d: ldc.i4.s 11 IL_000f: conv.i8 - IL_0010: stfld ""long C.k__BackingField"" + IL_0010: callvirt ""void C.X.init"" IL_0015: callvirt ""long C.X.get"" IL_001a: call ""void System.Console.WriteLine(long)"" IL_001f: ret @@ -1185,7 +1200,7 @@ .locals init (S V_0) //s IL_0015: dup IL_0016: ldloc.0 IL_0017: call ""long S.op_Implicit(S)"" - IL_001c: stfld ""long C.k__BackingField"" + IL_001c: callvirt ""void C.X.init"" IL_0021: callvirt ""long C.X.get"" IL_0026: call ""void System.Console.WriteLine(long)"" IL_002b: ret @@ -1274,26 +1289,81 @@ public static void Main() var verifier = CompileAndVerify(src, expectedOutput: "abc"); } + [Fact] + public void WithExprConversions6() + { + var src = @" +using System; +struct S +{ + private int _i; + public S(int i) + { + _i = i; + } + public static implicit operator int(S s) + { + Console.WriteLine(""conversion""); + return s._i; + } +} +class C +{ + private readonly long _x; + public long X { get => _x; init { Console.WriteLine(""set""); _x = value; } } + public C Clone() => new C(); + public static void Main() + { + var c = new C(); + var s = new S(11); + Console.WriteLine((c with { X = s }).X); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @" +conversion +set +11"); + verifier.VerifyIL("C.Main", @" +{ + // Code size 43 (0x2b) + .maxstack 3 + .locals init (S V_0) //s + IL_0000: newobj ""C..ctor()"" + IL_0005: ldloca.s V_0 + IL_0007: ldc.i4.s 11 + IL_0009: call ""S..ctor(int)"" + IL_000e: callvirt ""C C.Clone()"" + IL_0013: dup + IL_0014: ldloc.0 + IL_0015: call ""int S.op_Implicit(S)"" + IL_001a: conv.i8 + IL_001b: callvirt ""void C.X.init"" + IL_0020: callvirt ""long C.X.get"" + IL_0025: call ""void System.Console.WriteLine(long)"" + IL_002a: ret +}"); + } + [Fact] public void WithExprStaticProperty() { var src = @" -data class C(int X) +class C { public static int X { get; } public C Clone() => null; public static void Main() { - var c = new C(0); + var c = new C(); c = c with { }; c = c with { X = 11 }; } }"; var comp = CreateCompilation(src); comp.VerifyDiagnostics( - // (10,22): error CS8808: All arguments to a `with` expression must be instance properties or fields. + // (10,22): error CS0176: Member 'C.X' cannot be accessed with an instance reference; qualify it with a type name instead // c = c with { X = 11 }; - Diagnostic(ErrorCode.ERR_WithMemberIsNotRecordProperty, "X").WithLocation(10, 22) + Diagnostic(ErrorCode.ERR_ObjectProhibited, "X").WithArguments("C.X").WithLocation(10, 22) ); } @@ -1314,33 +1384,35 @@ public static void Main() }"; var comp = CreateCompilation(src); comp.VerifyDiagnostics( - // (10,22): error CS8808: All arguments to a `with` expression must be compiler-generated record properties. + // (10,22): error CS1913: Member 'X' cannot be initialized. It is not a field or property. // c = c with { X = 11 }; - Diagnostic(ErrorCode.ERR_WithMemberIsNotRecordProperty, "X").WithLocation(10, 22)); + Diagnostic(ErrorCode.ERR_MemberCannotBeInitialized, "X").WithArguments("X").WithLocation(10, 22) + ); } [Fact] public void WithExprStaticWithMethod() { var src = @" -data class C(int X) +class C { + public int X = 0; public static C Clone() => null; public static void Main() { - var c = new C(0); + var c = new C(); c = c with { }; c = c with { X = 11 }; } }"; var comp = CreateCompilation(src); comp.VerifyDiagnostics( - // (8,13): error CS8803: The receiver type 'C' does not have an accessible parameterless instance method named "Clone". + // (9,13): error CS8808: The receiver type 'C' does not have an accessible parameterless instance method named "Clone". // c = c with { }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c").WithArguments("C").WithLocation(8, 13), - // (9,13): error CS8803: The receiver type 'C' does not have an accessible parameterless instance method named "Clone". + Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c").WithArguments("C").WithLocation(9, 13), + // (10,13): error CS8808: The receiver type 'C' does not have an accessible parameterless instance method named "Clone". // c = c with { X = 11 }; - Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c").WithArguments("C").WithLocation(9, 13) + Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "c").WithArguments("C").WithLocation(10, 13) ); } @@ -1352,24 +1424,25 @@ class B { public B Clone() => null; } -data class C(int X) : B +class C : B { - public static new C Clone() => null; + public int X = 0; + public static new C Clone() => null; // static public static void Main() { - var c = new C(0); + var c = new C(); c = c with { }; c = c with { X = 11 }; } }"; var comp = CreateCompilation(src); comp.VerifyDiagnostics( - // (12,13): error CS0266: Cannot implicitly convert type 'B' to 'C'. An explicit conversion exists (are you missing a cast?) + // (13,13): error CS0266: Cannot implicitly convert type 'B' to 'C'. An explicit conversion exists (are you missing a cast?) // c = c with { }; - Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c with { }").WithArguments("B", "C").WithLocation(12, 13), - // (13,22): error CS1061: 'B' does not contain a definition for 'X' and no accessible extension method 'X' accepting a first argument of type 'B' could be found (are you missing a using directive or an assembly reference?) + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c with { }").WithArguments("B", "C").WithLocation(13, 13), + // (14,22): error CS0117: 'B' does not contain a definition for 'X' // c = c with { X = 11 }; - Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "X").WithArguments("B", "X").WithLocation(13, 22) + Diagnostic(ErrorCode.ERR_NoSuchMember, "X").WithArguments("B", "X").WithLocation(14, 22) ); } @@ -1380,7 +1453,7 @@ public void WithExprBadMemberBadType() class C { public C Clone() => null; - public int X { get; } + public int X { get; init; } public static void Main() { var c = new C(); @@ -1389,9 +1462,6 @@ public static void Main() }"; var comp = CreateCompilation(src); comp.VerifyDiagnostics( - // (9,22): error CS8808: All arguments to a `with` expression must be compiler-generated record properties. - // c = c with { X = "a" }; - Diagnostic(ErrorCode.ERR_WithMemberIsNotRecordProperty, "X").WithLocation(9, 22), // (9,26): error CS0029: Cannot implicitly convert type 'string' to 'int' // c = c with { X = "a" }; Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""a""").WithArguments("string", "int").WithLocation(9, 26) @@ -1402,30 +1472,13 @@ public static void Main() public void WithExprCloneReturnDifferent() { var src = @" -data class B(int X) { } -class C : B -{ - public C(int x) : base(x) { } - public B Clone() => new B(0); - public static void Main() - { - var c = new C(0); - var b = c with { X = 0 }; - } -}"; - var comp = CreateCompilation(src); - comp.VerifyDiagnostics(); - } - - [Fact] - public void WithExprCloneReturnDifferent2() - { - var src = @" -data class B(int X) { } +class B +{ + public int X { get; init; } +} class C : B { - public C() : base(0) {} - public B Clone() => new B(0); + public B Clone() => new B(); public static void Main() { var c = new C(); @@ -1480,9 +1533,9 @@ public static void Main() var comp = CreateCompilation(src); comp.VerifyDiagnostics( - // (8,22): error CS8811: Arguments to a `with` expression must be simple assignments + // (8,22): error CS0747: Invalid initializer member declarator // c = c with { 5 }; - Diagnostic(ErrorCode.ERR_BadWithExpressionArgument, "5").WithLocation(8, 22), + Diagnostic(ErrorCode.ERR_InvalidInitializerElementInitializer, "5").WithLocation(8, 22), // (9,22): error CS1513: } expected // c = c with { { X = 2 } }; Diagnostic(ErrorCode.ERR_RbraceExpected, "{").WithLocation(9, 22), @@ -1504,6 +1557,212 @@ public static void Main() ); } + [Fact] + public void WithExprNotRecord() + { + var src = @" +using System; +class C +{ + public int X { get; set; } + public string Y { get; init; } + public long Z; + public event Action E; + + public C Clone() => new C { + X = this.X, + Y = this.Y, + Z = this.Z, + E = this.E, + }; + + public static void Main() + { + var c = new C() { X = 1, Y = ""2"", Z = 3, E = () => { } }; + var c2 = c with {}; + Console.WriteLine(c.Equals(c2)); + Console.WriteLine(c2.X); + Console.WriteLine(c2.Y); + Console.WriteLine(c2.Z); + Console.WriteLine(ReferenceEquals(c.E, c2.E)); + var c3 = c with { Y = ""3"", X = 2 }; + Console.WriteLine(c.Y); + Console.WriteLine(c3.Y); + Console.WriteLine(c.X); + Console.WriteLine(c3.X); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @" +False +1 +2 +3 +True +2 +3 +1 +2"); + } + + [Fact] + public void WithExprNotRecord2() + { + var comp1 = CreateCompilation(@" +public class C +{ + public int X { get; set; } + public string Y { get; init; } + public long Z; + + public C Clone() => new C { + X = this.X, + Y = this.Y, + Z = this.Z, + }; +}"); + comp1.VerifyDiagnostics(); + + var verifier = CompileAndVerify(@" +class D +{ + public C M(C c) => c with + { + X = 5, + Y = ""a"", + Z = 2, + }; +}", references: new[] { comp1.EmitToImageReference() }); + + verifier.VerifyIL("D.M", @" +{ + // Code size 33 (0x21) + .maxstack 3 + IL_0000: ldarg.1 + IL_0001: callvirt ""C C.Clone()"" + IL_0006: dup + IL_0007: ldc.i4.5 + IL_0008: callvirt ""void C.X.set"" + IL_000d: dup + IL_000e: ldstr ""a"" + IL_0013: callvirt ""void C.Y.init"" + IL_0018: dup + IL_0019: ldc.i4.2 + IL_001a: conv.i8 + IL_001b: stfld ""long C.Z"" + IL_0020: ret +}"); + } + + [Fact] + public void WithExprAssignToRef1() + { + var src = @" +using System; +data class C(int Y) +{ + private readonly int[] _a = new[] { 0 }; + public ref int X => ref _a[0]; + + public C Clone() => new C(0); + + public static void Main() + { + var c = new C(0) { X = 5 }; + Console.WriteLine(c.X); + c = c with { X = 1 }; + Console.WriteLine(c.X); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @" +5 +1"); + verifier.VerifyIL("C.Main", @" +{ + // Code size 51 (0x33) + .maxstack 3 + IL_0000: ldc.i4.0 + IL_0001: newobj ""C..ctor(int)"" + IL_0006: dup + IL_0007: callvirt ""ref int C.X.get"" + IL_000c: ldc.i4.5 + IL_000d: stind.i4 + IL_000e: dup + IL_000f: callvirt ""ref int C.X.get"" + IL_0014: ldind.i4 + IL_0015: call ""void System.Console.WriteLine(int)"" + IL_001a: callvirt ""C C.Clone()"" + IL_001f: dup + IL_0020: callvirt ""ref int C.X.get"" + IL_0025: ldc.i4.1 + IL_0026: stind.i4 + IL_0027: callvirt ""ref int C.X.get"" + IL_002c: ldind.i4 + IL_002d: call ""void System.Console.WriteLine(int)"" + IL_0032: ret +}"); + } + + [Fact] + public void WithExprAssignToRef2() + { + var src = @" +using System; +data class C(int Y) +{ + private readonly int[] _a = new[] { 0 }; + public ref int X + { + get => ref _a[0]; + set { } + } + + public C Clone() => new C(0); + + public static void Main() + { + var a = new[] { 0 }; + var c = new C(0) { X = ref a[0] }; + Console.WriteLine(c.X); + c = c with { X = ref a[0] }; + Console.WriteLine(c.X); + } +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (9,9): error CS8147: Properties which return by reference cannot have set accessors + // set { } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithArguments("C.X.set").WithLocation(9, 9), + // (17,32): error CS1525: Invalid expression term 'ref' + // var c = new C(0) { X = ref a[0] }; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "ref a[0]").WithArguments("ref").WithLocation(17, 32), + // (17,32): error CS1073: Unexpected token 'ref' + // var c = new C(0) { X = ref a[0] }; + Diagnostic(ErrorCode.ERR_UnexpectedToken, "ref").WithArguments("ref").WithLocation(17, 32), + // (19,26): error CS1073: Unexpected token 'ref' + // c = c with { X = ref a[0] }; + Diagnostic(ErrorCode.ERR_UnexpectedToken, "ref").WithArguments("ref").WithLocation(19, 26) + ); + } + + [Fact] + public void WithExpressionSameLHS() + { + var comp = CreateCompilation(@" +data class C(int X) +{ + public static void Main() + { + var c = new C(0); + c = c with { X = 1, X = 2}; + } +}"); + comp.VerifyDiagnostics( + // (7,29): error CS1912: Duplicate initialization of member 'X' + // c = c with { X = 1, X = 2}; + Diagnostic(ErrorCode.ERR_MemberAlreadyInitialized, "X").WithArguments("X").WithLocation(7, 29) + ); + } + [Fact] public void Inheritance_01() { @@ -1521,17 +1780,17 @@ internal A() { } data class B(object P1, object P2, object P3, object P4, object P5, object P6) : A { }"; - var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + var comp = CreateCompilation(source); comp.VerifyDiagnostics(); var actualMembers = GetProperties(comp, "B").ToTestDisplayStrings(); var expectedMembers = new[] { - "System.Object B.P1 { get; }", - "System.Object B.P2 { get; }", - "System.Object B.P3 { get; }", - "System.Object B.P4 { get; }", - "System.Object B.P5 { get; }", - "System.Object B.P6 { get; }", + "System.Object B.P1 { get; init; }", + "System.Object B.P2 { get; init; }", + "System.Object B.P3 { get; init; }", + "System.Object B.P4 { get; init; }", + "System.Object B.P5 { get; init; }", + "System.Object B.P6 { get; init; }", }; AssertEx.Equal(expectedMembers, actualMembers); } @@ -1549,13 +1808,13 @@ private data class B(object P1, object P2) : A { } }"; - var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + var comp = CreateCompilation(source); comp.VerifyDiagnostics(); var actualMembers = GetProperties(comp, "A.B").ToTestDisplayStrings(); var expectedMembers = new[] { - "System.Object A.B.P1 { get; }", - "System.Object A.B.P2 { get; }", + "System.Object A.B.P1 { get; init; }", + "System.Object A.B.P2 { get; init; }", }; AssertEx.Equal(expectedMembers, actualMembers); } @@ -1575,7 +1834,7 @@ data class B1(object P) : A { }"; var comp = CreateCompilation(sourceA); - AssertEx.Equal(new[] { "System.Object B1.P { get; }" }, GetProperties(comp, "B1").ToTestDisplayStrings()); + AssertEx.Equal(new[] { "System.Object B1.P { get; init; }" }, GetProperties(comp, "B1").ToTestDisplayStrings()); var refA = useCompilationReference ? comp.ToMetadataReference() : comp.EmitToImageReference(); var sourceB = @@ -1584,7 +1843,7 @@ data class B1(object P) : A }"; comp = CreateCompilation(sourceB, references: new[] { refA }, parseOptions: TestOptions.RegularPreview); comp.VerifyDiagnostics(); - AssertEx.Equal(new[] { "System.Object B2.P { get; }" }, GetProperties(comp, "B2").ToTestDisplayStrings()); + AssertEx.Equal(new[] { "System.Object B2.P { get; init; }" }, GetProperties(comp, "B2").ToTestDisplayStrings()); } [Fact] @@ -1605,18 +1864,18 @@ public object P4 { set { } } data class B(object P1, object P2, object P3, object P4, object P5, object P6, object P7) : A { }"; - var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview); + var comp = CreateCompilation(source); comp.VerifyDiagnostics(); var actualMembers = GetProperties(comp, "B").ToTestDisplayStrings(); var expectedMembers = new[] { - "System.Object B.P1 { get; }", - "System.Object B.P2 { get; }", - "System.Object B.P3 { get; }", - "System.Object B.P4 { get; }", - "System.Object B.P5 { get; }", - "System.Object B.P6 { get; }", - "System.Object B.P7 { get; }", + "System.Object B.P1 { get; init; }", + "System.Object B.P2 { get; init; }", + "System.Object B.P3 { get; init; }", + "System.Object B.P4 { get; init; }", + "System.Object B.P5 { get; init; }", + "System.Object B.P6 { get; init; }", + "System.Object B.P7 { get; init; }", }; AssertEx.Equal(expectedMembers, actualMembers); } @@ -1634,13 +1893,13 @@ internal A() { } data class B(int P1, object P2) : A { }"; - var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview); + var comp = CreateCompilation(source); comp.VerifyDiagnostics(); var actualMembers = GetProperties(comp, "B").ToTestDisplayStrings(); var expectedMembers = new[] { - "System.Int32 B.P1 { get; }", - "System.Object B.P2 { get; }", + "System.Int32 B.P1 { get; init; }", + "System.Object B.P2 { get; init; }", }; AssertEx.Equal(expectedMembers, actualMembers); } @@ -1673,15 +1932,15 @@ static void Main() }"; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (15,9): error CS0200: Property or indexer 'B.X' cannot be assigned to -- it is read only + // (15,9): error CS8852: Init-only property or indexer 'B.X' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor. // b.X = 4; - Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "b.X").WithArguments("B.X").WithLocation(15, 9), - // (16,9): error CS0200: Property or indexer 'B.Y' cannot be assigned to -- it is read only + Diagnostic(ErrorCode.ERR_AssignmentInitOnly, "b.X").WithArguments("B.X").WithLocation(15, 9), + // (16,9): error CS8852: Init-only property or indexer 'B.Y' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor. // b.Y = 5; - Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "b.Y").WithArguments("B.Y").WithLocation(16, 9), - // (17,9): error CS0200: Property or indexer 'B.Z' cannot be assigned to -- it is read only + Diagnostic(ErrorCode.ERR_AssignmentInitOnly, "b.Y").WithArguments("B.Y").WithLocation(16, 9), + // (17,9): error CS8852: Init-only property or indexer 'B.Z' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor. // b.Z = 6; - Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "b.Z").WithArguments("B.Z").WithLocation(17, 9)); + Diagnostic(ErrorCode.ERR_AssignmentInitOnly, "b.Z").WithArguments("B.Z").WithLocation(17, 9)); } [Fact] @@ -1764,15 +2023,16 @@ data struct S(object X, object Y) : IA, IB // (9,36): error CS0738: 'C' does not implement interface member 'IA.X'. 'C.X' cannot implement 'IA.X' because it does not have the matching return type of 'int'. // data class C(object X, object Y) : IA, IB Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberWrongReturnType, "IA").WithArguments("C", "IA.X", "C.X", "int").WithLocation(9, 36), - // (9,40): error CS0535: 'C' does not implement interface member 'IB.Y.set' + // (9,40): error CS8854: 'C' does not implement interface member 'IB.Y.set'. 'C.Y.init' cannot implement 'IB.Y.set'. // data class C(object X, object Y) : IA, IB - Diagnostic(ErrorCode.ERR_UnimplementedInterfaceMember, "IB").WithArguments("C", "IB.Y.set").WithLocation(9, 40), + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberWrongInitOnly, "IB").WithArguments("C", "IB.Y.set", "C.Y.init").WithLocation(9, 40), // (12,37): error CS0738: 'S' does not implement interface member 'IA.X'. 'S.X' cannot implement 'IA.X' because it does not have the matching return type of 'int'. // data struct S(object X, object Y) : IA, IB Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberWrongReturnType, "IA").WithArguments("S", "IA.X", "S.X", "int").WithLocation(12, 37), - // (12,41): error CS0535: 'S' does not implement interface member 'IB.Y.set' + // (12,41): error CS8854: 'S' does not implement interface member 'IB.Y.set'. 'S.Y.init' cannot implement 'IB.Y.set'. // data struct S(object X, object Y) : IA, IB - Diagnostic(ErrorCode.ERR_UnimplementedInterfaceMember, "IB").WithArguments("S", "IB.Y.set").WithLocation(12, 41)); + Diagnostic(ErrorCode.ERR_CloseUnimplementedInterfaceMemberWrongInitOnly, "IB").WithArguments("S", "IB.Y.set", "S.Y.init").WithLocation(12, 41) + ); } [Fact] @@ -1800,15 +2060,19 @@ data class B(int X, int Y) : A var actualMembers = comp.GetMember("B").GetMembers().ToTestDisplayStrings(); var expectedMembers = new[] { + "B B.Clone()", "System.Int32 B.k__BackingField", "System.Int32 B.X.get", - "System.Int32 B.X { get; }", + "void modreq(System.Runtime.CompilerServices.IsExternalInit) B.X.init", + "System.Int32 B.X { get; init; }", "System.Int32 B.k__BackingField", "System.Int32 B.Y.get", - "System.Int32 B.Y { get; }", + "void modreq(System.Runtime.CompilerServices.IsExternalInit) B.Y.init", + "System.Int32 B.Y { get; init; }", "System.Boolean B.Equals(B? )", "System.Boolean B.Equals(System.Object? )", "System.Int32 B.GetHashCode()", + "B..ctor(B )", "B..ctor(System.Int32 X, System.Int32 Y)" }; AssertEx.Equal(expectedMembers, actualMembers); @@ -1836,15 +2100,19 @@ data class B(int X, int Y) : A var actualMembers = comp.GetMember("B").GetMembers().ToTestDisplayStrings(); var expectedMembers = new[] { + "B B.Clone()", "System.Int32 B.k__BackingField", "System.Int32 B.X.get", - "System.Int32 B.X { get; }", + "void modreq(System.Runtime.CompilerServices.IsExternalInit) B.X.init", + "System.Int32 B.X { get; init; }", "System.Int32 B.k__BackingField", "System.Int32 B.Y.get", - "System.Int32 B.Y { get; }", + "void modreq(System.Runtime.CompilerServices.IsExternalInit) B.Y.init", + "System.Int32 B.Y { get; init; }", "System.Boolean B.Equals(B? )", "System.Boolean B.Equals(System.Object? )", "System.Int32 B.GetHashCode()", + "B..ctor(B )", "B..ctor(System.Int32 X, System.Int32 Y)" }; AssertEx.Equal(expectedMembers, actualMembers); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs index 6a458908d4ad6..386355a48ecb1 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs @@ -4,7 +4,6 @@ #nullable enable -using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; @@ -15,10 +14,14 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests public class RecordTests : CompilingTestBase { private static CSharpCompilation CreateCompilation(CSharpTestSource source) - => CSharpTestBase.CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + => CSharpTestBase.CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview); private CompilationVerifier CompileAndVerify(CSharpTestSource src, string? expectedOutput = null) - => base.CompileAndVerify(src, expectedOutput: expectedOutput, parseOptions: TestOptions.RegularPreview); + => base.CompileAndVerify(new[] { src, IsExternalInitTypeDefinition }, + expectedOutput: expectedOutput, + parseOptions: TestOptions.RegularPreview, + // init-only fails verification + verify: Verification.Skipped); [Fact] public void GeneratedConstructor() @@ -26,7 +29,7 @@ public void GeneratedConstructor() var comp = CreateCompilation(@"data class C(int x, string y);"); comp.VerifyDiagnostics(); var c = comp.GlobalNamespace.GetTypeMember("C"); - var ctor = c.GetMethod(".ctor"); + var ctor = (MethodSymbol)c.GetMembers(".ctor")[0]; Assert.Equal(2, ctor.ParameterCount); var x = ctor.Parameters[0]; @@ -45,7 +48,7 @@ public void GeneratedConstructorDefaultValues() comp.VerifyDiagnostics(); var c = comp.GlobalNamespace.GetTypeMember("C"); Assert.Equal(1, c.Arity); - var ctor = c.GetMethod(".ctor"); + var ctor = (MethodSymbol)c.GetMembers(".ctor")[0]; Assert.Equal(0, ctor.Arity); Assert.Equal(2, ctor.ParameterCount); @@ -74,7 +77,7 @@ public C(int a, string b) Diagnostic(ErrorCode.ERR_DuplicateRecordConstructor, "(int x, string y)").WithLocation(2, 13) ); var c = comp.GlobalNamespace.GetTypeMember("C"); - var ctor = c.GetMethod(".ctor"); + var ctor = (MethodSymbol)c.GetMembers(".ctor")[0]; Assert.Equal(2, ctor.ParameterCount); var a = ctor.Parameters[0]; @@ -99,26 +102,32 @@ public C(int a, int b) // overload comp.VerifyDiagnostics(); var c = comp.GlobalNamespace.GetTypeMember("C"); var ctors = c.GetMembers(".ctor"); - Assert.Equal(2, ctors.Length); + Assert.Equal(3, ctors.Length); foreach (MethodSymbol ctor in ctors) { - Assert.Equal(2, ctor.ParameterCount); - - var p1 = ctor.Parameters[0]; - Assert.Equal(SpecialType.System_Int32, p1.Type.SpecialType); - var p2 = ctor.Parameters[1]; - if (ctor is SynthesizedRecordConstructor) + if (ctor.ParameterCount == 2) { - Assert.Equal("x", p1.Name); - Assert.Equal("y", p2.Name); - Assert.Equal(SpecialType.System_String, p2.Type.SpecialType); + var p1 = ctor.Parameters[0]; + Assert.Equal(SpecialType.System_Int32, p1.Type.SpecialType); + var p2 = ctor.Parameters[1]; + if (ctor is SynthesizedRecordConstructor) + { + Assert.Equal("x", p1.Name); + Assert.Equal("y", p2.Name); + Assert.Equal(SpecialType.System_String, p2.Type.SpecialType); + } + else + { + Assert.Equal("a", p1.Name); + Assert.Equal("b", p2.Name); + Assert.Equal(SpecialType.System_Int32, p2.Type.SpecialType); + } } else { - Assert.Equal("a", p1.Name); - Assert.Equal("b", p2.Name); - Assert.Equal(SpecialType.System_Int32, p2.Type.SpecialType); + Assert.Equal(1, ctor.ParameterCount); + Assert.True(c.Equals(ctor.Parameters[0].Type, TypeCompareKind.ConsiderEverything)); } } } @@ -134,7 +143,8 @@ public void GeneratedProperties() Assert.NotNull(x.GetMethod); Assert.Equal(MethodKind.PropertyGet, x.GetMethod.MethodKind); Assert.Equal(SpecialType.System_Int32, x.Type.SpecialType); - Assert.True(x.IsReadOnly); + Assert.False(x.IsReadOnly); + Assert.False(x.IsWriteOnly); Assert.Equal(Accessibility.Public, x.DeclaredAccessibility); Assert.False(x.IsVirtual); Assert.False(x.IsStatic); @@ -150,12 +160,21 @@ public void GeneratedProperties() Assert.Equal(x, getAccessor.AssociatedSymbol); Assert.Equal(c, getAccessor.ContainingSymbol); Assert.Equal(c, getAccessor.ContainingType); + Assert.Equal(Accessibility.Public, getAccessor.DeclaredAccessibility); + + var setAccessor = x.SetMethod; + Assert.Equal(x, setAccessor.AssociatedSymbol); + Assert.Equal(c, setAccessor.ContainingSymbol); + Assert.Equal(c, setAccessor.ContainingType); + Assert.Equal(Accessibility.Public, setAccessor.DeclaredAccessibility); + Assert.True(setAccessor.IsInitOnly); var y = (SourceOrRecordPropertySymbol)c.GetProperty("y"); Assert.NotNull(y.GetMethod); Assert.Equal(MethodKind.PropertyGet, y.GetMethod.MethodKind); Assert.Equal(SpecialType.System_Int32, y.Type.SpecialType); - Assert.True(y.IsReadOnly); + Assert.False(y.IsReadOnly); + Assert.False(y.IsWriteOnly); Assert.Equal(Accessibility.Public, y.DeclaredAccessibility); Assert.False(x.IsVirtual); Assert.False(x.IsStatic); @@ -171,6 +190,13 @@ public void GeneratedProperties() Assert.Equal(y, getAccessor.AssociatedSymbol); Assert.Equal(c, getAccessor.ContainingSymbol); Assert.Equal(c, getAccessor.ContainingType); + + setAccessor = y.SetMethod; + Assert.Equal(y, setAccessor.AssociatedSymbol); + Assert.Equal(c, setAccessor.ContainingSymbol); + Assert.Equal(c, setAccessor.ContainingType); + Assert.Equal(Accessibility.Public, setAccessor.DeclaredAccessibility); + Assert.True(setAccessor.IsInitOnly); } [Fact] @@ -355,7 +381,6 @@ public void RecordEquals_08() data class C(int X, int Y) { public int Z; - public static void Main() { var c = new C(1, 2); @@ -412,7 +437,6 @@ public void RecordEquals_09() data class C(int X, int Y) { public int Z { get; set; } - public static void Main() { var c = new C(1, 2); @@ -439,7 +463,6 @@ public void RecordEquals_10() data class C(int X, int Y) { public static int Z; - public static void Main() { var c = new C(1, 2); @@ -491,7 +514,6 @@ data class C(int X, int Y) { static Dictionary s_dict = new Dictionary(); public int Z { get => s_dict[this]; set => s_dict[this] = value; } - public static void Main() { var c = new C(1, 2); @@ -542,7 +564,6 @@ public void RecordEquals_12() data class C(int X, int Y) { private event Action E; - public static void Main() { var c = new C(1, 2); @@ -588,6 +609,207 @@ .maxstack 3 IL_0049: ret IL_004a: ldc.i4.0 IL_004b: ret +}"); + } + + [Fact] + public void RecordClone1() + { + var comp = CreateCompilation("data class C(int x, int y);"); + comp.VerifyDiagnostics(); + + var c = comp.GlobalNamespace.GetTypeMember("C"); + var clone = c.GetMethod(WellKnownMemberNames.CloneMethodName); + Assert.Equal(0, clone.Arity); + Assert.Equal(0, clone.ParameterCount); + Assert.Equal(c, clone.ReturnType); + + var ctor = (MethodSymbol)c.GetMembers(".ctor")[1]; + Assert.Equal(1, ctor.ParameterCount); + Assert.True(ctor.Parameters[0].Type.Equals(c, TypeCompareKind.ConsiderEverything)); + + var verifier = CompileAndVerify(comp, verify: Verification.Fails); + verifier.VerifyIL("C." + WellKnownMemberNames.CloneMethodName, @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: newobj ""C..ctor(C)"" + IL_0006: ret +} +"); + verifier.VerifyIL("C..ctor(C)", @" +{ + // Code size 31 (0x1f) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: ldfld ""int C.k__BackingField"" + IL_000d: stfld ""int C.k__BackingField"" + IL_0012: ldarg.0 + IL_0013: ldarg.1 + IL_0014: ldfld ""int C.k__BackingField"" + IL_0019: stfld ""int C.k__BackingField"" + IL_001e: ret +}"); + } + + [Fact] + public void RecordClone2() + { + var comp = CreateCompilation(@" +data class C(int x, int y) +{ + public C(C other) { } +}"); + comp.VerifyDiagnostics(); + + var c = comp.GlobalNamespace.GetTypeMember("C"); + var clone = c.GetMethod(WellKnownMemberNames.CloneMethodName); + Assert.Equal(0, clone.Arity); + Assert.Equal(0, clone.ParameterCount); + Assert.Equal(c, clone.ReturnType); + + var ctor = (MethodSymbol)c.GetMembers(".ctor")[0]; + Assert.Equal(1, ctor.ParameterCount); + Assert.True(ctor.Parameters[0].Type.Equals(c, TypeCompareKind.ConsiderEverything)); + + var verifier = CompileAndVerify(comp, verify: Verification.Fails); + verifier.VerifyIL("C." + WellKnownMemberNames.CloneMethodName, @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: newobj ""C..ctor(C)"" + IL_0006: ret +} +"); + verifier.VerifyIL("C..ctor(C)", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: ret +}"); + } + + [Fact] + public void RecordClone3() + { + var comp = CreateCompilation(@" +using System; +public data class C(int x, int y) +{ + public event Action E; + public int Z; +}"); + comp.VerifyDiagnostics(); + + var c = comp.GlobalNamespace.GetTypeMember("C"); + var clone = c.GetMethod(WellKnownMemberNames.CloneMethodName); + Assert.Equal(0, clone.Arity); + Assert.Equal(0, clone.ParameterCount); + Assert.Equal(c, clone.ReturnType); + + var ctor = (MethodSymbol)c.GetMembers(".ctor")[1]; + Assert.Equal(1, ctor.ParameterCount); + Assert.True(ctor.Parameters[0].Type.Equals(c, TypeCompareKind.ConsiderEverything)); + + var verifier = CompileAndVerify(comp, verify: Verification.Fails); + verifier.VerifyIL("C." + WellKnownMemberNames.CloneMethodName, @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: newobj ""C..ctor(C)"" + IL_0006: ret +}"); + verifier.VerifyIL("C..ctor(C)", @" +{ + // Code size 55 (0x37) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: ldfld ""int C.k__BackingField"" + IL_000d: stfld ""int C.k__BackingField"" + IL_0012: ldarg.0 + IL_0013: ldarg.1 + IL_0014: ldfld ""int C.k__BackingField"" + IL_0019: stfld ""int C.k__BackingField"" + IL_001e: ldarg.0 + IL_001f: ldarg.1 + IL_0020: ldfld ""System.Action C.E"" + IL_0025: stfld ""System.Action C.E"" + IL_002a: ldarg.0 + IL_002b: ldarg.1 + IL_002c: ldfld ""int C.Z"" + IL_0031: stfld ""int C.Z"" + IL_0036: ret +}"); + } + + [Fact] + public void RecordClone4() + { + var comp = CreateCompilation(@" +using System; +public data struct S(int x, int y) +{ + public event Action E; + public int Z; +}"); + comp.VerifyDiagnostics( + // (5,25): warning CS0067: The event 'S.E' is never used + // public event Action E; + Diagnostic(ErrorCode.WRN_UnreferencedEvent, "E").WithArguments("S.E").WithLocation(5, 25) + ); + + var s = comp.GlobalNamespace.GetTypeMember("S"); + var clone = s.GetMethod(WellKnownMemberNames.CloneMethodName); + Assert.Equal(0, clone.Arity); + Assert.Equal(0, clone.ParameterCount); + Assert.Equal(s, clone.ReturnType); + + var ctor = (MethodSymbol)s.GetMembers(".ctor")[1]; + Assert.Equal(1, ctor.ParameterCount); + Assert.True(ctor.Parameters[0].Type.Equals(s, TypeCompareKind.ConsiderEverything)); + + var verifier = CompileAndVerify(comp, verify: Verification.Fails); + verifier.VerifyIL("S." + WellKnownMemberNames.CloneMethodName, @" +{ + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: ldobj ""S"" + IL_0006: newobj ""S..ctor(S)"" + IL_000b: ret +}"); + verifier.VerifyIL("S..ctor(S)", @" +{ + // Code size 49 (0x31) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: ldfld ""int S.k__BackingField"" + IL_0007: stfld ""int S.k__BackingField"" + IL_000c: ldarg.0 + IL_000d: ldarg.1 + IL_000e: ldfld ""int S.k__BackingField"" + IL_0013: stfld ""int S.k__BackingField"" + IL_0018: ldarg.0 + IL_0019: ldarg.1 + IL_001a: ldfld ""System.Action S.E"" + IL_001f: stfld ""System.Action S.E"" + IL_0024: ldarg.0 + IL_0025: ldarg.1 + IL_0026: ldfld ""int S.Z"" + IL_002b: stfld ""int S.Z"" + IL_0030: ret }"); } } diff --git a/src/Test/Utilities/Portable/Assert/AssertEx.cs b/src/Test/Utilities/Portable/Assert/AssertEx.cs index 4fb8980b75563..18a2b6a5ceb5f 100644 --- a/src/Test/Utilities/Portable/Assert/AssertEx.cs +++ b/src/Test/Utilities/Portable/Assert/AssertEx.cs @@ -441,6 +441,9 @@ public static void SetEqual(IEnumerable expected, IEnumerable actual, I } } + public static void SetEqual(T[] expected, T[] actual) + => SetEqual((IEnumerable)actual, expected); + public static void SetEqual(IEnumerable actual, params T[] expected) { var expectedSet = new HashSet(expected);