diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 91fcd884dc576..e2b3ed4647928 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1582,7 +1582,7 @@ private static bool AccessingAutoPropertyFromConstructor(BoundExpression receive propertySymbol = propertySymbol.OriginalDefinition; } - var sourceProperty = propertySymbol as SourcePropertySymbol; + var sourceProperty = propertySymbol as SourceOrRecordPropertySymbol; var propertyIsStatic = propertySymbol.IsStatic; return (object)sourceProperty != null && diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 4af53ad5a846a..fa51098ddc2db 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5942,4 +5942,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Records must have both a 'data' modifier and parameter list + + There cannot be a primary constructor and a member constructor with the same parameter types. + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index ced7fa0cfa207..9bbca3c962a5d 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -1741,6 +1741,16 @@ private static BoundBlock BindMethodBody(MethodSymbol method, TypeCompilationSta return null; } } + else if (method is SynthesizedInstanceConstructor ctor) + { + // Synthesized instance constructors may partially synthesize + // their body + var node = ctor.GetNonNullSyntaxNode(); + var factory = new SyntheticBoundNodeFactory(ctor, node, compilationState, diagnostics); + var stmts = ArrayBuilder.GetInstance(); + ctor.GenerateMethodBodyStatements(factory, stmts, diagnostics); + body = BoundBlock.SynthesizedNoLocals(node, stmts.ToImmutableAndFree()); + } else { // synthesized methods should return their bound bodies diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 3d438c78013df..282e70cca400d 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1743,6 +1743,7 @@ internal enum ErrorCode ERR_AmbigBinaryOpsOnUnconstrainedDefault = 8761, ERR_BadRecordDeclaration = 8770, + ERR_DuplicateRecordConstructor = 8771, // Note: you will need to re-generate compiler code after adding warnings (eng\generate-compiler-code.cmd) } diff --git a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs index 9b2f590686289..57fdf4792e486 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LambdaRewriter/LambdaRewriter.cs @@ -346,7 +346,7 @@ private void SynthesizeClosureEnvironments(ArrayBuilder closur AddSynthesizedMethod( frame.Constructor, FlowAnalysisPass.AppendImplicitReturn( - MethodCompiler.BindMethodBody(frame.Constructor, CompilationState, null), + MethodCompiler.BindMethodBody(frame.Constructor, CompilationState, Diagnostics), frame.Constructor)); } @@ -539,7 +539,7 @@ private SynthesizedClosureEnvironment GetStaticFrame(DiagnosticBag diagnostics, AddSynthesizedMethod( frame.Constructor, FlowAnalysisPass.AppendImplicitReturn( - MethodCompiler.BindMethodBody(frame.Constructor, CompilationState, null), + MethodCompiler.BindMethodBody(frame.Constructor, CompilationState, diagnostics), frame.Constructor)); // add cctor diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs index 08b2f545c98e8..341029989ccec 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs @@ -277,6 +277,8 @@ private BoundExpression MakeStaticAssignmentOperator( } } +#nullable enable + private BoundExpression MakePropertyAssignment( SyntaxNode syntax, BoundExpression rewrittenReceiver, @@ -292,12 +294,13 @@ private BoundExpression MakePropertyAssignment( // Rewrite property assignment into call to setter. var setMethod = property.GetOwnOrInheritedSetMethod(); - if ((object)setMethod == null) + if (setMethod is null) { - Debug.Assert((property as SourcePropertySymbol)?.IsAutoProperty == true, + var autoProp = (SourceOrRecordPropertySymbol)property; + Debug.Assert(autoProp.IsAutoProperty, "only autoproperties can be assignable without having setters"); - var backingField = (property as SourcePropertySymbol).BackingField; + var backingField = autoProp.BackingField; return _factory.AssignmentExpression( _factory.Field(rewrittenReceiver, backingField), rewrittenRight); @@ -323,7 +326,7 @@ private BoundExpression MakePropertyAssignment( // Save expression value to a temporary before calling the // setter, and restore the temporary after the setter, so the // assignment can be used as an embedded expression. - TypeSymbol exprType = rewrittenRight.Type; + TypeSymbol? exprType = rewrittenRight.Type; LocalSymbol rhsTemp = _factory.SynthesizedLocal(exprType); diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs index 9d786915d71cc..ec6638f89e67d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs @@ -446,8 +446,11 @@ public int GetHashCode(Symbol member) // CONSIDER: modify hash for constraints? - hash = Hash.Combine(member.GetMemberArity(), hash); - hash = Hash.Combine(member.GetParameterCount(), hash); + if (member.Kind != SymbolKind.Field) + { + hash = Hash.Combine(member.GetMemberArity(), hash); + hash = Hash.Combine(member.GetParameterCount(), hash); + } } return hash; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index c0946146c9551..04ca163051751 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -2323,6 +2323,7 @@ internal void AddOrWrapTupleMembers(SourceMemberContainerTypeSymbol type) case TypeKind.Struct: CheckForStructBadInitializers(builder, diagnostics); CheckForStructDefaultConstructors(builder.NonTypeNonIndexerMembers, isEnum: false, diagnostics: diagnostics); + AddSynthesizedRecordMembersIfNecessary(builder, diagnostics); AddSynthesizedConstructorsIfNecessary(builder.NonTypeNonIndexerMembers, builder.StaticInitializers, diagnostics); break; @@ -2334,6 +2335,7 @@ internal void AddOrWrapTupleMembers(SourceMemberContainerTypeSymbol type) case TypeKind.Class: case TypeKind.Interface: case TypeKind.Submission: + AddSynthesizedRecordMembersIfNecessary(builder, diagnostics); // No additional checking required. AddSynthesizedConstructorsIfNecessary(builder.NonTypeNonIndexerMembers, builder.StaticInitializers, diagnostics); break; @@ -2846,9 +2848,19 @@ private void CheckForStructBadInitializers(MembersAndInitializersBuilder builder } } - private void AddSynthesizedRecordMembersIfNecessary(ArrayBuilder members, DiagnosticBag diagnostics) + private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilder builder, DiagnosticBag diagnostics) { - Debug.Assert(declaration.Kind == DeclarationKind.Class || declaration.Kind == DeclarationKind.Struct); + switch (declaration.Kind) + { + case DeclarationKind.Class: + case DeclarationKind.Struct: + break; + + default: + return; + } + + var members = builder.NonTypeNonIndexerMembers; ParameterListSyntax? paramList = null; foreach (SingleTypeDeclaration decl in declaration.Declarations) @@ -2901,6 +2913,21 @@ private void AddSynthesizedRecordMembersIfNecessary(ArrayBuilder members { members.Add(ctor); } + else + { + diagnostics.Add(ErrorCode.ERR_DuplicateRecordConstructor, paramList.Location); + } + + foreach (ParameterSymbol param in ctor.Parameters) + { + var property = new SynthesizedRecordPropertySymbol(this, param); + if (!memberSignatures.Contains(property)) + { + members.Add(property); + members.Add(property.GetMethod); + members.Add(property.BackingField); + } + } } private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder members, ArrayBuilder> staticInitializers, DiagnosticBag diagnostics) @@ -2909,7 +2936,6 @@ private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder members, { case DeclarationKind.Class: case DeclarationKind.Struct: - AddSynthesizedRecordMembersIfNecessary(members, diagnostics); break; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs index c8766eda8c632..06fa8e5e100be 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs @@ -555,9 +555,9 @@ private void CheckMembersAgainstBaseType( } } } - else + else if (property is SourcePropertySymbol sourceProperty) { - var isNewProperty = ((SourcePropertySymbol)property).IsNew; + var isNewProperty = sourceProperty.IsNew; CheckNonOverrideMember(property, isNewProperty, property.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); if (!suppressAccessors) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrRecordPropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrRecordPropertySymbol.cs new file mode 100644 index 0000000000000..da5440291d09f --- /dev/null +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrRecordPropertySymbol.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + internal abstract class SourceOrRecordPropertySymbol : PropertySymbol, IAttributeTargetSymbol + { + public Location Location { get; } + + public SourceOrRecordPropertySymbol(Location location) + { + Location = location; + } + + internal abstract SynthesizedBackingFieldSymbol BackingField { get; } + + internal abstract bool IsAutoProperty { get; } + + internal abstract bool HasPointerType { get; } + + public abstract SyntaxList AttributeDeclarationSyntaxList { get; } + + protected abstract IAttributeTargetSymbol AttributesOwner { get; } + + protected abstract AttributeLocation AllowedAttributeLocations { get; } + + protected abstract AttributeLocation DefaultAttributeLocation { get; } + + IAttributeTargetSymbol IAttributeTargetSymbol.AttributesOwner => AttributesOwner; + + AttributeLocation IAttributeTargetSymbol.AllowedAttributeLocations => AllowedAttributeLocations; + + AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation => DefaultAttributeLocation; + } +} diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index 42b82d25e51cc..94bbb570e3bf0 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { - internal sealed class SourcePropertySymbol : PropertySymbol, IAttributeTargetSymbol + internal sealed class SourcePropertySymbol : SourceOrRecordPropertySymbol { /// /// Condensed flags storing useful information about the @@ -36,12 +36,10 @@ private enum Flags : byte private readonly SourceMemberContainerTypeSymbol _containingType; private readonly string _name; private readonly SyntaxReference _syntaxRef; - private readonly Location _location; private readonly DeclarationModifiers _modifiers; private readonly ImmutableArray _refCustomModifiers; private readonly SourcePropertyAccessorSymbol _getMethod; private readonly SourcePropertyAccessorSymbol _setMethod; - private readonly SynthesizedBackingFieldSymbol _backingField; private readonly TypeSymbol _explicitInterfaceType; private readonly ImmutableArray _explicitInterfaceImplementations; private readonly Flags _propertyFlags; @@ -71,6 +69,7 @@ private SourcePropertySymbol( string name, Location location, DiagnosticBag diagnostics) + : base(location) { // This has the value that IsIndexer will ultimately have, once we've populated the fields of this object. bool isIndexer = syntax.Kind() == SyntaxKind.IndexerDeclaration; @@ -81,7 +80,6 @@ private SourcePropertySymbol( _propertyFlags |= Flags.IsExplicitInterfaceImplementation; } - _location = location; _containingType = containingType; _syntaxRef = syntax.GetReference(); _refKind = syntax.Type.GetRefKind(); @@ -192,7 +190,7 @@ private SourcePropertySymbol( } string fieldName = GeneratedNames.MakeBackingFieldName(_sourceName); - _backingField = new SynthesizedBackingFieldSymbol(this, + BackingField = new SynthesizedBackingFieldSymbol(this, fieldName, isGetterOnly, this.IsStatic, @@ -545,7 +543,7 @@ public override TypeWithAnnotations TypeWithAnnotations } } - internal bool HasPointerType + internal override bool HasPointerType { get { @@ -602,22 +600,14 @@ public override NamedTypeSymbol ContainingType internal override LexicalSortKey GetLexicalSortKey() { - return new LexicalSortKey(_location, this.DeclaringCompilation); + return new LexicalSortKey(Location, this.DeclaringCompilation); } public override ImmutableArray Locations { get { - return ImmutableArray.Create(_location); - } - } - - internal Location Location - { - get - { - return _location; + return ImmutableArray.Create(Location); } } @@ -749,17 +739,14 @@ public bool HasSkipLocalsInitAttribute } } - internal bool IsAutoProperty + internal override bool IsAutoProperty => (_propertyFlags & Flags.IsAutoProperty) != 0; /// /// Backing field for automatically implemented property, or /// for a property with an initializer. /// - internal SynthesizedBackingFieldSymbol BackingField - { - get { return _backingField; } - } + internal override SynthesizedBackingFieldSymbol BackingField { get; } internal override bool MustCallMethodsDirectly { @@ -782,6 +769,9 @@ internal BasePropertyDeclarationSyntax CSharpSyntaxNode } } + public override SyntaxList AttributeDeclarationSyntaxList + => CSharpSyntaxNode.AttributeLists; + internal SyntaxTree SyntaxTree { get @@ -1153,17 +1143,11 @@ private SynthesizedSealedPropertyAccessor MakeSynthesizedSealedAccessor() #region Attributes - IAttributeTargetSymbol IAttributeTargetSymbol.AttributesOwner - { - get { return this; } - } + protected override IAttributeTargetSymbol AttributesOwner => this; - AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation - { - get { return AttributeLocation.Property; } - } + protected override AttributeLocation DefaultAttributeLocation => AttributeLocation.Property; - AttributeLocation IAttributeTargetSymbol.AllowedAttributeLocations + protected override AttributeLocation AllowedAttributeLocations => (_propertyFlags & Flags.IsAutoProperty) != 0 ? AttributeLocation.Property | AttributeLocation.Field : AttributeLocation.Property; @@ -1183,7 +1167,7 @@ private CustomAttributesBag GetAttributesBag() } // The property is responsible for completion of the backing field - _ = _backingField?.GetAttributes(); + _ = BackingField?.GetAttributes(); if (LoadAndValidateAttributes(OneOrMany.Create(this.CSharpSyntaxNode.AttributeLists), ref _lazyCustomAttributesBag)) { @@ -1562,7 +1546,7 @@ internal override void ForceComplete(SourceLocation locationOpt, CancellationTok { var diagnostics = DiagnosticBag.GetInstance(); var conversions = new TypeConversions(this.ContainingAssembly.CorLibrary); - this.Type.CheckAllConstraints(DeclaringCompilation, conversions, _location, diagnostics); + this.Type.CheckAllConstraints(DeclaringCompilation, conversions, Location, diagnostics); var type = this.Type; if (type.IsRestrictedType(ignoreSpanLikeTypes: true)) @@ -1613,15 +1597,15 @@ private TypeWithAnnotations ComputeType(Binder binder, BasePropertyDeclarationSy { // "Inconsistent accessibility: indexer return type '{1}' is less accessible than indexer '{0}'" // "Inconsistent accessibility: property type '{1}' is less accessible than property '{0}'" - diagnostics.Add((this.IsIndexer ? ErrorCode.ERR_BadVisIndexerReturn : ErrorCode.ERR_BadVisPropertyType), _location, this, type.Type); + diagnostics.Add((this.IsIndexer ? ErrorCode.ERR_BadVisIndexerReturn : ErrorCode.ERR_BadVisPropertyType), Location, this, type.Type); } - diagnostics.Add(_location, useSiteDiagnostics); + diagnostics.Add(Location, useSiteDiagnostics); if (type.IsVoidType()) { ErrorCode errorCode = this.IsIndexer ? ErrorCode.ERR_IndexerCantHaveVoidType : ErrorCode.ERR_PropertyCantHaveVoidType; - diagnostics.Add(errorCode, _location, this); + diagnostics.Add(errorCode, Location, this); } return type; @@ -1637,15 +1621,15 @@ private ImmutableArray ComputeParameters(Binder binder, BasePro { if (syntax.ExplicitInterfaceSpecifier == null && !this.IsNoMoreVisibleThan(param.Type, ref useSiteDiagnostics)) { - diagnostics.Add(ErrorCode.ERR_BadVisIndexerParam, _location, this, param.Type); + diagnostics.Add(ErrorCode.ERR_BadVisIndexerParam, Location, this, param.Type); } else if ((object)_setMethod != null && param.Name == ParameterSymbol.ValueParameterName) { - diagnostics.Add(ErrorCode.ERR_DuplicateGeneratedName, param.Locations.FirstOrDefault() ?? _location, param.Name); + diagnostics.Add(ErrorCode.ERR_DuplicateGeneratedName, param.Locations.FirstOrDefault() ?? Location, param.Name); } } - diagnostics.Add(_location, useSiteDiagnostics); + diagnostics.Add(Location, useSiteDiagnostics); return parameters; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs index e1f05fbcda5ad..0451b39332c17 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs @@ -14,13 +14,13 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols /// internal sealed class SynthesizedBackingFieldSymbol : FieldSymbolWithAttributesAndModifiers { - private readonly SourcePropertySymbol _property; + private readonly SourceOrRecordPropertySymbol _property; private readonly string _name; internal bool HasInitializer { get; } protected override DeclarationModifiers Modifiers { get; } public SynthesizedBackingFieldSymbol( - SourcePropertySymbol property, + SourceOrRecordPropertySymbol property, string name, bool isReadOnly, bool isStatic, @@ -45,7 +45,7 @@ internal override Location ErrorLocation => _property.Location; protected override SyntaxList AttributeDeclarationSyntaxList - => _property.CSharpSyntaxNode.AttributeLists; + => _property.AttributeDeclarationSyntaxList; public override Symbol AssociatedSymbol => _property; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullableAttributeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullableAttributeSymbol.cs index b03bd3bb15b03..2a4580571a19e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullableAttributeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedNullableAttributeSymbol.cs @@ -126,7 +126,7 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, GenerateMethodBodyCore(compilationState, diagnostics); } - protected override void GenerateMethodBodyStatements(SyntheticBoundNodeFactory factory, ArrayBuilder statements, DiagnosticBag diagnostics) => _getConstructorBody(factory, statements, _parameters); + internal override void GenerateMethodBodyStatements(SyntheticBoundNodeFactory factory, ArrayBuilder statements, DiagnosticBag diagnostics) => _getConstructorBody(factory, statements, _parameters); } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs index a585d72781c6e..4992c3868ed9b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs @@ -301,7 +301,7 @@ protected void GenerateMethodBodyCore(TypeCompilationState compilationState, Dia factory.CloseMethod(block); } - protected virtual void GenerateMethodBodyStatements(SyntheticBoundNodeFactory factory, ArrayBuilder statements, DiagnosticBag diagnostics) + internal virtual void GenerateMethodBodyStatements(SyntheticBoundNodeFactory factory, ArrayBuilder statements, DiagnosticBag diagnostics) { // overridden in a derived class to add extra statements to the body of the generated constructor } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordConstructor.cs index ce2643d655eb1..d271b2e9ccce1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordConstructor.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -12,7 +13,7 @@ internal sealed class SynthesizedRecordConstructor : SynthesizedInstanceConstruc public override ImmutableArray Parameters { get; } public SynthesizedRecordConstructor( - NamedTypeSymbol containingType, + SourceMemberContainerTypeSymbol containingType, Binder parameterBinder, ParameterListSyntax parameterList, DiagnosticBag diagnostics) @@ -28,5 +29,26 @@ public SynthesizedRecordConstructor( allowThis: false, addRefReadOnlyModifier: false); } + + internal override void GenerateMethodBodyStatements(SyntheticBoundNodeFactory F, ArrayBuilder statements, DiagnosticBag diagnostics) + { + // Write assignments to backing fields + // + // { + // this.backingField1 = arg1 + // ... + // this.backingFieldN = argN + // } + var containing = (SourceMemberContainerTypeSymbol)ContainingType; + foreach (var param in Parameters) + { + var members = containing.GetMembers(param.Name); + if (members.Length == 1 && members[0] is SynthesizedRecordPropertySymbol prop) + { + var field = prop.BackingField; + statements.Add(F.Assignment(F.Field(F.This(), field), F.Parameter(param))); + } + } + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordPropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordPropertySymbol.cs new file mode 100644 index 0000000000000..141d49a885cb1 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordPropertySymbol.cs @@ -0,0 +1,208 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection; +using System.Text; +using Microsoft.Cci; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + internal sealed class SynthesizedRecordPropertySymbol : SourceOrRecordPropertySymbol + { + private readonly ParameterSymbol _backingParameter; + internal override SynthesizedBackingFieldSymbol BackingField { get; } + public override MethodSymbol GetMethod { get; } + public override NamedTypeSymbol ContainingType { get; } + + public SynthesizedRecordPropertySymbol( + NamedTypeSymbol containingType, + ParameterSymbol backingParameter) + : base(backingParameter.Locations[0]) + { + ContainingType = containingType; + _backingParameter = backingParameter; + string name = backingParameter.Name; + BackingField = new SynthesizedBackingFieldSymbol( + this, + GeneratedNames.MakeBackingFieldName(name), + isReadOnly: true, + isStatic: false, + hasInitializer: backingParameter.HasExplicitDefaultValue); + GetMethod = new GetAccessorSymbol(this, name); + } + + internal override bool IsAutoProperty => true; + + public override RefKind RefKind => RefKind.None; + + public override TypeWithAnnotations TypeWithAnnotations => _backingParameter.TypeWithAnnotations; + + public override ImmutableArray RefCustomModifiers => ImmutableArray.Empty; + + public override ImmutableArray Parameters => ImmutableArray.Empty; + + public override bool IsIndexer => false; + + public override MethodSymbol? SetMethod => null; + + public override ImmutableArray ExplicitInterfaceImplementations => ImmutableArray.Empty; + + public override Symbol ContainingSymbol => ContainingType; + + public override ImmutableArray Locations => _backingParameter.Locations; + + public override ImmutableArray DeclaringSyntaxReferences => _backingParameter.DeclaringSyntaxReferences; + + public override Accessibility DeclaredAccessibility => Accessibility.Public; + + public override bool IsStatic => false; + + public override bool IsVirtual => false; + + public override bool IsOverride => false; + + public override bool IsAbstract => false; + + public override bool IsSealed => false; + + public override bool IsExtern => false; + + internal override bool HasSpecialName => false; + + internal override CallingConvention CallingConvention => CallingConvention.HasThis; + + internal override bool MustCallMethodsDirectly => false; + + internal override ObsoleteAttributeData? ObsoleteAttributeData => null; + + public override string Name => _backingParameter.Name; + + protected override IAttributeTargetSymbol AttributesOwner => this; + + protected override AttributeLocation AllowedAttributeLocations => AttributeLocation.None; + + protected override AttributeLocation DefaultAttributeLocation => AttributeLocation.None; + + public override ImmutableArray GetAttributes() => ImmutableArray.Empty; + + internal override bool HasPointerType => Type.IsPointerType(); + + public override SyntaxList AttributeDeclarationSyntaxList => new SyntaxList(); + + private sealed class GetAccessorSymbol : SynthesizedInstanceMethodSymbol + { + private readonly SynthesizedRecordPropertySymbol _property; + + public override string Name { get; } + + public GetAccessorSymbol(SynthesizedRecordPropertySymbol property, string paramName) + { + _property = property; + Name = SourcePropertyAccessorSymbol.GetAccessorName( + paramName, + getNotSet: true, + isWinMdOutput: false /* unused for getters */); + } + + public override MethodKind MethodKind => MethodKind.PropertyGet; + + 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 TypeWithAnnotations ReturnTypeWithAnnotations => _property.TypeWithAnnotations; + + public override FlowAnalysisAnnotations ReturnTypeFlowAnalysisAnnotations => FlowAnalysisAnnotations.None; + + public override ImmutableArray TypeArgumentsWithAnnotations => ImmutableArray.Empty; + + public override ImmutableArray TypeParameters => ImmutableArray.Empty; + + public override ImmutableArray Parameters => _property.Parameters; + + 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: + // + // { + // return this.<>backingField; + // } + + var F = new SyntheticBoundNodeFactory(this, this.GetNonNullSyntaxNode(), compilationState, diagnostics); + + F.CurrentFunction = this; + F.CloseMethod(F.Block(F.Return(F.Field(F.This(), _property.BackingField)))); + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 32a355cf523c5..2d174390d4f46 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -152,6 +152,11 @@ Pro přístupové objekty vlastnosti i indexeru {0} nelze zadat modifikátory readonly. Místo toho zadejte modifikátor readonly jenom pro vlastnost. + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. Příkaz nemůže začínat na else. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 5c38de835bf45..059b03296f619 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -152,6 +152,11 @@ readonly-Modifizierer können nicht für beide Accessoren der Eigenschaft oder des Indexers "{0}" angegeben werden. Legen Sie stattdessen einen readonly-Modifizierer für die Eigenschaft selbst fest. + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. Eine Anweisung kann nicht mit "else" beginnen. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 86ae17fd696f2..96f474d75835d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -152,6 +152,11 @@ No se pueden especificar modificadores "readonly" en ambos descriptores de acceso de la propiedad o del indizador "{0}". En su lugar, coloque un modificador "readonly" en la propiedad. + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. “else” no puede iniciar una instrucción. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index f0b21615ba4de..554701573a9d9 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -152,6 +152,11 @@ Impossible de spécifier des modificateurs 'readonly' sur les deux accesseurs de la propriété ou de l'indexeur '{0}'. À la place, mettez un modificateur 'readonly' sur la propriété elle-même. + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. 'else' ne peut pas démarrer d'instruction. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 64d75dcc07de0..b1c4235e28d0d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -152,6 +152,11 @@ Non è possibile specificare i modificatori 'readonly' in entrambe le funzioni di accesso della proprietà o dell'indicizzatore '{0}'. Inserire invece un modificatore 'readonly' nella proprietà stessa. + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. Un'istruzione non può iniziare con 'else'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 50a29b0aa7061..2a7b975d1a58a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -152,6 +152,11 @@ プロパティまたはインデクサー '{0}' の両方のアクセサーで 'readonly' 修飾子を指定することはできません。代わりに、プロパティ自体に 'readonly' 修飾子を指定してください。 + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. 'else' でステートメントを開始することはできません。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 596da44396626..8871a1f24ec37 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -152,6 +152,11 @@ '{0}' 속성 또는 인덱서의 두 접근자에 'readonly' 한정자를 지정할 수 없습니다. 대신 속성 자체에 'readonly' 한정자를 지정하세요. + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. 'else'로 문을 시작할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 9ca29e308cad4..453b2b4cad75a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -152,6 +152,11 @@ Nie można określić modyfikatorów „readonly” dla obu metod dostępu właściwości lub indeksatora „{0}”. Zamiast tego dodaj modyfikator „readonly” do samej właściwości. + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. Instrukcja nie może rozpoczynać się od elementu „else”. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index e823f50cd8abf..690bdf5c4145b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -152,6 +152,11 @@ Não é possível especificar modificadores 'readonly' em ambos os acessadores de propriedade ou de indexador '{0}'. Nesse caso, coloque um modificador 'readonly' na própria propriedade. + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. 'else' não pode iniciar uma instrução. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 6d1d5508d9e9d..406357ae91402 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -152,6 +152,11 @@ Запрещено указывать модификаторы readonly для обоих методов доступа свойства или индексатора "{0}". Вместо этого укажите модификатор readonly для самого свойства. + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. "else" не может запускать оператор. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index ce79ded7e9302..bd3233b105908 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -152,6 +152,11 @@ 'readonly' değiştiricileri, '{0}' özelliğinin veya dizin oluşturucusunun her iki erişimcisinde de belirtilemez. Bunun yerine özelliğin kendisine bir 'readonly' değiştiricisi koyun. + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. 'else' bir deyim başlatamaz. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index f86538360bee7..ad754953dc9e5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -152,6 +152,11 @@ 不能在属性或索引器 "{0}" 的两个访问器上指定 "readonly" 修饰符。而应在属性本身上指定 "readonly" 修饰符。 + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. "else" 不能用在语句的开头。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 01ead305f4923..cfdfd26784adc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -152,6 +152,11 @@ 在屬性和索引子 '{0}' 的存取子上均無法指定 'readonly' 修飾詞。請改在屬性自身上放置 'readonly' 修飾詞。 + + There cannot be a primary constructor and a member constructor with the same parameter types. + There cannot be a primary constructor and a member constructor with the same parameter types. + + 'else' cannot start a statement. 'else' 無法開始陳述式。 diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs index a7114c5a86dd9..2ca27eed87e60 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs @@ -49,6 +49,8 @@ public void PositionalRecord2() Add( // C Type parameters "T"), Add( // Members + "System.Int32 C.x { get; }", + "T C.t { get; }", "System.Boolean System.Object.Equals(System.Object obj)", "System.Boolean System.Object.Equals(System.Object objA, System.Object objB)", "System.Boolean System.Object.ReferenceEquals(System.Object objA, System.Object objB)", diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index 4966e4aa03e61..f448670a31d96 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -8,6 +8,12 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics { public class RecordTests : CompilingTestBase { + private static CSharpCompilation CreateCompilation(CSharpTestSource source) + => CSharpTestBase.CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + private CompilationVerifier CompileAndVerify(CSharpTestSource src, string expectedOutput) + => base.CompileAndVerify(src, expectedOutput: expectedOutput, parseOptions: TestOptions.RegularPreview); + [Fact] public void RecordLanguageVersion() { @@ -20,7 +26,7 @@ data class Point { } var src3 = @" data class Point(int x, int y); "; - var comp = CreateCompilation(src1); + var comp = CreateCompilation(src1, parseOptions: TestOptions.Regular8); comp.VerifyDiagnostics( // (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); @@ -32,7 +38,7 @@ data class Point { } // class Point(int x, int y); Diagnostic(ErrorCode.ERR_FeatureInPreview, ";").WithArguments("records").WithLocation(2, 26) ); - comp = CreateCompilation(src2); + comp = CreateCompilation(src2, parseOptions: TestOptions.Regular8); comp.VerifyDiagnostics( // (2,1): error CS8652: The feature 'records' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // data class Point { } @@ -41,7 +47,7 @@ data class Point { } // data class Point { } Diagnostic(ErrorCode.ERR_BadRecordDeclaration, "Point").WithLocation(2, 12) ); - comp = CreateCompilation(src3); + comp = CreateCompilation(src3, parseOptions: TestOptions.Regular8); comp.VerifyDiagnostics( // (2,1): 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); @@ -54,20 +60,177 @@ data class Point { } Diagnostic(ErrorCode.ERR_FeatureInPreview, ";").WithArguments("records").WithLocation(2, 31) ); - comp = CreateCompilation(src1, parseOptions: TestOptions.RegularPreview); + comp = CreateCompilation(src1); comp.VerifyDiagnostics( // (2,12): error CS8761: Records must have both a 'data' modifier and parameter list // class Point(int x, int y); Diagnostic(ErrorCode.ERR_BadRecordDeclaration, "(int x, int y)").WithLocation(2, 12) ); - comp = CreateCompilation(src2, parseOptions: TestOptions.RegularPreview); + comp = CreateCompilation(src2); comp.VerifyDiagnostics( // (2,12): error CS8761: Records must have both a 'data' modifier and parameter list // data class Point { } Diagnostic(ErrorCode.ERR_BadRecordDeclaration, "Point").WithLocation(2, 12) ); - comp = CreateCompilation(src3, parseOptions: TestOptions.RegularPreview); + comp = CreateCompilation(src3); comp.VerifyDiagnostics(); } + + [Fact] + public void RecordProperties_01() + { + var src = @" +using System; +data class C(int X, int Y) +{ + public static void Main() + { + var c = new C(1, 2); + Console.WriteLine(c.X); + Console.WriteLine(c.Y); + } +}"; + CompileAndVerify(src, expectedOutput: @" +1 +2"); + } + + [Fact] + public void RecordProperties_02() + { + var src = @" +using System; +data class C(int X, int Y) +{ + public C(int a, int b) + { + } + + public static void Main() + { + var c = new C(1, 2); + Console.WriteLine(c.X); + Console.WriteLine(c.Y); + } +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (3,13): error CS8762: There cannot be a primary constructor and a member constructor with the same parameter types. + // data class C(int X, int Y) + Diagnostic(ErrorCode.ERR_DuplicateRecordConstructor, "(int X, int Y)").WithLocation(3, 13) + ); + } + + [Fact] + public void RecordProperties_03() + { + var src = @" +using System; +data class C(int X, int Y) +{ + public int X { get; } + + public static void Main() + { + var c = new C(1, 2); + Console.WriteLine(c.X); + Console.WriteLine(c.Y); + } +}"; + CompileAndVerify(src, expectedOutput: @" +0 +2"); + } + + [Fact] + public void RecordProperties_04() + { + var src = @" +using System; +data class C(int X, int Y) +{ + public int X { get; } = 3; + + public static void Main() + { + var c = new C(1, 2); + Console.WriteLine(c.X); + Console.WriteLine(c.Y); + } +}"; + CompileAndVerify(src, expectedOutput: @" +3 +2"); + } + + [Fact] + public void RecordProperties_05() + { + var src = @" +data class C(int X, int X) +{ +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,25): error CS0100: The parameter name 'X' is a duplicate + // data class C(int X, int X) + Diagnostic(ErrorCode.ERR_DuplicateParamName, "X").WithArguments("X").WithLocation(2, 25), + // (2,25): error CS0102: The type 'C' already contains a definition for 'X' + // data class C(int X, int X) + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "X").WithArguments("C", "X").WithLocation(2, 25) + ); + } + + [Fact] + public void RecordProperties_06() + { + var src = @" +data class C(int X) +{ + public void get_X() {} +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (2,18): error CS0082: Type 'C' already reserves a member called 'get_X' with the same parameter types + // data class C(int X) + Diagnostic(ErrorCode.ERR_MemberReserved, "X").WithArguments("get_X", "C").WithLocation(2, 18) + ); + } + + [Fact] + public void RecordProperties_07() + { + var comp = CreateCompilation(@" +data class C1(object P, object get_P); +data class C2(object get_P, object P);"); + comp.VerifyDiagnostics( + // (2,22): error CS0102: The type 'C1' already contains a definition for 'get_P' + // data class C1(object P, object get_P); + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C1", "get_P").WithLocation(2, 22), + // (3,36): error CS0102: The type 'C2' already contains a definition for 'get_P' + // data class C2(object get_P, object P); + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C2", "get_P").WithLocation(3, 36) + ); + } + + [Fact] + public void RecordProperties_08() + { + var comp = CreateCompilation(@" +data class C1(object O1) +{ + public object O1 { get; } = O1; + public object O2 { get; } = O1; +}"); + // PROTOTYPE: primary ctor parameters not currently in scope + comp.VerifyDiagnostics( + // (4,33): error CS0236: A field initializer cannot reference the non-static field, method, or property 'C1.O1' + // public object O1 { get; } = O1; + Diagnostic(ErrorCode.ERR_FieldInitRefNonstatic, "O1").WithArguments("C1.O1").WithLocation(4, 33), + // (5,33): error CS0236: A field initializer cannot reference the non-static field, method, or property 'C1.O1' + // public object O2 { get; } = O1; + Diagnostic(ErrorCode.ERR_FieldInitRefNonstatic, "O1").WithArguments("C1.O1").WithLocation(5, 33) + ); + } } } \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs index f1e0644b01bff..a7d758566ed83 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs @@ -60,7 +60,11 @@ public C(int a, string b) { } }"); - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (2,13): error CS8762: There cannot be a primary constructor and a member constructor with the same parameter types. + // data class C(int x, string y) + Diagnostic(ErrorCode.ERR_DuplicateRecordConstructor, "(int x, string y)").WithLocation(2, 13) + ); var c = comp.GlobalNamespace.GetTypeMember("C"); var ctor = c.GetMethod(".ctor"); Assert.Equal(2, ctor.ParameterCount); @@ -110,5 +114,55 @@ public C(int a, int b) // overload } } } + + [Fact] + public void GeneratedProperties() + { + var comp = CreateCompilation("data class C(int x, int y);"); + comp.VerifyDiagnostics(); + var c = comp.GlobalNamespace.GetTypeMember("C"); + + var x = (SourceOrRecordPropertySymbol)c.GetProperty("x"); + Assert.NotNull(x.GetMethod); + Assert.Equal(MethodKind.PropertyGet, x.GetMethod.MethodKind); + Assert.Equal(SpecialType.System_Int32, x.Type.SpecialType); + Assert.True(x.IsReadOnly); + Assert.Equal(Accessibility.Public, x.DeclaredAccessibility); + Assert.False(x.IsVirtual); + Assert.False(x.IsStatic); + Assert.Equal(c, x.ContainingType); + Assert.Equal(c, x.ContainingSymbol); + + var backing = x.BackingField; + Assert.Equal(x, backing.AssociatedSymbol); + Assert.Equal(c, backing.ContainingSymbol); + Assert.Equal(c, backing.ContainingType); + + var getAccessor = x.GetMethod; + Assert.Equal(x, getAccessor.AssociatedSymbol); + Assert.Equal(c, getAccessor.ContainingSymbol); + Assert.Equal(c, getAccessor.ContainingType); + + 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.Equal(Accessibility.Public, y.DeclaredAccessibility); + Assert.False(x.IsVirtual); + Assert.False(x.IsStatic); + Assert.Equal(c, y.ContainingType); + Assert.Equal(c, y.ContainingSymbol); + + backing = y.BackingField; + Assert.Equal(y, backing.AssociatedSymbol); + Assert.Equal(c, backing.ContainingSymbol); + Assert.Equal(c, backing.ContainingType); + + getAccessor = y.GetMethod; + Assert.Equal(y, getAccessor.AssociatedSymbol); + Assert.Equal(c, getAccessor.ContainingSymbol); + Assert.Equal(c, getAccessor.ContainingType); + } } }