From 08f6625abd011c0ce59477df2c457ef2b4432540 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 27 Dec 2019 15:49:49 -0500 Subject: [PATCH 1/8] Generate ctor from the record syntax --- .../BinderFactory.BinderFactoryVisitor.cs | 10 +- .../Binder/BinderFactory.NodeUsage.cs | 2 +- .../Source/SourceMemberContainerSymbol.cs | 135 +++++++++++++----- .../SynthesizedRecordConstructor.cs | 30 ++++ .../Symbols/Tuples/TupleTypeSymbol.cs | 2 +- .../CSharp/Portable/Syntax/LookupPosition.cs | 3 + .../Semantic/Semantics/LookupPositionTests.cs | 41 ++++++ .../Test/Symbol/Symbols/Source/RecordTests.cs | 51 +++++++ 8 files changed, 229 insertions(+), 45 deletions(-) create mode 100644 src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordConstructor.cs create mode 100644 src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs index d5d233efdab75..013dac0d9e977 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs @@ -643,8 +643,10 @@ private Binder VisitTypeDeclarationCore(TypeDeclarationSyntax parent) // we are visiting type declarations fairly frequently // and position is more likely to be in the body, so lets check for "inBody" first. - if (LookupPosition.IsBetweenTokens(_position, parent.OpenBraceToken, parent.CloseBraceToken) || - LookupPosition.IsInAttributeSpecification(_position, parent.AttributeLists)) + if (parent.OpenBraceToken != default && + parent.CloseBraceToken != default && + (LookupPosition.IsBetweenTokens(_position, parent.OpenBraceToken, parent.CloseBraceToken) || + LookupPosition.IsInAttributeSpecification(_position, parent.AttributeLists))) { extraInfo = NodeUsage.NamedTypeBodyOrTypeParameters; } @@ -654,7 +656,7 @@ private Binder VisitTypeDeclarationCore(TypeDeclarationSyntax parent) } else if (LookupPosition.IsBetweenTokens(_position, parent.Keyword, parent.OpenBraceToken)) { - extraInfo = NodeUsage.NamedTypeBaseList; + extraInfo = NodeUsage.NamedTypeBaseListOrParameterList; } return VisitTypeDeclarationCore(parent, extraInfo); @@ -679,7 +681,7 @@ private Binder VisitTypeDeclarationCore(TypeDeclarationSyntax parent, NodeUsage { var typeSymbol = ((NamespaceOrTypeSymbol)resultBinder.ContainingMemberOrLambda).GetSourceTypeMember(parent); - if (extraInfo == NodeUsage.NamedTypeBaseList) + if (extraInfo == NodeUsage.NamedTypeBaseListOrParameterList) { // even though there could be no type parameter, we need this binder // for its "IsAccessible" diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.NodeUsage.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.NodeUsage.cs index 76e95b6555652..d865e4cfbc22e 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.NodeUsage.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.NodeUsage.cs @@ -15,7 +15,7 @@ internal enum NodeUsage : byte OperatorBody = 1 << 0, NamedTypeBodyOrTypeParameters = 1 << 0, - NamedTypeBaseList = 1 << 1, + NamedTypeBaseListOrParameterList = 1 << 1, NamespaceBody = 1 << 0, NamespaceUsings = 1 << 1, diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 1ff10da7114e6..989284e5eb360 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -1,5 +1,7 @@ // 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; @@ -147,12 +149,12 @@ public bool SetNullableContext(byte? value) private readonly NamespaceOrTypeSymbol _containingSymbol; protected readonly MergedTypeDeclaration declaration; - private MembersAndInitializers _lazyMembersAndInitializers; - private Dictionary> _lazyMembersDictionary; - private Dictionary> _lazyEarlyAttributeDecodingMembersDictionary; + private MembersAndInitializers? _lazyMembersAndInitializers; + private Dictionary>? _lazyMembersDictionary; + private Dictionary>? _lazyEarlyAttributeDecodingMembersDictionary; private static readonly Dictionary> s_emptyTypeMembers = new Dictionary>(EmptyComparer.Instance); - private Dictionary> _lazyTypeMembers; + private Dictionary>? _lazyTypeMembers; private ImmutableArray _lazyMembersFlattened; private ImmutableArray _lazySynthesizedExplicitImplementations; private int _lazyKnownCircularStruct; @@ -167,7 +169,7 @@ internal SourceMemberContainerTypeSymbol( NamespaceOrTypeSymbol containingSymbol, MergedTypeDeclaration declaration, DiagnosticBag diagnostics, - TupleExtraData tupleData = null) + TupleExtraData? tupleData = null) : base(tupleData) { _containingSymbol = containingSymbol; @@ -199,7 +201,7 @@ internal SourceMemberContainerTypeSymbol( _flags = new Flags(specialType, typeKind); var containingType = this.ContainingType; - if ((object)containingType != null && containingType.IsSealed && this.DeclaredAccessibility.HasProtected()) + if (containingType?.IsSealed == true && this.DeclaredAccessibility.HasProtected()) { diagnostics.Add(AccessCheck.GetProtectedMemberInSealedTypeError(ContainingType), Locations[0], this); } @@ -382,7 +384,7 @@ private DeclarationModifiers MakeAndCheckTypeModifiers( case SymbolKind.NamedType: for (var i = 1; i < partCount; i++) { - if (ContainingType.Locations.Length == 1 || ContainingType.IsPartial()) + if (ContainingType!.Locations.Length == 1 || ContainingType.IsPartial()) diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, declaration.Declarations[i].NameLocation, self.ContainingSymbol, self.Name); modifierErrors = true; } @@ -579,7 +581,8 @@ internal void EnsureFieldDefinitionsNoted() private void NoteFieldDefinitions() { // we must note all fields once therefore we need to lock - lock (this.GetMembersAndInitializers()) + var membersAndInitializers = this.GetMembersAndInitializers(); + lock (membersAndInitializers) { if (!_flags.FieldDefinitionsNoted) { @@ -587,7 +590,7 @@ private void NoteFieldDefinitions() Accessibility containerEffectiveAccessibility = EffectiveAccessibility(); - foreach (var member in _lazyMembersAndInitializers.NonTypeNonIndexerMembers) + foreach (var member in membersAndInitializers.NonTypeNonIndexerMembers) { FieldSymbol field; if (!member.IsFieldOrFieldLikeEvent(out field) || field.IsConst || field.IsFixedSizeBuffer) @@ -623,7 +626,7 @@ private void NoteFieldDefinitions() #region Containers - public sealed override NamedTypeSymbol ContainingType + public sealed override NamedTypeSymbol? ContainingType { get { @@ -720,7 +723,7 @@ private Accessibility EffectiveAccessibility() { var result = DeclaredAccessibility; if (result == Accessibility.Private) return Accessibility.Private; - for (Symbol container = this.ContainingType; (object)container != null; container = container.ContainingType) + for (Symbol? container = this.ContainingType; !(container is null); container = container.ContainingType) { switch (container.DeclaredAccessibility) { @@ -1213,7 +1216,7 @@ internal override IEnumerable GetFieldsToEmit() { // For consistency with Dev10, emit value__ field first. var valueField = ((SourceNamedTypeSymbol)this).EnumValueField; - Debug.Assert((object)valueField != null); + RoslynDebug.Assert((object)valueField != null); yield return valueField; } @@ -1303,14 +1306,14 @@ private MembersAndInitializers GetMembersAndInitializers() AddDeclarationDiagnostics(diagnostics); diagnostics.Free(); - return membersAndInitializers; + return _lazyMembersAndInitializers!; } protected Dictionary> GetMembersByName() { if (this.state.HasComplete(CompletionPart.Members)) { - return _lazyMembersDictionary; + return _lazyMembersDictionary!; } return GetMembersByNameSlow(); @@ -1528,7 +1531,7 @@ private void CheckMemberNameConflicts(DiagnosticBag diagnostics) var conversion = symbol as SourceUserDefinedConversionSymbol; var method = symbol as SourceMemberMethodSymbol; - if ((object)conversion != null) + if (!(conversion is null)) { // Does this conversion collide *as a conversion* with any previously-seen // conversion? @@ -1559,7 +1562,7 @@ private void CheckMemberNameConflicts(DiagnosticBag diagnostics) // Do not add the conversion to the set of previously-seen methods; that set // is only non-conversion methods. } - else if ((object)method != null) + else if (!(method is null)) { // Does this method collide *as a method* with any previously-seen // conversion? @@ -1636,7 +1639,7 @@ private void ReportMethodSignatureCollision(DiagnosticBag diagnostics, SourceMem private void CheckIndexerNameConflicts(DiagnosticBag diagnostics, Dictionary> membersByName) { - PooledHashSet typeParameterNames = null; + PooledHashSet? typeParameterNames = null; if (this.Arity > 0) { typeParameterNames = PooledHashSet.GetInstance(); @@ -1652,7 +1655,7 @@ private void CheckIndexerNameConflicts(DiagnosticBag diagnostics, Dictionary> membersByName, Dictionary indexersBySignature, - ref string lastIndexerName) + ref string? lastIndexerName) { if (!indexer.IsExplicitInterfaceImplementation) //explicit implementation names are not checked { @@ -2046,7 +2049,7 @@ private void CheckFiniteFlatteningGraph(DiagnosticBag diagnostics) foreach (var m in this.GetMembersUnordered()) { var f = m as FieldSymbol; - if ((object)f == null || !f.IsStatic || f.Type.TypeKind != TypeKind.Struct) continue; + if (f is null || !f.IsStatic || f.Type.TypeKind != TypeKind.Struct) continue; var type = (NamedTypeSymbol)f.Type; if (InfiniteFlatteningGraph(this, type, instanceMap)) { @@ -2076,7 +2079,7 @@ private static bool InfiniteFlatteningGraph(SourceMemberContainerTypeSymbol top, foreach (var m in t.GetMembersUnordered()) { var f = m as FieldSymbol; - if ((object)f == null || !f.IsStatic || f.Type.TypeKind != TypeKind.Struct) continue; + if (f is null || !f.IsStatic || f.Type.TypeKind != TypeKind.Struct) continue; var type = (NamedTypeSymbol)f.Type; if (InfiniteFlatteningGraph(top, type, instanceMap)) return true; } @@ -2096,7 +2099,7 @@ private void CheckSequentialOnPartialType(DiagnosticBag diagnostics) return; } - SyntaxReference whereFoundField = null; + SyntaxReference? whereFoundField = null; if (this.SyntaxReferences.Length <= 1) { return; @@ -2182,8 +2185,8 @@ private Dictionary> MakeAllMembers(DiagnosticBag // Add indexers (plus their accessors) var indexerMembers = ArrayBuilder.GetInstance(); - Binder binder = null; - SyntaxTree currentTree = null; + Binder? binder = null; + SyntaxTree? currentTree = null; foreach (var decl in membersAndInitializers.IndexerDeclarations) { var syntax = (IndexerDeclarationSyntax)decl.GetSyntax(); @@ -2310,7 +2313,7 @@ internal void AddOrWrapTupleMembers(SourceMemberContainerTypeSymbol type) } } - private MembersAndInitializers BuildMembersAndInitializers(DiagnosticBag diagnostics) + private MembersAndInitializers? BuildMembersAndInitializers(DiagnosticBag diagnostics) { var builder = new MembersAndInitializersBuilder(); AddDeclaredNontypeMembers(builder, diagnostics); @@ -2432,7 +2435,7 @@ private static void MergePartialMembers( foreach (var symbol in membersByName[name]) { var method = symbol as SourceMemberMethodSymbol; - if ((object)method == null || !method.IsPartial) + if (method is null || !method.IsPartial) { continue; // only partial methods need to be merged } @@ -2443,8 +2446,8 @@ private static void MergePartialMembers( var prevPart = (SourceOrdinaryMethodSymbol)prev; var methodPart = (SourceOrdinaryMethodSymbol)method; - bool hasImplementation = (object)prevPart.OtherPartOfPartial != null || prevPart.IsPartialImplementation; - bool hasDefinition = (object)prevPart.OtherPartOfPartial != null || prevPart.IsPartialDefinition; + bool hasImplementation = (object?)prevPart.OtherPartOfPartial != null || prevPart.IsPartialImplementation; + bool hasDefinition = (object?)prevPart.OtherPartOfPartial != null || prevPart.IsPartialDefinition; if (hasImplementation && methodPart.IsPartialImplementation) { @@ -2470,11 +2473,11 @@ private static void MergePartialMembers( foreach (SourceOrdinaryMethodSymbol method in methodsBySignature.Values) { // partial implementations not paired with a definition - if (method.IsPartialImplementation && (object)method.OtherPartOfPartial == null) + if (method.IsPartialImplementation && method.OtherPartOfPartial is null) { diagnostics.Add(ErrorCode.ERR_PartialMethodMustHaveLatent, method.Locations[0], method); } - else if ((object)method.OtherPartOfPartial != null && MemberSignatureComparer.ConsideringTupleNamesCreatesDifference(method, method.OtherPartOfPartial)) + else if (!(method.OtherPartOfPartial is null) && MemberSignatureComparer.ConsideringTupleNamesCreatesDifference(method, method.OtherPartOfPartial)) { diagnostics.Add(ErrorCode.ERR_PartialMethodInconsistentTupleNames, method.Locations[0], method, method.OtherPartOfPartial); } @@ -2674,7 +2677,7 @@ private void AddEnumMembers(MembersAndInitializersBuilder result, EnumDeclaratio // The previous enum constant used to calculate subsequent // implicit enum constants. (This is the most recent explicit // enum constant or the first implicit constant if no explicit values.) - SourceEnumConstantSymbol otherSymbol = null; + SourceEnumConstantSymbol? otherSymbol = null; // Offset from "otherSymbol". int otherSymbolOffset = 0; @@ -2695,7 +2698,7 @@ private void AddEnumMembers(MembersAndInitializersBuilder result, EnumDeclaratio result.NonTypeNonIndexerMembers.Add(symbol); - if (valueOpt != null || (object)otherSymbol == null) + if (valueOpt != null || otherSymbol is null) { otherSymbol = symbol; otherSymbolOffset = 1; @@ -2707,7 +2710,7 @@ private void AddEnumMembers(MembersAndInitializersBuilder result, EnumDeclaratio } } - private static void AddInitializer(ref ArrayBuilder initializers, ref int aggregateSyntaxLength, FieldSymbol fieldOpt, CSharpSyntaxNode node) + private static void AddInitializer(ref ArrayBuilder? initializers, ref int aggregateSyntaxLength, FieldSymbol? fieldOpt, CSharpSyntaxNode node) { if (initializers == null) { @@ -2734,7 +2737,9 @@ private static void AddInitializer(ref ArrayBuilder initializers.Add(new FieldOrPropertyInitializer(fieldOpt, node, currentLength)); } - private static void AddInitializers(ArrayBuilder> allInitializers, ArrayBuilder siblingsOpt) + private static void AddInitializers( + ArrayBuilder> allInitializers, + ArrayBuilder? siblingsOpt) { if (siblingsOpt != null) { @@ -2810,7 +2815,7 @@ private static void CheckForStructDefaultConstructors( foreach (var s in members) { var m = s as MethodSymbol; - if ((object)m != null) + if (!(m is null)) { if (m.MethodKind == MethodKind.Constructor && m.ParameterCount == 0) { @@ -2841,8 +2846,60 @@ private void CheckForStructBadInitializers(MembersAndInitializersBuilder builder } } + private void AddSynthesizedRecordMembersIfNecessary(ArrayBuilder members, DiagnosticBag diagnostics) + { + Debug.Assert(declaration.Kind == DeclarationKind.Class || declaration.Kind == DeclarationKind.Struct); + + ParameterListSyntax? paramList = null; + foreach (SingleTypeDeclaration decl in declaration.Declarations) + { + var syntaxNode = decl.SyntaxReference.GetSyntax(); + ParameterListSyntax? curParamList = null; + switch (declaration.Kind) + { + case DeclarationKind.Class: + var classDecl = (ClassDeclarationSyntax)syntaxNode; + curParamList = classDecl.ParameterList; + break; + case DeclarationKind.Struct: + var structDecl = (StructDeclarationSyntax)syntaxNode; + curParamList = structDecl.ParameterList; + break; + } + + if (paramList is null) + { + paramList = curParamList; + } + else + { + // PROTOTYPE: Add error for multiple parameter lists + } + } + + if (paramList is null) + { + return; + } + + BinderFactory binderFactory = this.DeclaringCompilation.GetBinderFactory(paramList.SyntaxTree); + var binder = binderFactory.GetBinder(paramList); + + // PROTOTYPE: This adds members unconditionally, but instead we should check if + // the members are already written in user code + members.Add(new SynthesizedRecordConstructor(this, binder, paramList, diagnostics)); + } + private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder members, ArrayBuilder> staticInitializers, DiagnosticBag diagnostics) { + switch (declaration.Kind) + { + case DeclarationKind.Class: + case DeclarationKind.Struct: + AddSynthesizedRecordMembersIfNecessary(members, diagnostics); + break; + } + //we're not calling the helpers on NamedTypeSymbol base, because those call //GetMembers and we're inside a GetMembers call ourselves (i.e. stack overflow) var hasInstanceConstructor = false; @@ -2924,8 +2981,8 @@ private void AddNonTypeMembers( var firstMember = members[0]; var bodyBinder = this.GetBinder(firstMember); - ArrayBuilder staticInitializers = null; - ArrayBuilder instanceInitializers = null; + ArrayBuilder? staticInitializers = null; + ArrayBuilder? instanceInitializers = null; foreach (var m in members) { @@ -3265,9 +3322,9 @@ private void AddNonTypeMembers( AddInitializers(builder.StaticInitializers, staticInitializers); } - private void AddAccessorIfAvailable(ArrayBuilder symbols, MethodSymbol accessorOpt, DiagnosticBag diagnostics, bool checkName = false) + private void AddAccessorIfAvailable(ArrayBuilder symbols, MethodSymbol? accessorOpt, DiagnosticBag diagnostics, bool checkName = false) { - if ((object)accessorOpt != null) + if (!(accessorOpt is null)) { symbols.Add(accessorOpt); if (checkName) diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordConstructor.cs new file mode 100644 index 0000000000000..7e4f601b622aa --- /dev/null +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordConstructor.cs @@ -0,0 +1,30 @@ +// 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 System.Collections.Immutable; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + internal sealed class SynthesizedRecordConstructor : SynthesizedInstanceConstructor + { + public override ImmutableArray Parameters { get; } + + public SynthesizedRecordConstructor( + NamedTypeSymbol containingType, + Binder parameterBinder, + ParameterListSyntax parameterList, + DiagnosticBag diagnostics) + : base(containingType) + { + Parameters = ParameterHelpers.MakeParameters( + parameterBinder, + this, + parameterList, + out _, + diagnostics, + allowRefOrOut: true, + allowThis: false, + addRefReadOnlyModifier: false); + } + } +} diff --git a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs index 95a80a52d8afe..6ad067f022f08 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs @@ -567,7 +567,7 @@ public sealed override ImmutableArray TupleElements return IsTupleType ? TupleData!.GetTupleMemberSymbolForUnderlyingMember(underlyingMemberOpt) : default; } - protected ArrayBuilder? AddOrWrapTupleMembers(ImmutableArray currentMembers) + protected ArrayBuilder AddOrWrapTupleMembers(ImmutableArray currentMembers) { Debug.Assert(IsTupleType); Debug.Assert(currentMembers.All(m => !(m is TupleVirtualElementFieldSymbol))); diff --git a/src/Compilers/CSharp/Portable/Syntax/LookupPosition.cs b/src/Compilers/CSharp/Portable/Syntax/LookupPosition.cs index a914cd171fd5d..f28b7ba5d88a6 100644 --- a/src/Compilers/CSharp/Portable/Syntax/LookupPosition.cs +++ b/src/Compilers/CSharp/Portable/Syntax/LookupPosition.cs @@ -118,6 +118,9 @@ internal static bool IsInParameterList(int position, BaseMethodDeclarationSyntax return IsBeforeToken(position, parameterList, parameterList.CloseParenToken); } + internal static bool IsInParameterList(int position, ParameterListSyntax parameterList) + => parameterList != null && IsBeforeToken(position, parameterList, parameterList.CloseParenToken); + internal static bool IsInMethodDeclaration(int position, BaseMethodDeclarationSyntax methodDecl) { Debug.Assert(methodDecl != null); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs index d41a1d0985f5e..a7114c5a86dd9 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs @@ -22,6 +22,47 @@ public class LookupPositionTests : CompilingTestBase { private const char KeyPositionMarker = '`'; + [Fact] + public void PositionalRecord1() + { + var text = @" +class C(int x, int y);"; + var expectedNames = MakeExpectedSymbols( + Add( // Global + "System", + "Microsoft", + "C")); + + TestLookupNames(text, expectedNames); + } + + [Fact] + public void PositionalRecord2() + { + var text = @" +`class C`(int x, T t = default(T));"; + var expectedNames = MakeExpectedSymbols( + Add( // Global + "System", + "Microsoft", + "C"), + Add( // C Type parameters + "T"), + Add( // Members + "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)", + "System.Int32 System.Object.GetHashCode()", + "System.Object System.Object.MemberwiseClone()", + "void System.Object.Finalize()", + "System.String System.Object.ToString()", + "System.Type System.Object.GetType()"), + s_pop + ); + + TestLookupNames(text, expectedNames); + } + [Fact] public void ExpressionBodiedProp() { diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs new file mode 100644 index 0000000000000..5148554e6f558 --- /dev/null +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs @@ -0,0 +1,51 @@ +// 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.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests +{ + public class RecordTests : CompilingTestBase + { + private static CSharpCompilation CreateCompilation(CSharpTestSource source) + => CSharpTestBase.CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + + [Fact] + public void GeneratedConstructor() + { + var comp = CreateCompilation(@"class C(int x, string y);"); + comp.VerifyDiagnostics(); + var c = comp.GlobalNamespace.GetTypeMember("C"); + var ctor = c.GetMethod(".ctor"); + Assert.Equal(2, ctor.ParameterCount); + + var x = ctor.Parameters[0]; + Assert.Equal(SpecialType.System_Int32, x.Type.SpecialType); + Assert.Equal("x", x.Name); + + var y = ctor.Parameters[1]; + Assert.Equal(SpecialType.System_String, y.Type.SpecialType); + Assert.Equal("y", y.Name); + } + + [Fact] + public void GeneratedConstructorDefaultValues() + { + var comp = CreateCompilation(@"class C(int x, T t = default);"); + comp.VerifyDiagnostics(); + var c = comp.GlobalNamespace.GetTypeMember("C"); + Assert.Equal(1, c.Arity); + var ctor = c.GetMethod(".ctor"); + Assert.Equal(0, ctor.Arity); + Assert.Equal(2, ctor.ParameterCount); + + var x = ctor.Parameters[0]; + Assert.Equal(SpecialType.System_Int32, x.Type.SpecialType); + Assert.Equal("x", x.Name); + + var t = ctor.Parameters[1]; + Assert.Equal(c.TypeParameters[0], t.Type); + Assert.Equal("t", t.Name); + } + } +} From 3ff96cd55fab8429e450c70b22790e6815d6dcd4 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Sun, 5 Jan 2020 17:39:34 -0800 Subject: [PATCH 2/8] Add checking for existing ctor --- .../Source/SourceMemberContainerSymbol.cs | 10 ++- ...odeAnalysis.CSharp.Symbol.UnitTests.csproj | 3 +- .../Test/Symbol/Symbols/Source/RecordTests.cs | 62 +++++++++++++++++++ 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 989284e5eb360..39d2a39416a42 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -2885,9 +2885,13 @@ private void AddSynthesizedRecordMembersIfNecessary(ArrayBuilder members BinderFactory binderFactory = this.DeclaringCompilation.GetBinderFactory(paramList.SyntaxTree); var binder = binderFactory.GetBinder(paramList); - // PROTOTYPE: This adds members unconditionally, but instead we should check if - // the members are already written in user code - members.Add(new SynthesizedRecordConstructor(this, binder, paramList, diagnostics)); + var memberSignatures = new HashSet(members, MemberSignatureComparer.DuplicateSourceComparer); + + var ctor = new SynthesizedRecordConstructor(this, binder, paramList, diagnostics); + if (!memberSignatures.Contains(ctor)) + { + members.Add(ctor); + } } private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder members, ArrayBuilder> staticInitializers, DiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Test/Symbol/Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests.csproj b/src/Compilers/CSharp/Test/Symbol/Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests.csproj index 41c0f83c0d4f2..03f24669cfa61 100644 --- a/src/Compilers/CSharp/Test/Symbol/Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests.csproj +++ b/src/Compilers/CSharp/Test/Symbol/Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests.csproj @@ -5,7 +5,8 @@ Library Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests true - net472;netcoreapp3.0 + + netcoreapp3.0;net472 true diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs index 5148554e6f558..983bfcac0b0e7 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs @@ -1,5 +1,7 @@ // 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 System.Collections.Immutable; +using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Xunit; @@ -47,5 +49,65 @@ public void GeneratedConstructorDefaultValues() Assert.Equal(c.TypeParameters[0], t.Type); Assert.Equal("t", t.Name); } + + [Fact] + public void RecordExistingConstructor1() + { + var comp = CreateCompilation(@" +class C(int x, string y) +{ + public C(int a, string b) + { + } +}"); + comp.VerifyDiagnostics(); + var c = comp.GlobalNamespace.GetTypeMember("C"); + var ctor = c.GetMethod(".ctor"); + Assert.Equal(2, ctor.ParameterCount); + + var a = ctor.Parameters[0]; + Assert.Equal(SpecialType.System_Int32, a.Type.SpecialType); + Assert.Equal("a", a.Name); + + var b = ctor.Parameters[1]; + Assert.Equal(SpecialType.System_String, b.Type.SpecialType); + Assert.Equal("b", b.Name); + } + + [Fact] + public void RecordExistingConstructor01() + { + var comp = CreateCompilation(@" +class C(int x, string y) +{ + public C(int a, int b) // overload + { + } +}"); + comp.VerifyDiagnostics(); + var c = comp.GlobalNamespace.GetTypeMember("C"); + var ctors = c.GetMembers(".ctor"); + + 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) + { + 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); + } + } + } } } From d5b861cc40cb46dfe8d5ec051a84c7b2e6346a82 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 6 Jan 2020 19:59:44 -0800 Subject: [PATCH 3/8] Add error for parameter list without 'data' --- .../CSharp/Portable/CSharpResources.resx | 3 +++ .../CSharp/Portable/Errors/ErrorCode.cs | 1 + .../Source/SourceMemberContainerSymbol.cs | 9 +++++++++ .../Portable/xlf/CSharpResources.cs.xlf | 5 +++++ .../Portable/xlf/CSharpResources.de.xlf | 5 +++++ .../Portable/xlf/CSharpResources.es.xlf | 5 +++++ .../Portable/xlf/CSharpResources.fr.xlf | 5 +++++ .../Portable/xlf/CSharpResources.it.xlf | 5 +++++ .../Portable/xlf/CSharpResources.ja.xlf | 5 +++++ .../Portable/xlf/CSharpResources.ko.xlf | 5 +++++ .../Portable/xlf/CSharpResources.pl.xlf | 5 +++++ .../Portable/xlf/CSharpResources.pt-BR.xlf | 5 +++++ .../Portable/xlf/CSharpResources.ru.xlf | 5 +++++ .../Portable/xlf/CSharpResources.tr.xlf | 5 +++++ .../Portable/xlf/CSharpResources.zh-Hans.xlf | 5 +++++ .../Portable/xlf/CSharpResources.zh-Hant.xlf | 5 +++++ .../Test/Semantic/Semantics/RecordTests.cs | 20 ++++++++++++++++--- .../Test/Symbol/Symbols/Source/RecordTests.cs | 8 ++++---- 18 files changed, 99 insertions(+), 7 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 9d202a7d5ea68..361530fe9e0e4 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5930,4 +5930,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ records + + Records must have both a 'data' modifier and parameter list + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 5e05e824aabad..e392cae72254b 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1738,6 +1738,7 @@ internal enum ErrorCode ERR_InternalError = 8751, ERR_ExternEventInitializer = 8760, + ERR_BadRecordDeclaration = 8761, // Note: you will need to re-generate compiler code after adding warnings (eng\generate-compiler-code.cmd) } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 39d2a39416a42..b19c4d0b1674f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -2879,9 +2879,18 @@ private void AddSynthesizedRecordMembersIfNecessary(ArrayBuilder members if (paramList is null) { + if (_declModifiers.HasFlag(DeclarationModifiers.Data)) + { + diagnostics.Add(ErrorCode.ERR_BadRecordDeclaration, declaration.NameLocations[0]); + } return; } + if (!_declModifiers.HasFlag(DeclarationModifiers.Data)) + { + diagnostics.Add(ErrorCode.ERR_BadRecordDeclaration, paramList.Location); + } + BinderFactory binderFactory = this.DeclaringCompilation.GetBinderFactory(paramList.SyntaxTree); var binder = binderFactory.GetBinder(paramList); diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index d181eb1f31824..b906d2e483095 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -82,6 +82,11 @@ Neplatný operand pro porovnávací vzorek. Vyžaduje se hodnota, ale nalezeno: {0}. + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index d957e1fccc86c..8e239d7f3de78 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -82,6 +82,11 @@ Ungültiger Operand für die Musterübereinstimmung. Ein Wert ist erforderlich, gefunden wurde aber "{0}". + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 06d7aee1be938..68ddf751d413e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -82,6 +82,11 @@ Operando no válido para la coincidencia de patrones. Se requería un valor, pero se encontró '{0}'. + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 8a0f48a2899ef..3bc66af39ebcc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -82,6 +82,11 @@ Opérande non valide pour les critères spéciaux ; la valeur nécessaire n'est pas celle trouvée, '{0}'. + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 6c15d8df11bf4..56a15a450d7b5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -82,6 +82,11 @@ L'operando non è valido per i criteri di ricerca. È richiesto un valore ma è stato trovato '{0}'. + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index e40296bd1ec4c..eae8904e1f6e7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -82,6 +82,11 @@ パターン マッチには使用できないオペランドです。値が必要ですが、'{0}' が見つかりました。 + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 724fae79a23d8..fa7ffeed9f334 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -82,6 +82,11 @@ 패턴 일치에 대한 피연산자가 잘못되었습니다. 값이 필요하지만 '{0}'을(를) 찾았습니다. + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 4f97e5f5a2b23..611300034ee88 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -82,6 +82,11 @@ Nieprawidłowy operand dla dopasowania wzorca; wymagana jest wartość, a znaleziono „{0}”. + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 71fa93f245c3c..979d49226d8c1 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -82,6 +82,11 @@ Operando inválido para correspondência de padrão. Um valor era obrigatório, mas '{0}' foi encontrado. + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index edda67df6a355..f39433259a3bb 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -82,6 +82,11 @@ Недопустимый операнд для сопоставления с шаблоном. Требуется значение, но найдено "{0}". + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index f1c6c7372b0e9..aac52b2110a9d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -82,6 +82,11 @@ Desen eşleşmesi için işlenen geçersiz. Değer gerekiyordu ancak '{0}' bulundu. + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 15282e02836bc..f5cfe305db672 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -82,6 +82,11 @@ 用于模式匹配的操作数无效;需要值,但找到的是“{0}”。 + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index fe989c6725917..820d90e723797 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -82,6 +82,11 @@ 模式比對運算元無效; 需要值,但找到 '{0}'。 + + Records must have both a 'data' modifier and parameter list + Records must have both a 'data' modifier and parameter list + + Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. Command-line syntax error: '{0}' is not a valid value for the '{1}' option. The value must be of the form '{2}'. diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index faa9926edebf0..b2f6c6cfa05a3 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -26,6 +26,9 @@ 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 + // class Point(int x, int y); + Diagnostic(ErrorCode.ERR_BadRecordDeclaration, "(int x, int y)").WithLocation(2, 12), // (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) @@ -34,7 +37,10 @@ data class Point { } 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 { } - Diagnostic(ErrorCode.ERR_FeatureInPreview, "data").WithArguments("records").WithLocation(2, 1) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "data").WithArguments("records").WithLocation(2, 1), + // (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); comp.VerifyDiagnostics( @@ -50,9 +56,17 @@ data class Point { } ); comp = CreateCompilation(src1, parseOptions: TestOptions.RegularPreview); - comp.VerifyDiagnostics(); + 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.VerifyDiagnostics(); + 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.VerifyDiagnostics(); } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs index 983bfcac0b0e7..f11c41a5d995c 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs @@ -15,7 +15,7 @@ private static CSharpCompilation CreateCompilation(CSharpTestSource source) [Fact] public void GeneratedConstructor() { - var comp = CreateCompilation(@"class C(int x, string y);"); + var comp = CreateCompilation(@"data class C(int x, string y);"); comp.VerifyDiagnostics(); var c = comp.GlobalNamespace.GetTypeMember("C"); var ctor = c.GetMethod(".ctor"); @@ -33,7 +33,7 @@ public void GeneratedConstructor() [Fact] public void GeneratedConstructorDefaultValues() { - var comp = CreateCompilation(@"class C(int x, T t = default);"); + var comp = CreateCompilation(@"data class C(int x, T t = default);"); comp.VerifyDiagnostics(); var c = comp.GlobalNamespace.GetTypeMember("C"); Assert.Equal(1, c.Arity); @@ -54,7 +54,7 @@ public void GeneratedConstructorDefaultValues() public void RecordExistingConstructor1() { var comp = CreateCompilation(@" -class C(int x, string y) +data class C(int x, string y) { public C(int a, string b) { @@ -78,7 +78,7 @@ public C(int a, string b) public void RecordExistingConstructor01() { var comp = CreateCompilation(@" -class C(int x, string y) +data class C(int x, string y) { public C(int a, int b) // overload { From 2f488019c7eab4ca705355d383e8c8bc2346240e Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 6 Jan 2020 20:05:35 -0800 Subject: [PATCH 4/8] Respond to PR feedback --- src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs index f11c41a5d995c..f1e0644b01bff 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs @@ -87,6 +87,7 @@ 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); foreach (MethodSymbol ctor in ctors) { From 7d4b5c117d8d1e52ff87398dd6667a7d627a205b Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Wed, 15 Jan 2020 15:16:40 -0800 Subject: [PATCH 5/8] nullable enable in one more file --- .../Symbols/Synthesized/SynthesizedRecordConstructor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordConstructor.cs index 7e4f601b622aa..ce2643d655eb1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedRecordConstructor.cs @@ -1,5 +1,7 @@ // 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.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Syntax; From 8d916571a006ddd706957cb943160bac11691dd3 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 16 Jan 2020 10:22:15 -0800 Subject: [PATCH 6/8] Add '?' --- .../Portable/Symbols/Source/SourceMemberContainerSymbol.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index b19c4d0b1674f..a2609d7fb0c87 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -426,7 +426,7 @@ internal sealed override bool HasComplete(CompletionPart part) protected abstract void CheckBase(DiagnosticBag diagnostics); protected abstract void CheckInterfaces(DiagnosticBag diagnostics); - internal override void ForceComplete(SourceLocation locationOpt, CancellationToken cancellationToken) + internal override void ForceComplete(SourceLocation? locationOpt, CancellationToken cancellationToken) { while (true) { From 28d0443ef057a063e63e7d29b2bfbc11a479feb9 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 16 Jan 2020 14:08:34 -0800 Subject: [PATCH 7/8] Revert change to GetMembersAndInitializers --- .../Portable/Symbols/Source/SourceMemberContainerSymbol.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index a2609d7fb0c87..c0946146c9551 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -1306,7 +1306,7 @@ private MembersAndInitializers GetMembersAndInitializers() AddDeclarationDiagnostics(diagnostics); diagnostics.Free(); - return _lazyMembersAndInitializers!; + return membersAndInitializers!; } protected Dictionary> GetMembersByName() From 019f110c02ea5bd9e447509d2dc08c4f3bcd3865 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 16 Jan 2020 16:30:13 -0800 Subject: [PATCH 8/8] Accidentally added compiler trait --- src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index b2f6c6cfa05a3..4966e4aa03e61 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -6,7 +6,6 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics { - [CompilerTrait(CompilerFeature.ReadOnlyReferences)] public class RecordTests : CompilingTestBase { [Fact]