From bbfeab56279f1617a183a8bd1af494a161eec284 Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Mon, 12 Jul 2021 14:22:08 -0700 Subject: [PATCH 1/2] Allow public parameterless struct constructors declared in source (#51679) --- .../Portable/Binder/Binder_Expressions.cs | 2 +- .../CSharp/Portable/CSharpResources.resx | 15 +- .../CSharp/Portable/CodeGen/EmitExpression.cs | 4 +- .../Portable/Compiler/MethodCompiler.cs | 10 +- .../Emitter/Model/AttributeDataAdapter.cs | 6 +- .../Portable/Emitter/Model/PEModuleBuilder.cs | 2 +- .../Emitter/NoPia/EmbeddedTypesManager.cs | 2 +- .../CSharp/Portable/Errors/ErrorCode.cs | 5 +- .../CSharp/Portable/Errors/MessageID.cs | 4 + .../Portable/FlowAnalysis/AbstractFlowPass.cs | 3 + .../Portable/FlowAnalysis/NullableWalker.cs | 9 +- .../ExpressionLambdaRewriter.cs | 2 +- .../LocalRewriter_ObjectCreationExpression.cs | 2 +- .../Portable/Symbols/ConstraintsHelper.cs | 12 +- .../Symbols/MemberSymbolExtensions.cs | 62 +- .../Symbols/Metadata/PE/PENamedTypeSymbol.cs | 6 +- .../Portable/Symbols/NamedTypeSymbol.cs | 10 + .../Retargeting/RetargetingNamedTypeSymbol.cs | 2 + .../Symbols/Source/ParameterHelpers.cs | 2 +- .../Source/SourceMemberContainerSymbol.cs | 25 +- .../Portable/xlf/CSharpResources.cs.xlf | 25 +- .../Portable/xlf/CSharpResources.de.xlf | 25 +- .../Portable/xlf/CSharpResources.es.xlf | 25 +- .../Portable/xlf/CSharpResources.fr.xlf | 25 +- .../Portable/xlf/CSharpResources.it.xlf | 25 +- .../Portable/xlf/CSharpResources.ja.xlf | 25 +- .../Portable/xlf/CSharpResources.ko.xlf | 25 +- .../Portable/xlf/CSharpResources.pl.xlf | 25 +- .../Portable/xlf/CSharpResources.pt-BR.xlf | 25 +- .../Portable/xlf/CSharpResources.ru.xlf | 25 +- .../Portable/xlf/CSharpResources.tr.xlf | 25 +- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 25 +- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 25 +- .../AttributeTests_WellKnownAttributes.cs | 48 + .../CSharp/Test/Emit/Emit/NoPiaEmbedTypes.cs | 4 +- .../FlowAnalysis/FlowDiagnosticTests.cs | 58 +- .../Test/Semantic/Semantics/ConstantTests.cs | 15 +- .../Semantics/NullableContextTests.cs | 5 +- .../Semantics/NullableReferenceTypesTests.cs | 9 +- .../Semantic/Semantics/RecordStructTests.cs | 149 +- .../Semantics/StructConstructorTests.cs | 1861 +++++++++++++++++ .../Test/Semantic/Semantics/StructsTests.cs | 90 +- .../UninitializedNonNullableFieldTests.cs | 13 +- .../Test/Symbol/Symbols/Source/FieldTests.cs | 23 +- .../Test/Symbol/Symbols/Source/MethodTests.cs | 11 +- .../Symbol/Symbols/Source/PropertyTests.cs | 98 +- .../Test/Symbol/Symbols/SymbolErrorTests.cs | 51 +- .../CompilationTestDataExtensions.cs | 57 +- .../Test/Core/CompilationVerifier.cs | 7 + 49 files changed, 2569 insertions(+), 440 deletions(-) create mode 100644 src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 24f36003c8f29..36f10674a8141 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -5274,7 +5274,7 @@ protected BoundExpression BindClassCreationExpression( ReportDiagnosticsIfObsolete(diagnostics, method, node, hasBaseReceiver: false); // NOTE: Use-site diagnostics were reported during overload resolution. - ConstantValue constantValueOpt = (initializerSyntaxOpt == null && method.IsDefaultValueTypeConstructor()) ? + ConstantValue constantValueOpt = (initializerSyntaxOpt == null && method.IsDefaultValueTypeConstructor(requireZeroInit: true)) ? FoldParameterlessValueTypeConstructor(type) : null; diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 2e5a1dac19235..460589cc00bec 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -1691,9 +1691,6 @@ If such a class is used as a base class and if the deriving class defines a dest Conversion, equality, or inequality operators declared in interfaces must be abstract - - Structs cannot contain explicit parameterless constructors - Enums cannot contain explicit parameterless constructors @@ -1709,9 +1706,6 @@ If such a class is used as a base class and if the deriving class defines a dest '{0}': cannot reference a type through an expression; try '{1}' instead - - '{0}': cannot have instance property or field initializers in structs - Name of destructor must match name of type @@ -6597,6 +6591,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ positional fields in records + + parameterless struct constructors + + + struct field initializers + variance safety for static interface members @@ -6667,6 +6667,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A global using directive must precede all non-global using directives. + + The parameterless struct constructor must be 'public'. + static abstract members in interfaces diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index a57d88fd832c5..ca23029bec378 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -1481,7 +1481,7 @@ private enum CallKind private void EmitCallExpression(BoundCall call, UseKind useKind) { - if (call.Method.IsDefaultValueTypeConstructor()) + if (call.Method.IsDefaultValueTypeConstructor(requireZeroInit: true)) { EmitDefaultValueTypeConstructorCallExpression(call); } @@ -1956,7 +1956,7 @@ private void EmitConvertedStackAllocExpression(BoundConvertedStackAllocExpressio private void EmitObjectCreationExpression(BoundObjectCreationExpression expression, bool used) { MethodSymbol constructor = expression.Constructor; - if (constructor.IsDefaultValueTypeConstructor()) + if (constructor.IsDefaultValueTypeConstructor(requireZeroInit: true)) { EmitInitObj(expression.Type, used, expression.Syntax); } diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index e07d3123407a9..228d185fb7ba3 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -965,7 +965,7 @@ private void CompileMethod( } // no need to emit the default ctor, we are not emitting those - if (methodSymbol.IsDefaultValueTypeConstructor()) + if (methodSymbol.IsDefaultValueTypeConstructor(requireZeroInit: true)) { return; } @@ -1194,7 +1194,7 @@ forSemanticModel.Syntax is { } semanticModelSyntax && if (!hasErrors && (hasBody || includeNonEmptyInitializersInBody)) { Debug.Assert(!(methodSymbol.IsImplicitInstanceConstructor && methodSymbol.ParameterCount == 0) || - !methodSymbol.ContainingType.IsStructType()); + !methodSymbol.IsDefaultValueTypeConstructor(requireZeroInit: true)); // Fields must be initialized before constructor initializer (which is the first statement of the analyzed body, if specified), // so that the initialization occurs before any method overridden by the declaring class can be invoked from the base constructor @@ -1707,13 +1707,13 @@ syntaxNode is ConstructorDeclarationSyntax constructorSyntax && constructorSyntax.Identifier.ValueText); } - ExecutableCodeBinder bodyBinder = sourceMethod.TryGetBodyBinder(); - - if (sourceMethod.IsExtern || sourceMethod.IsDefaultValueTypeConstructor()) + Debug.Assert(!sourceMethod.IsDefaultValueTypeConstructor(requireZeroInit: false)); + if (sourceMethod.IsExtern) { return null; } + ExecutableCodeBinder bodyBinder = sourceMethod.TryGetBodyBinder(); if (bodyBinder != null) { importChain = bodyBinder.ImportChain; diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/AttributeDataAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/AttributeDataAdapter.cs index 75726e3486208..744f7e691c1a7 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/AttributeDataAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/AttributeDataAdapter.cs @@ -35,10 +35,10 @@ internal abstract partial class CSharpAttributeData : Cci.ICustomAttribute Cci.IMethodReference Cci.ICustomAttribute.Constructor(EmitContext context, bool reportDiagnostics) { - if (this.AttributeConstructor.IsDefaultValueTypeConstructor()) + if (this.AttributeConstructor.IsDefaultValueTypeConstructor(requireZeroInit: true)) { - // Parameter constructors for structs exist in symbol table, but are not emitted. - // Produce an error since we cannot use it (instead of crashing): + // Default parameterless constructors for structs exist in symbol table, but are not emitted. + // Produce an error since we cannot use it (instead of crashing). // Details: https://github.com/dotnet/roslyn/issues/19394 if (reportDiagnostics) diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs index 3fdface141a00..b7e0570557518 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs @@ -1147,7 +1147,7 @@ internal Cci.IMethodReference Translate( BoundArgListOperator optArgList = null, bool needDeclaration = false) { - Debug.Assert(!methodSymbol.IsDefaultValueTypeConstructor()); + Debug.Assert(!methodSymbol.IsDefaultValueTypeConstructor(requireZeroInit: true)); Debug.Assert(optArgList == null || (methodSymbol.IsVararg && !needDeclaration)); Cci.IMethodReference unexpandedMethodRef = Translate(methodSymbol, syntaxNodeOpt, diagnostics, needDeclaration); diff --git a/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedTypesManager.cs b/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedTypesManager.cs index 2362945a2ba54..c675813c72d5f 100644 --- a/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedTypesManager.cs +++ b/src/Compilers/CSharp/Portable/Emitter/NoPia/EmbeddedTypesManager.cs @@ -454,7 +454,7 @@ internal override EmbeddedMethod EmbedMethod( DiagnosticBag diagnostics) { Debug.Assert(method.AdaptedSymbol.IsDefinition); - Debug.Assert(!method.AdaptedMethodSymbol.IsDefaultValueTypeConstructor()); + Debug.Assert(!method.AdaptedMethodSymbol.IsDefaultValueTypeConstructor(requireZeroInit: false)); EmbeddedMethod embedded = new EmbeddedMethod(type, method); EmbeddedMethod cached = EmbeddedMethodsMap.GetOrAdd(method, embedded); diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 16f5d833134d0..7f4ebccae74b5 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -384,12 +384,12 @@ internal enum ErrorCode ERR_BadBinaryOperatorSignature = 563, ERR_BadShiftOperatorSignature = 564, ERR_InterfacesCantContainConversionOrEqualityOperators = 567, - ERR_StructsCantContainDefaultConstructor = 568, + //ERR_StructsCantContainDefaultConstructor = 568, ERR_CantOverrideBogusMethod = 569, ERR_BindToBogus = 570, ERR_CantCallSpecialMethod = 571, ERR_BadTypeReference = 572, - ERR_FieldInitializerInStruct = 573, + //ERR_FieldInitializerInStruct = 573, ERR_BadDestructorName = 574, ERR_OnlyClassesCanContainDestructors = 575, ERR_ConflictAliasAndMember = 576, @@ -1960,6 +1960,7 @@ internal enum ErrorCode ERR_BuilderAttributeDisallowed = 8935, ERR_FeatureNotAvailableInVersion10 = 8936, ERR_SimpleProgramIsEmpty = 8937, + ERR_NonPublicParameterlessStructConstructor = 8938, #endregion diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index 50a2a6dc96cda..5ae0e24615359 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -229,6 +229,8 @@ internal enum MessageID IDS_FeatureLambdaReturnType = MessageBase + 12804, IDS_AsyncMethodBuilderOverride = MessageBase + 12805, IDS_FeatureImplicitImplementationOfNonPublicMemebers = MessageBase + 12806, + IDS_FeatureParameterlessStructConstructors = MessageBase + 12807, + IDS_FeatureStructFieldInitializers = MessageBase + 12808, } // Message IDs may refer to strings that need to be localized. @@ -354,6 +356,8 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) case MessageID.IDS_AsyncMethodBuilderOverride: // semantic check case MessageID.IDS_FeatureConstantInterpolatedStrings: // semantic check case MessageID.IDS_FeatureImplicitImplementationOfNonPublicMemebers: // semantic check + case MessageID.IDS_FeatureParameterlessStructConstructors: // semantic check + case MessageID.IDS_FeatureStructFieldInitializers: // semantic check return LanguageVersion.CSharp10; // C# 9.0 features. diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index df4d53bdf8607..69069edb84c27 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -442,6 +442,9 @@ protected ImmutableArray Analyze(ref bool badRegion, Optional - /// Return true if the class type has a public parameterless constructor. + /// Return true if the type has a public parameterless constructor. /// - private static bool HasPublicParameterlessConstructor(NamedTypeSymbol type) + private static bool HasPublicParameterlessConstructor(NamedTypeSymbol type, bool synthesizedIfMissing) { - Debug.Assert(type.TypeKind == TypeKind.Class); + Debug.Assert(type.TypeKind is TypeKind.Class or TypeKind.Struct); foreach (var constructor in type.InstanceConstructors) { if (constructor.ParameterCount == 0) @@ -1382,7 +1384,7 @@ private static bool HasPublicParameterlessConstructor(NamedTypeSymbol type) return constructor.DeclaredAccessibility == Accessibility.Public; } } - return false; + return synthesizedIfMissing; } /// diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs index 413b7ed50c171..a7e5623820c57 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs @@ -370,7 +370,7 @@ internal static bool IsConstructor(this MethodSymbol method) /// /// Returns true if the method is a constructor and has a this() constructor initializer. /// - internal static bool HasThisConstructorInitializer(this MethodSymbol method) + internal static bool HasThisConstructorInitializer(this MethodSymbol method, out ConstructorInitializerSyntax initializerSyntax) { if ((object)method != null && method.MethodKind == MethodKind.Constructor) { @@ -378,30 +378,40 @@ internal static bool HasThisConstructorInitializer(this MethodSymbol method) if ((object)sourceMethod != null) { ConstructorDeclarationSyntax constructorSyntax = sourceMethod.SyntaxNode as ConstructorDeclarationSyntax; - if (constructorSyntax != null) + if (constructorSyntax?.Initializer?.Kind() == SyntaxKind.ThisConstructorInitializer) { - ConstructorInitializerSyntax initializerSyntax = constructorSyntax.Initializer; - if (initializerSyntax != null) - { - return initializerSyntax.Kind() == SyntaxKind.ThisConstructorInitializer; - } + initializerSyntax = constructorSyntax.Initializer; + return true; } } } + initializerSyntax = null; return false; } internal static bool IncludeFieldInitializersInBody(this MethodSymbol methodSymbol) { return methodSymbol.IsConstructor() - && !methodSymbol.HasThisConstructorInitializer() + && !(methodSymbol.HasThisConstructorInitializer(out var initializerSyntax) && !isDefaultValueTypeConstructor(methodSymbol.ContainingType, initializerSyntax)) && !(methodSymbol is SynthesizedRecordCopyCtor) // A record copy constructor is special, regular initializers are not supposed to be executed by it. && !Binder.IsUserDefinedRecordCopyConstructor(methodSymbol); + + // A struct constructor that calls ": this()" will need to include field initializers if the + // parameterless constructor is a synthesized default constructor that is not emitted. + static bool isDefaultValueTypeConstructor(NamedTypeSymbol containingType, ConstructorInitializerSyntax initializerSyntax) + { + if (initializerSyntax.ArgumentList.Arguments.Count > 0) + { + return false; + } + var constructor = containingType.InstanceConstructors.SingleOrDefault(m => m.ParameterCount == 0); + return constructor?.IsDefaultValueTypeConstructor(requireZeroInit: true) == true; + } } /// - /// NOTE: every struct has a public parameterless constructor either used-defined or default one + /// NOTE: every struct has a public parameterless constructor either user-defined or default one /// internal static bool IsParameterlessConstructor(this MethodSymbol method) { @@ -409,15 +419,33 @@ internal static bool IsParameterlessConstructor(this MethodSymbol method) } /// - /// default zero-init constructor symbol is added to a struct when it does not define - /// its own parameterless public constructor. - /// We do not emit this constructor and do not call it + /// Returns true if the method is the default constructor synthesized for struct types, and + /// if is true, the constructor simply zero-inits the instance. + /// If the containing struct type is from metadata, the default constructor is synthesized when there + /// is no accessible parameterless constructor. (That synthesized constructor from metadata zero-inits + /// the instance.) If the containing struct type is from source, the parameterless constructor is synthesized + /// if there is no explicit parameterless constructor. And if the source type has no field initializers, or + /// the type has field initializers and at least one explicit constructor with parameters, the synthesized + /// parameterless constructor simply zero-inits the instance (and is not emitted). /// - internal static bool IsDefaultValueTypeConstructor(this MethodSymbol method) + internal static bool IsDefaultValueTypeConstructor(this MethodSymbol method, bool requireZeroInit) { - return method.IsImplicitlyDeclared && - method.ContainingType.IsValueType && - method.IsParameterlessConstructor(); + if (method.IsImplicitlyDeclared && + method.ContainingType.IsValueType && + method.IsParameterlessConstructor()) + { + if (!requireZeroInit) + { + return true; + } + var containingType = method.ContainingType.OriginalDefinition; + var constructors = containingType.InstanceConstructors; + // If there are field initializers and an explicit constructor with parameters + // (that is, more than one constructor), the implicit parameterless constructor + // is treated as the zero-init constructor and does not execute field initializers. + return constructors.Length > 1 || !containingType.HasFieldInitializers(); + } + return false; } /// @@ -426,7 +454,7 @@ internal static bool IsDefaultValueTypeConstructor(this MethodSymbol method) internal static bool ShouldEmit(this MethodSymbol method) { // Don't emit the default value type constructor - the runtime handles that - if (method.IsDefaultValueTypeConstructor()) + if (method.IsDefaultValueTypeConstructor(requireZeroInit: true)) { return false; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs index 94efbb4550f53..5b8ec4864be4f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs @@ -978,8 +978,10 @@ internal override IEnumerable GetMethodsToEmit() var method = (MethodSymbol)members[index]; - // Don't emit the default value type constructor - the runtime handles that - if (!method.IsDefaultValueTypeConstructor()) + // Don't emit the default value type constructor - the runtime handles that. + // For parameterless struct constructors from metadata, IsDefaultValueTypeConstructor() + // ignores requireZeroInit and simply checks if the method is implicitly declared. + if (!method.IsDefaultValueTypeConstructor(requireZeroInit: false)) { yield return method; } diff --git a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs index 05afb5f6ca28d..562a770862603 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs @@ -1536,6 +1536,16 @@ internal bool IsTupleTypeOfCardinality(out int tupleCardinality) /// internal abstract NamedTypeSymbol NativeIntegerUnderlyingType { get; } + /// + /// Returns true if the type is defined in source and contains field initializers. + /// This method is only valid on a definition. + /// + internal virtual bool HasFieldInitializers() + { + Debug.Assert(IsDefinition); + return false; + } + protected override ISymbol CreateISymbol() { return new PublicModel.NonErrorNamedTypeSymbol(this, DefaultNullableAnnotation); diff --git a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingNamedTypeSymbol.cs index 4bd11b5db2f26..3afb8804108a9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingNamedTypeSymbol.cs @@ -395,6 +395,8 @@ public sealed override bool AreLocalsZeroed internal sealed override bool IsRecordStruct => _underlyingType.IsRecordStruct; internal sealed override bool HasPossibleWellKnownCloneMethod() => _underlyingType.HasPossibleWellKnownCloneMethod(); + internal override bool HasFieldInitializers() => _underlyingType.HasFieldInitializers(); + internal override IEnumerable<(MethodSymbol Body, MethodSymbol Implemented)> SynthesizedInterfaceMethodImpls() { foreach ((MethodSymbol body, MethodSymbol implemented) in _underlyingType.SynthesizedInterfaceMethodImpls()) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs index a31c1ffbba242..62b2df3750bda 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs @@ -659,7 +659,7 @@ private static bool IsValidDefaultValue(BoundExpression expression) private static bool IsValidDefaultValue(BoundObjectCreationExpression expression) { - return expression.Constructor.IsDefaultValueTypeConstructor() && expression.InitializerExpressionOpt == null; + return expression.Constructor.IsDefaultValueTypeConstructor(requireZeroInit: true) && expression.InitializerExpressionOpt == null; } internal static MethodSymbol FindContainingGenericMethod(Symbol symbol) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 7cf65647d90a8..f49f164a8a61a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -2978,14 +2978,6 @@ private void AddDeclaredNontypeMembers(DeclaredMembersAndInitializersBuilder bui var parameterList = recordDecl.ParameterList; noteRecordParameters(recordDecl, parameterList, builder, diagnostics); AddNonTypeMembers(builder, recordDecl.Members, diagnostics); - - // We will allow declaring parameterless constructors - // Tracking issue https://github.com/dotnet/roslyn/issues/52240 - if (syntax.Kind() == SyntaxKind.RecordStructDeclaration && parameterList?.ParameterCount == 0) - { - diagnostics.Add(ErrorCode.ERR_StructsCantContainDefaultConstructor, parameterList.Location); - } - break; default: @@ -3423,13 +3415,18 @@ private static void CheckForStructDefaultConstructors( { if (m.MethodKind == MethodKind.Constructor && m.ParameterCount == 0) { + var location = m.Locations[0]; if (isEnum) { - diagnostics.Add(ErrorCode.ERR_EnumsCantContainDefaultConstructor, m.Locations[0]); + diagnostics.Add(ErrorCode.ERR_EnumsCantContainDefaultConstructor, location); } else { - diagnostics.Add(ErrorCode.ERR_StructsCantContainDefaultConstructor, m.Locations[0]); + MessageID.IDS_FeatureParameterlessStructConstructors.CheckFeatureAvailability(diagnostics, m.DeclaringCompilation, location); + if (m.DeclaredAccessibility != Accessibility.Public) + { + diagnostics.Add(ErrorCode.ERR_NonPublicParameterlessStructConstructor, location); + } } } } @@ -3451,8 +3448,8 @@ private void CheckForStructBadInitializers(DeclaredMembersAndInitializersBuilder { foreach (FieldOrPropertyInitializer initializer in initializers) { - // '{0}': cannot have instance field initializers in structs - diagnostics.Add(ErrorCode.ERR_FieldInitializerInStruct, (initializer.FieldOpt.AssociatedSymbol ?? initializer.FieldOpt).Locations[0], this); + var symbol = initializer.FieldOpt.AssociatedSymbol ?? initializer.FieldOpt; + MessageID.IDS_FeatureStructFieldInitializers.CheckFeatureAvailability(diagnostics, symbol.DeclaringCompilation, symbol.Locations[0]); } } } @@ -4097,7 +4094,7 @@ private void AddSynthesizedConstructorsIfNecessary(MembersAndInitializersBuilder // NOTE: Per section 11.3.8 of the spec, "every struct implicitly has a parameterless instance constructor". // We won't insert a parameterless constructor for a struct if there already is one. - // We don't expect anything to be emitted, but it should be in the symbol table. + // The synthesized constructor will only be emitted if there are field initializers, but it should be in the symbol table. if ((!hasParameterlessInstanceConstructor && this.IsStructType()) || (!hasInstanceConstructor && !this.IsStatic && !this.IsInterface)) { @@ -4644,6 +4641,8 @@ public sealed override NamedTypeSymbol ConstructedFrom get { return this; } } + internal sealed override bool HasFieldInitializers() => InstanceInitializers.Length > 0; + internal class SynthesizedExplicitImplementations { public static readonly SynthesizedExplicitImplementations Empty = new SynthesizedExplicitImplementations(ImmutableArray.Empty, diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 681d371b2108c..460fdd2d22091 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -772,6 +772,11 @@ Člen záznamu {0} musí být veřejný. + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. {0} musí povolovat přepisování, protože obsahující záznam není zapečetěný. @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ zapečetěný ToString v záznamu + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5240,11 +5255,6 @@ Pokud se taková třída používá jako základní třída a pokud odvozující První operand přetěžovaného operátoru shift musí být stejného typu jako obsahující typ a druhý operand musí být typu int. - - Structs cannot contain explicit parameterless constructors - Struktury nemůžou obsahovat explicitní konstruktory bez parametrů. - - Enums cannot contain explicit parameterless constructors Výčty nemůžou obsahovat explicitní konstruktory bez parametrů. @@ -5270,11 +5280,6 @@ Pokud se taková třída používá jako základní třída a pokud odvozující {0}: Nemůže odkazovat na typ prostřednictvím výrazu. Místo toho zkuste {1}. - - '{0}': cannot have instance property or field initializers in structs - {0}: Vlastnosti instancí nebo inicializátory polí nemůžou být ve strukturách. - - Name of destructor must match name of type Název destruktoru musí odpovídat názvu typu. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index ac8302d5b02e1..b46d35383cf15 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -772,6 +772,11 @@ Der Datensatzmember "{0}" muss öffentlich sein. + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. "{0}" muss Überschreibungen zulassen, weil der enthaltende Datensatz nicht versiegelt ist. @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ versiegelte "ToString" im Datensatz + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5240,11 +5255,6 @@ Wenn solch eine Klasse als Basisklasse verwendet wird und die ableitende Klasse Der erste Operand eines überladenen Schiebeoperators muss den enthaltenden Typ aufweisen, und der zweite Operand muss eine ganze Zahl sein. - - Structs cannot contain explicit parameterless constructors - Strukturen können keine expliziten parameterlosen Konstruktoren enthalten. - - Enums cannot contain explicit parameterless constructors Enumerationen können keine expliziten parameterlosen Konstruktoren enthalten. @@ -5270,11 +5280,6 @@ Wenn solch eine Klasse als Basisklasse verwendet wird und die ableitende Klasse "{0}": Auf einen Typ kann nicht durch einen Ausdruck verwiesen werden. Verwenden Sie stattdessen "{1}". - - '{0}': cannot have instance property or field initializers in structs - "{0}": Instanzenfeld- oder -eigenschaftsinitialisierer können sich nicht in Strukturen befinden. - - Name of destructor must match name of type Der Name des Destruktors muss mit dem Namen des Typs Klasse übereinstimmen. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 28449569693d6..52f430638c9ca 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -772,6 +772,11 @@ El miembro de registro "{0}" debe ser público. + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. "{0}" debe permitir la invalidación porque el registro contenedor no está sellado. @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ ToString sellado en el registro + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5240,11 +5255,6 @@ Si se utiliza una clase de este tipo como clase base y si la clase derivada defi El primer operando de un operador de desplazamiento sobrecargado debe tener el mismo tipo que el tipo contenedor, y el tipo del segundo operando debe ser int - - Structs cannot contain explicit parameterless constructors - Los structs no pueden contener constructores explícitos sin parámetros - - Enums cannot contain explicit parameterless constructors Las enumeraciones no pueden contener constructores explícitos sin parámetros @@ -5270,11 +5280,6 @@ Si se utiliza una clase de este tipo como clase base y si la clase derivada defi '{0}': no se puede hacer referencia a un tipo a través de una expresión; pruebe con '{1}' - - '{0}': cannot have instance property or field initializers in structs - '{0}': no se pueden tener propiedades de instancia o inicializadores de campo en estructuras - - Name of destructor must match name of type El nombre del destructor debe coincidir con el nombre del tipo diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 5ae4c587debd5..1a6c376d6ebe7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -772,6 +772,11 @@ Le membre d'enregistrement '{0}' doit être public. + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. '{0}' doit autoriser la substitution, car l'enregistrement contenant n'est pas sealed. @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ ToString scellé dans l’enregistrement + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5240,11 +5255,6 @@ Si une telle classe est utilisée en tant que classe de base et si la classe dé Le premier opérande d'un opérateur de décalage surchargé doit être du même type que le type conteneur et le type du second opérande doit être int - - Structs cannot contain explicit parameterless constructors - Les structures ne peuvent pas contenir de constructeurs exempts de paramètres explicites - - Enums cannot contain explicit parameterless constructors Les enums ne peuvent pas contenir de constructeurs sans paramètre explicites @@ -5270,11 +5280,6 @@ Si une telle classe est utilisée en tant que classe de base et si la classe dé '{0}' : impossible de référencer un type par l'intermédiaire d'une expression ; essayez plutôt '{1}' - - '{0}': cannot have instance property or field initializers in structs - '{0}' : impossible d'avoir des propriétés d'instance ou des initialiseurs de champ dans des structs - - Name of destructor must match name of type Le nom du destructeur doit correspondre au nom du type diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 687c587c69e33..89f3cb3d1bcfa 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -772,6 +772,11 @@ Il membro del record '{0}' deve essere pubblico. + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. '{0}' deve consentire l'override perché il record contenitore non è sealed. @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ ToString sealed nel record + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5240,11 +5255,6 @@ Se si usa tale classe come classe base e se la classe di derivazione definisce u Il primo operando di un operatore shift di overload deve essere dello stesso tipo del tipo che lo contiene, mentre il tipo del secondo operando deve essere int - - Structs cannot contain explicit parameterless constructors - Gli struct non possono contenere costruttori espliciti senza parametri - - Enums cannot contain explicit parameterless constructors Le enumerazioni non possono contenere costruttori espliciti senza parametri @@ -5270,11 +5280,6 @@ Se si usa tale classe come classe base e se la classe di derivazione definisce u '{0}': non è possibile fare riferimento a un tipo con un'espressione. Provare con '{1}' - - '{0}': cannot have instance property or field initializers in structs - '{0}': le strutture non possono contenere inizializzatori di campo o di proprietà delle istanze - - Name of destructor must match name of type Il nome del distruttore deve corrispondere al nome del tipo diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 584fab8afb86f..7a0867ff4fad6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -772,6 +772,11 @@ レコード メンバー '{0}' は public でなければなりません。 + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. '{0}' ではオーバーライドを許可する必要があります。これが含まれているレコードが sealed ではないためです。 @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ レコードでシールされた ToString + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5240,11 +5255,6 @@ If such a class is used as a base class and if the deriving class defines a dest オーバーロードされた shift 演算子の最初のオペランドはそれを含む型と同じ型、2 番目のオペランドの型は int でなければなりません - - Structs cannot contain explicit parameterless constructors - 構造体に明示的なパラメーターのないコンストラクターを含めることはできません - - Enums cannot contain explicit parameterless constructors 列挙型は明示的なパラメーターなしのコンス トラクターを含めることはできません @@ -5270,11 +5280,6 @@ If such a class is used as a base class and if the deriving class defines a dest '{0}': 式から型を参照することはできません。'{1}' を使用してください - - '{0}': cannot have instance property or field initializers in structs - '{0}': 構造体にインスタンス プロパティまたはフィールド初期化子を含めることはできません - - Name of destructor must match name of type デストラクターの名前を型の名前と同じにしてください diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index aad215b024ad0..189511a4fc7b1 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -772,6 +772,11 @@ 레코드 멤버 '{0}'은(는) 퍼블릭이어야 합니다. + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. 포함된 레코드가 봉인되지 않았으므로 '{0}'은(는) 재정의를 허용해야 합니다. @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ 레코드의 봉인된 ToString + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5239,11 +5254,6 @@ If such a class is used as a base class and if the deriving class defines a dest 오버로드된 시프트 연산자의 첫 번째 피연산자는 포함하는 형식과 동일한 형식이어야 하며 두 번째 피연산자는 정수 형식이어야 합니다. - - Structs cannot contain explicit parameterless constructors - 구조체는 매개 변수가 없는 명시적 생성자를 포함할 수 없습니다. - - Enums cannot contain explicit parameterless constructors 열거형은 명시적인 매개 변수가 없는 생성자를 포함할 수 없습니다. @@ -5269,11 +5279,6 @@ If such a class is used as a base class and if the deriving class defines a dest '{0}': 식을 통해 형식을 참조할 수 없습니다. 대신 '{1}'을(를) 시도하세요. - - '{0}': cannot have instance property or field initializers in structs - '{0}': 구조체에는 인스턴스 속성 또는 이니셜라이저를 사용할 수 없습니다. - - Name of destructor must match name of type 소멸자 이름은 형식 이름과 일치해야 합니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 16e776b6e992f..bc4c05e274bf0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -772,6 +772,11 @@ Składowa rekordu „{0}” musi być publiczna. + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. Element „{0}” musi zezwalać na przesłanianie, ponieważ zawierający go rekord nie jest zapieczętowany. @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ zapieczętowany obiekt ToString w rekordzie + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5240,11 +5255,6 @@ Jeśli taka klasa zostanie użyta jako klasa bazowa i klasa pochodna definiuje d Pierwszy argument operacji przeciążonego operatora przesunięcia musi mieć taki sam typ co typ zawierający, a typ drugiego argumentu operacji musi być typem int - - Structs cannot contain explicit parameterless constructors - Struktury nie mogą zawierać jawnych konstruktorów bez parametrów - - Enums cannot contain explicit parameterless constructors Wyliczenia nie mogą zawierać jawnych konstruktorów bez parametrów @@ -5270,11 +5280,6 @@ Jeśli taka klasa zostanie użyta jako klasa bazowa i klasa pochodna definiuje d „{0}”: nie można odwołać się do typu przy użyciu wyrażenia. Spróbuj użyć „{1}” - - '{0}': cannot have instance property or field initializers in structs - „{0}”: nie można umieścić inicjatorów właściwości lub pola wystąpienia w strukturach - - Name of destructor must match name of type Nazwa destruktora musi być zgodna z nazwą typu diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 8fde0b712cdbf..572aacb3f73a8 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -772,6 +772,11 @@ O membro do registro '{0}' precisa ser público. + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. '{0}' precisa permitir a substituição porque o registro contentor não está selado. @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ ToString selado no registro + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5240,11 +5255,6 @@ Se tal classe for usada como uma classe base e se a classe derivada definir um d O primeiro operando de um operador de deslocamento sobrecarregado deve ser do tipo recipiente, enquanto o do segundo deve ser int - - Structs cannot contain explicit parameterless constructors - Structs não podem conter construtores explícitos sem parâmetro - - Enums cannot contain explicit parameterless constructors Enums não podem conter construtores explícitos sem parâmetros @@ -5270,11 +5280,6 @@ Se tal classe for usada como uma classe base e se a classe derivada definir um d "{0}": não é possível fazer referência a um tipo por meio de uma expressão; ao invés disso, tente "{1}" - - '{0}': cannot have instance property or field initializers in structs - '{0}': não é possível ter propriedade de instância ou inicializadores de campo em estruturas - - Name of destructor must match name of type O nome do destruidor precisa corresponder ao nome do tipo diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 95c292a3ec8fa..aa21fdc7c3aca 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -772,6 +772,11 @@ Элемент записи "{0}" должен быть открытым. + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. "{0}" должен допускать переопределение, поскольку содержащая его запись не является запечатанной. @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ запечатанный ToString в записи + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5240,11 +5255,6 @@ If such a class is used as a base class and if the deriving class defines a dest Тип первого операнда переопределенного оператора сдвига должен совпадать с вмещающим типом, а тип второго операнда должен быть int. - - Structs cannot contain explicit parameterless constructors - Структуры не могут содержать явных конструкторов без параметров. - - Enums cannot contain explicit parameterless constructors Перечисления не могут содержать явные конструкторы без параметров @@ -5270,11 +5280,6 @@ If such a class is used as a base class and if the deriving class defines a dest "{0}": невозможно сослаться на тип через выражение; попытайтесь использовать "{1}". - - '{0}': cannot have instance property or field initializers in structs - "{0}": в структуре не могут содержаться инициализаторы свойств или полей экземпляров. - - Name of destructor must match name of type Имя деструктора должно соответствовать имени типа. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index e86a57c511822..a10cfb39189c4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -772,6 +772,11 @@ '{0}' kayıt üyesi genel olmalıdır. + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. '{0}', kapsayan kayıt mühürlü olmadığından geçersiz kılmaya izin vermelidir. @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ kayıtta mühürlü ToString + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5240,11 +5255,6 @@ Bu sınıf temel sınıf olarak kullanılırsa ve türetilen sınıf bir yıkıc Tekrar yüklenen kaydırma işlecinin ilk işleneninin türü kapsayan tür ile aynı, ikinci işlenenin türü de tamsayı olmalıdır - - Structs cannot contain explicit parameterless constructors - Yapı birimleri parametresi olmayan açık oluşturucular içeremez - - Enums cannot contain explicit parameterless constructors Sabit listeleri açık parametresiz oluşturucu içeremez @@ -5270,11 +5280,6 @@ Bu sınıf temel sınıf olarak kullanılırsa ve türetilen sınıf bir yıkıc '{0}': bir türe ifade üzerinden başvurulamaz; bunun yerine '{1}' deneyin - - '{0}': cannot have instance property or field initializers in structs - '{0}': struct'larda örnek özellik veya alan başlatıcıları olamaz - - Name of destructor must match name of type Yıkıcının adı türün adıyla eşleşmelidir diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 150bb0eb61286..7c72cf99b7894 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -772,6 +772,11 @@ 记录成员“{0}”必须是公共的。 + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. “{0}”必须允许替代,因为包含的记录未密封。 @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ 记录的密封 ToString + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5245,11 +5260,6 @@ If such a class is used as a base class and if the deriving class defines a dest 重载移位运算符的第一个操作数的类型必须与包含类型相同,第二个操作数的类型必须是 int 类型 - - Structs cannot contain explicit parameterless constructors - 结构不能包含显式的无参数构造函数 - - Enums cannot contain explicit parameterless constructors 枚举不能包含显式无参数构造函数 @@ -5275,11 +5285,6 @@ If such a class is used as a base class and if the deriving class defines a dest “{0}”: 无法通过表达式引用类型;请尝试“{1}” - - '{0}': cannot have instance property or field initializers in structs - “{0}”: 结构中不能实例属性或字段初始值设定项 - - Name of destructor must match name of type 析构函数的名称必须与类型的名称匹配 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 4ee731bb84b8a..c94901e833d20 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -772,6 +772,11 @@ 記錄成員 '{0}' 必須為公用。 + + The parameterless struct constructor must be 'public'. + The parameterless struct constructor must be 'public'. + + '{0}' must allow overriding because the containing record is not sealed. '{0}' 必須允許覆寫,因為包含的記錄並未密封。 @@ -1137,6 +1142,11 @@ lambda return type + + parameterless struct constructors + parameterless struct constructors + + positional fields in records positional fields in records @@ -1152,6 +1162,11 @@ 記錄中有密封的 ToString + + struct field initializers + struct field initializers + + with on anonymous types with on anonymous types @@ -5240,11 +5255,6 @@ If such a class is used as a base class and if the deriving class defines a dest 多載移位 (Shift) 運算子的第一個運算元的類型必須和包含類型相同,而第二個運算元的類型必須是 int - - Structs cannot contain explicit parameterless constructors - 結構無法包含明確無參數的建構函式 - - Enums cannot contain explicit parameterless constructors 列舉不能包含明確的無參數建構函式 @@ -5270,11 +5280,6 @@ If such a class is used as a base class and if the deriving class defines a dest '{0}': 不可透過運算式參考類型; 請嘗試改用 '{1}' - - '{0}': cannot have instance property or field initializers in structs - '{0}': 結構中不可有執行個體屬性或欄位初始設定式 - - Name of destructor must match name of type 解構函式的名稱必須符合類型的名稱 diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs index 022e63e1cb9b1..6f34883766d19 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs @@ -13383,6 +13383,54 @@ void M(in int x) Diagnostic(ErrorCode.ERR_NotAnAttributeClass).WithArguments("System.Runtime.CompilerServices.IsReadOnlyAttribute").WithLocation(1, 1)); } + /// + /// Verify that a synthesized (and emitted) parameterless constructor can be referenced + /// in metadata for an "attribute type", even if the attribute type is a struct. Compare + /// with previous test where no parameterless constructor is emitted for the struct type. + /// + [Theory] + [InlineData( +@"#pragma warning disable 414 +namespace System.Runtime.CompilerServices +{ + public struct IsReadOnlyAttribute + { + private int F = 1; // requires synthesized parameterless .ctor + } +}")] + [InlineData( +@"namespace System.Runtime.CompilerServices +{ + public struct IsReadOnlyAttribute + { + // explicit parameterless .ctor + public IsReadOnlyAttribute() { } + } +}")] + public void WellKnownTypeAsStruct_ParameterlessConstructor_IsReadOnlyAttribute(string sourceAttribute) + { + var sourceA = +@"public class A +{ + public static void M(in int i) + { + System.Console.WriteLine(i); + } +}"; + var sourceB = +@"class B +{ + static void Main() + { + int i = 42; + A.M(in i); + } +}"; + var comp = CreateCompilation(new[] { sourceAttribute, sourceA }, parseOptions: TestOptions.RegularPreview); + var refA = comp.EmitToImageReference(); + CompileAndVerify(sourceB, references: new[] { refA }, expectedOutput: "42"); + } + [Fact] public void TestObsoleteOnPropertyAccessorUsedInNameofAndXmlDocComment() { diff --git a/src/Compilers/CSharp/Test/Emit/Emit/NoPiaEmbedTypes.cs b/src/Compilers/CSharp/Test/Emit/Emit/NoPiaEmbedTypes.cs index 542afad36bf62..3eab1fd08603b 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/NoPiaEmbedTypes.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/NoPiaEmbedTypes.cs @@ -1309,7 +1309,7 @@ public void M6(ITest26 x) Assert.Equal(3, test9.GetMembers().Length); Assert.Same(f1, test9.GetMembers()[0]); Assert.Same(f2, test9.GetMembers()[1]); - Assert.True(((MethodSymbol)test9.GetMembers()[2]).IsDefaultValueTypeConstructor()); + Assert.True(((MethodSymbol)test9.GetMembers()[2]).IsDefaultValueTypeConstructor(requireZeroInit: true)); var test10 = module.GlobalNamespace.GetTypeMembers("Test10").Single(); Assert.Equal(TypeKind.Struct, test10.TypeKind); @@ -1333,7 +1333,7 @@ public void M6(ITest26 x) Assert.Equal(System.Runtime.InteropServices.UnmanagedType.U4, f4.MarshallingType); Assert.False(f4.IsNotSerialized); - Assert.True(((MethodSymbol)test10.GetMembers()[2]).IsDefaultValueTypeConstructor()); + Assert.True(((MethodSymbol)test10.GetMembers()[2]).IsDefaultValueTypeConstructor(requireZeroInit: true)); var test11 = (PENamedTypeSymbol)module.GlobalNamespace.GetTypeMembers("Test11").Single(); Assert.Equal(TypeKind.Delegate, test11.TypeKind); diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowDiagnosticTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowDiagnosticTests.cs index 7c7bac7476864..5002210ce5735 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowDiagnosticTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowDiagnosticTests.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; -using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -925,20 +924,19 @@ public static void Main() } } "; - CreateCompilation(program).VerifyDiagnostics( - // (4,7): error CS0573: 'A': cannot have instance property or field initializers in structs - // A a = new A(); // CS8036 - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "a").WithArguments("A").WithLocation(4, 7), - // (4,7): error CS0523: Struct member 'A.a' of type 'A' causes a cycle in the struct layout - // A a = new A(); // CS8036 - Diagnostic(ErrorCode.ERR_StructLayoutCycle, "a").WithArguments("A.a", "A").WithLocation(4, 7), - // (7,11): warning CS0219: The variable 'a' is assigned but its value is never used - // A a = new A(); - Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "a").WithArguments("a").WithLocation(7, 11), - // (4,7): warning CS0169: The field 'A.a' is never used - // A a = new A(); // CS8036 - Diagnostic(ErrorCode.WRN_UnreferencedField, "a").WithArguments("A.a").WithLocation(4, 7) - ); + CreateCompilation(program, parseOptions: TestOptions.Regular9).VerifyDiagnostics( + // (4,7): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // A a = new A(); // CS8036 + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "a").WithArguments("struct field initializers", "10.0").WithLocation(4, 7), + // (4,7): error CS0523: Struct member 'A.a' of type 'A' causes a cycle in the struct layout + // A a = new A(); // CS8036 + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "a").WithArguments("A.a", "A").WithLocation(4, 7), + // (7,11): warning CS0219: The variable 'a' is assigned but its value is never used + // A a = new A(); + Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "a").WithArguments("a").WithLocation(7, 11), + // (4,7): warning CS0414: The field 'A.a' is assigned but its value is never used + // A a = new A(); // CS8036 + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "a").WithArguments("A.a").WithLocation(4, 7)); } [WorkItem(542356, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542356")] @@ -1269,15 +1267,17 @@ public void AutoPropInitialization2() public S(int i) {} }"; - var comp = CreateCompilation(text); + var comp = CreateCompilation(text, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (3,16): error CS0573: 'S': cannot have instance property or field initializers in structs - // public int P { get; set; } = 1; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "P").WithArguments("S").WithLocation(3, 16), - // (5,20): error CS0573: 'S': cannot have instance property or field initializers in structs - // public decimal R { get; } = 300; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "R").WithArguments("S").WithLocation(5, 20) -); + // (3,16): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // public int P { get; set; } = 1; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "P").WithArguments("struct field initializers", "10.0").WithLocation(3, 16), + // (5,20): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // public decimal R { get; } = 300; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "R").WithArguments("struct field initializers", "10.0").WithLocation(5, 20)); + + comp = CreateCompilation(text); + comp.VerifyDiagnostics(); } [Fact] @@ -1295,12 +1295,14 @@ public S(int p) } }"; - var comp = CreateCompilation(text); + var comp = CreateCompilation(text, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (5,20): error CS0573: 'S': cannot have instance property or field initializers in structs - // public decimal R { get; } = 300; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "R").WithArguments("S").WithLocation(5, 20) - ); + // (5,20): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // public decimal R { get; } = 300; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "R").WithArguments("struct field initializers", "10.0").WithLocation(5, 20)); + + comp = CreateCompilation(text); + comp.VerifyDiagnostics(); } [Fact] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ConstantTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ConstantTests.cs index a963782c19e28..699fe4fa1d61a 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ConstantTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ConstantTests.cs @@ -181,15 +181,14 @@ static void Main(string[] args) } } "; - var comp = CreateCompilationWithMscorlib45(source); + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (10,12): error CS0568: Structs cannot contain explicit parameterless constructors - // public S2() - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "S2").WithLocation(10, 12), - // (26,28): error CS1736: Default parameter value for 's' must be a compile-time constant - // static void Goo(S2 s = new S2()) - Diagnostic(ErrorCode.ERR_DefaultValueMustBeConstant, "new S2()").WithArguments("s").WithLocation(26, 28) -); + // (10,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // public S2() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S2").WithArguments("parameterless struct constructors", "10.0").WithLocation(10, 12), + // (26,28): error CS1736: Default parameter value for 's' must be a compile-time constant + // static void Goo(S2 s = new S2()) + Diagnostic(ErrorCode.ERR_DefaultValueMustBeConstant, "new S2()").WithArguments("s").WithLocation(26, 28)); } [Fact] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs index b9e926d117d23..a68dccf4aca19 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs @@ -961,10 +961,11 @@ struct S object F1; S(object obj) : this() { } }"; - verify(source, expectedAnalyzedKeys: new[] { ".ctor" }, + // PROTOTYPE: NullableWalker.Scan() overwrites initial field state when calling EnterParameter(methodThisParameter). + verify(source, expectedAnalyzedKeys: new[] { ".ctor" }/*, // (6,5): warning CS8618: Non-nullable field 'F1' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. // S(object obj) : this() { } - Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "F1").WithLocation(6, 5)); + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "F1").WithLocation(6, 5)*/); source = @"#pragma warning disable 169 diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index fa06937b150cd..5e4c044ae1c62 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -81270,11 +81270,14 @@ static void F() z.F/*T:object!*/.ToString(); } }"; - var comp = CreateCompilation(source, options: WithNullableEnable()); + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9, options: WithNullableEnable()); comp.VerifyDiagnostics( - // (5,14): error CS0568: Structs cannot contain explicit parameterless constructors + // (5,14): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // internal S() { F = default!; } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S").WithArguments("parameterless struct constructors", "10.0").WithLocation(5, 14), + // (5,14): error CS8938: The parameterless struct constructor must be 'public'. // internal S() { F = default!; } - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "S").WithLocation(5, 14), + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "S").WithLocation(5, 14), // (13,9): warning CS8602: Dereference of a possibly null reference. // x.F/*T:object?*/.ToString(); // 1 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x.F").WithLocation(13, 9)); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs index 75948efc9befc..7cd09257b8b16 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs @@ -963,26 +963,93 @@ public S(int i) : base() { } } [Fact] - public void TypeDeclaration_NoParameterlessConstructor() + public void TypeDeclaration_ParameterlessConstructor_01() { - var src = @" -public record struct S + var src = +@"record struct S0(); +record struct S1; +record struct S2 +{ + public S2() { } +}"; + + var comp = CreateCompilation(src, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (1,8): error CS8773: Feature 'record structs' is not available in C# 9.0. Please use language version 10.0 or greater. + // record struct S0(); + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "struct").WithArguments("record structs", "10.0").WithLocation(1, 8), + // (2,8): error CS8773: Feature 'record structs' is not available in C# 9.0. Please use language version 10.0 or greater. + // record struct S1; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "struct").WithArguments("record structs", "10.0").WithLocation(2, 8), + // (3,8): error CS8773: Feature 'record structs' is not available in C# 9.0. Please use language version 10.0 or greater. + // record struct S2 + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "struct").WithArguments("record structs", "10.0").WithLocation(3, 8), + // (5,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // public S2() { } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S2").WithArguments("parameterless struct constructors", "10.0").WithLocation(5, 12)); + + var verifier = CompileAndVerify(src); + verifier.VerifyIL("S0..ctor()", +@"{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + verifier.VerifyMissing("S1..ctor()"); + verifier.VerifyIL("S2..ctor()", +@"{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + } + + [Fact] + public void TypeDeclaration_ParameterlessConstructor_02() + { + var src = +@"record struct S1 { - public S() { } + S1() { } } -"; - // This will be allowed in C# 10 - // Tracking issue https://github.com/dotnet/roslyn/issues/52240 - var comp = CreateCompilation(src); +record struct S2 +{ + internal S2() { } +}"; + + var comp = CreateCompilation(src, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (4,12): error CS0568: Structs cannot contain explicit parameterless constructors - // public S() { } - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "S").WithLocation(4, 12) - ); + // (1,8): error CS8773: Feature 'record structs' is not available in C# 9.0. Please use language version 10.0 or greater. + // record struct S1 + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "struct").WithArguments("record structs", "10.0").WithLocation(1, 8), + // (3,5): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // S1() { } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S1").WithArguments("parameterless struct constructors", "10.0").WithLocation(3, 5), + // (3,5): error CS8938: The parameterless struct constructor must be 'public'. + // S1() { } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "S1").WithLocation(3, 5), + // (5,8): error CS8773: Feature 'record structs' is not available in C# 9.0. Please use language version 10.0 or greater. + // record struct S2 + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "struct").WithArguments("record structs", "10.0").WithLocation(5, 8), + // (7,14): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // internal S2() { } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S2").WithArguments("parameterless struct constructors", "10.0").WithLocation(7, 14), + // (7,14): error CS8938: The parameterless struct constructor must be 'public'. + // internal S2() { } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "S2").WithLocation(7, 14)); + + comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (3,5): error CS8918: The parameterless struct constructor must be 'public'. + // S1() { } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "S1").WithLocation(3, 5), + // (7,14): error CS8918: The parameterless struct constructor must be 'public'. + // internal S2() { } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "S2").WithLocation(7, 14)); } [Fact] - public void TypeDeclaration_NoInstanceInitializers() + public void TypeDeclaration_InstanceInitializers() { var src = @" public record struct S @@ -991,19 +1058,26 @@ public record struct S public int Property { get; set; } = 43; } "; - // This will be allowed in C# 10, or we need to improve the message - // Tracking issue https://github.com/dotnet/roslyn/issues/52240 - var comp = CreateCompilation(src); + + var comp = CreateCompilation(src, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (4,16): error CS0573: 'S': cannot have instance property or field initializers in structs + // (2,15): error CS8773: Feature 'record structs' is not available in C# 9.0. Please use language version 10.0 or greater. + // public record struct S + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "struct").WithArguments("record structs", "10.0").WithLocation(2, 15), + // (4,16): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. // public int field = 42; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "field").WithArguments("S").WithLocation(4, 16), - // (5,16): error CS0573: 'S': cannot have instance property or field initializers in structs + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "field").WithArguments("struct field initializers", "10.0").WithLocation(4, 16), + // (5,16): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. // public int Property { get; set; } = 43; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "Property").WithArguments("S").WithLocation(5, 16) - ); + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "Property").WithArguments("struct field initializers", "10.0").WithLocation(5, 16)); + + comp = CreateCompilation(src); + comp.VerifyDiagnostics(); } + // PROTOTYPE: Verify initializers are executed from synthesized and explicit parameterless constructors. + // PROTOTYPE: Verify explicit parameterless constructor calls 'this(...)' for primary constructor. + [Fact] public void TypeDeclaration_NoDestructor() { @@ -1115,18 +1189,9 @@ partial record struct S3(); // (6,25): error CS8863: Only a single record partial declaration may have a parameter list // partial record struct S2(); Diagnostic(ErrorCode.ERR_MultipleRecordParameterLists, "()").WithLocation(6, 25), - // (6,25): error CS0568: Structs cannot contain explicit parameterless constructors - // partial record struct S2(); - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "()").WithLocation(6, 25), - // (8,25): error CS0568: Structs cannot contain explicit parameterless constructors - // partial record struct S3(); - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "()").WithLocation(8, 25), // (9,25): error CS8863: Only a single record partial declaration may have a parameter list // partial record struct S3(); - Diagnostic(ErrorCode.ERR_MultipleRecordParameterLists, "()").WithLocation(9, 25), - // (9,25): error CS0568: Structs cannot contain explicit parameterless constructors - // partial record struct S3(); - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "()").WithLocation(9, 25) + Diagnostic(ErrorCode.ERR_MultipleRecordParameterLists, "()").WithLocation(9, 25) ); } @@ -1620,8 +1685,6 @@ .maxstack 2 [Fact] public void RecordProperties_01_EmptyParameterList() { - // We will allow declaring parameterless constructors - // Tracking issue https://github.com/dotnet/roslyn/issues/52240 var src = @" using System; record struct C() @@ -1633,11 +1696,7 @@ public static void Main() Console.Write(c.Z); } }"; - CreateCompilation(src).VerifyEmitDiagnostics( - // (3,16): error CS0568: Structs cannot contain explicit parameterless constructors - // record struct C() - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "()").WithLocation(3, 16) - ); + CreateCompilation(src).VerifyEmitDiagnostics(); } [Fact] @@ -2857,7 +2916,7 @@ public void StaticRecordWithConstructorAndDestructor() var text = @" static record struct R(int I) { - R() : this(0) { } + public R() : this(0) { } ~R() { } } "; @@ -2866,9 +2925,6 @@ static record struct R(int I) // (2,22): error CS0106: The modifier 'static' is not valid for this item // static record struct R(int I) Diagnostic(ErrorCode.ERR_BadMemberFlag, "R").WithArguments("static").WithLocation(2, 22), - // (4,5): error CS0568: Structs cannot contain explicit parameterless constructors - // R() : this(0) { } - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "R").WithLocation(4, 5), // (5,6): error CS0575: Only class types can contain destructors // ~R() { } Diagnostic(ErrorCode.ERR_OnlyClassesCanContainDestructors, "R").WithArguments("R.~R()").WithLocation(5, 6) @@ -2935,11 +2991,7 @@ record struct C() int Property { get; set; } = 42; }"; var comp = CreateCompilation(src); - comp.VerifyDiagnostics( - // (2,16): error CS0568: Structs cannot contain explicit parameterless constructors - // record struct C() - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "()").WithLocation(2, 16) - ); + comp.VerifyDiagnostics(); } [Fact] @@ -3388,9 +3440,6 @@ static void Main() "; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (2,16): error CS0568: Structs cannot contain explicit parameterless constructors - // record struct C() - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "()").WithLocation(2, 16), // (8,19): error CS1061: 'C' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?) // case C(): Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "()").WithArguments("C", "Deconstruct").WithLocation(8, 19), diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs new file mode 100644 index 0000000000000..16ba48f44c369 --- /dev/null +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs @@ -0,0 +1,1861 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics +{ + public class StructConstructorTests : CSharpTestBase + { + [CombinatorialData] + [Theory] + public void PublicParameterlessConstructor(bool useCompilationReference) + { + var sourceA = +@"public struct S +{ + public readonly bool Initialized; + public S() { Initialized = true; } +}"; + var comp = CreateCompilation(sourceA, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (4,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // public S() { Initialized = true; } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S").WithArguments("parameterless struct constructors", "10.0").WithLocation(4, 12)); + + comp = CreateCompilation(sourceA); + comp.VerifyDiagnostics(); + var refA = AsReference(comp, useCompilationReference); + + var sourceB = +@"using System; +class Program +{ + static T CreateNew() where T : new() => new T(); + static T CreateStruct() where T : struct => new T(); + static void Main() + { + Console.WriteLine(new S().Initialized); + Console.WriteLine(CreateNew>().Initialized); + Console.WriteLine(CreateStruct>().Initialized); + Console.WriteLine(CreateStruct>().Initialized); + Console.WriteLine(CreateNew>().Initialized); + Console.WriteLine(new S().Initialized); + } +}"; + bool secondCall = ExecutionConditionUtil.IsCoreClr; // .NET Framework ignores constructor in second call to Activator.CreateInstance(). + CompileAndVerify(sourceB, references: new[] { refA }, expectedOutput: +$@"True +True +{secondCall} +True +{secondCall} +True"); + } + + [Fact] + public void NonPublicParameterlessConstructor_01() + { + var source = +@"public struct A0 { public A0() { } } +public struct A1 { internal A1() { } } +public struct A2 { private A2() { } } +public struct A3 { A3() { } } + +internal struct B0 { public B0() { } } +internal struct B1 { internal B1() { } } +internal struct B2 { private B2() { } } +internal struct B3 { B3() { } } + +public class C +{ + internal protected struct C0 { public C0() { } } + internal protected struct C1 { internal C1() { } } + internal protected struct C2 { private C2() { } } + internal protected struct C3 { C3() { } } + + protected struct D0 { public D0() { } } + protected struct D1 { internal D1() { } } + protected struct D2 { private D2() { } } + protected struct D3 { D3() { } } + + private protected struct E0 { public E0() { } } + private protected struct E1 { internal E1() { } } + private protected struct E2 { private E2() { } } + private protected struct E3 { E3() { } } + + private struct F0 { public F0() { } } + private struct F1 { internal F1() { } } + private struct F2 { private F2() { } } + private struct F3 { F3() { } } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (2,29): error CS8938: The parameterless struct constructor must be 'public'. + // public struct A1 { internal A1() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "A1").WithLocation(2, 29), + // (3,28): error CS8938: The parameterless struct constructor must be 'public'. + // public struct A2 { private A2() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "A2").WithLocation(3, 28), + // (4,20): error CS8938: The parameterless struct constructor must be 'public'. + // public struct A3 { A3() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "A3").WithLocation(4, 20), + // (7,31): error CS8938: The parameterless struct constructor must be 'public'. + // internal struct B1 { internal B1() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "B1").WithLocation(7, 31), + // (8,30): error CS8938: The parameterless struct constructor must be 'public'. + // internal struct B2 { private B2() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "B2").WithLocation(8, 30), + // (9,22): error CS8938: The parameterless struct constructor must be 'public'. + // internal struct B3 { B3() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "B3").WithLocation(9, 22), + // (14,45): error CS8938: The parameterless struct constructor must be 'public'. + // internal protected struct C1 { internal C1() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "C1").WithLocation(14, 45), + // (15,44): error CS8938: The parameterless struct constructor must be 'public'. + // internal protected struct C2 { private C2() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "C2").WithLocation(15, 44), + // (16,36): error CS8938: The parameterless struct constructor must be 'public'. + // internal protected struct C3 { C3() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "C3").WithLocation(16, 36), + // (19,36): error CS8938: The parameterless struct constructor must be 'public'. + // protected struct D1 { internal D1() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "D1").WithLocation(19, 36), + // (20,35): error CS8938: The parameterless struct constructor must be 'public'. + // protected struct D2 { private D2() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "D2").WithLocation(20, 35), + // (21,27): error CS8938: The parameterless struct constructor must be 'public'. + // protected struct D3 { D3() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "D3").WithLocation(21, 27), + // (24,44): error CS8938: The parameterless struct constructor must be 'public'. + // private protected struct E1 { internal E1() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "E1").WithLocation(24, 44), + // (25,43): error CS8938: The parameterless struct constructor must be 'public'. + // private protected struct E2 { private E2() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "E2").WithLocation(25, 43), + // (26,35): error CS8938: The parameterless struct constructor must be 'public'. + // private protected struct E3 { E3() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "E3").WithLocation(26, 35), + // (29,34): error CS8938: The parameterless struct constructor must be 'public'. + // private struct F1 { internal F1() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "F1").WithLocation(29, 34), + // (30,33): error CS8938: The parameterless struct constructor must be 'public'. + // private struct F2 { private F2() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "F2").WithLocation(30, 33), + // (31,25): error CS8938: The parameterless struct constructor must be 'public'. + // private struct F3 { F3() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "F3").WithLocation(31, 25)); + } + + [InlineData("assembly")] + [InlineData("private")] + [Theory] + public void NonPublicParameterlessConstructor_02(string accessibility) + { + var sourceA = +$@".class public sealed S extends [mscorlib]System.ValueType +{{ + .method {accessibility} hidebysig specialname rtspecialname instance void .ctor() cil managed {{ ret }} +}}"; + var refA = CompileIL(sourceA); + + var sourceB = +@"using System; +class Program +{ + static T CreateNew() where T : new() => new T(); + static T CreateStruct1() where T : struct => new T(); + static T CreateStruct2() where T : struct => CreateNew(); + static string Invoke(Func f) + { + object obj; + try + { + obj = f(); + } + catch (Exception e) + { + obj = e; + } + return obj.GetType().FullName; + } + static void Main() + { + Console.WriteLine(Invoke(() => new S())); + Console.WriteLine(Invoke(() => CreateNew())); + Console.WriteLine(Invoke(() => CreateStruct1())); + Console.WriteLine(Invoke(() => CreateStruct2())); + } +}"; + CompileAndVerify(sourceB, references: new[] { refA }, expectedOutput: +@"S +System.MissingMethodException +System.MissingMethodException +System.MissingMethodException"); + } + + [InlineData("internal")] + [InlineData("private")] + [Theory] + public void NonPublicParameterlessConstructor_03(string accessibility) + { + var sourceA = +$@"public struct S +{{ + {accessibility} + S() {{ }} +}}"; + var comp = CreateCompilation(sourceA); + comp.VerifyDiagnostics( + // (4,5): error CS8938: The parameterless struct constructor must be 'public'. + // S() { } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "S").WithLocation(4, 5)); + + var refA = comp.ToMetadataReference(); + + var sourceB = +@"class Program +{ + static T CreateNew() where T : new() => new T(); + static T CreateStruct() where T : struct => new T(); + static void Main() + { + _ = new S(); + _ = CreateNew(); + _ = CreateStruct(); + } +}"; + var expectedDiagnostics = new[] + { + // (7,17): error CS0122: 'S.S()' is inaccessible due to its protection level + // _ = new S(); + Diagnostic(ErrorCode.ERR_BadAccess, "S").WithArguments("S.S()").WithLocation(7, 17), + // (8,13): error CS0310: 'S' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'T' in the generic type or method 'Program.CreateNew()' + // _ = CreateNew(); + Diagnostic(ErrorCode.ERR_NewConstraintNotSatisfied, "CreateNew").WithArguments("Program.CreateNew()", "T", "S").WithLocation(8, 13), + }; + + comp = CreateCompilation(sourceB, references: new[] { refA }, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics(expectedDiagnostics); + + comp = CreateCompilation(sourceB, references: new[] { refA }); + comp.VerifyDiagnostics(expectedDiagnostics); + } + + [InlineData("internal")] + [InlineData("protected")] + [InlineData("protected internal")] + [InlineData("private")] + [InlineData("private protected")] + [Theory] + public void PublicConstructorPrivateStruct_NewConstraint(string accessibility) + { + var sourceA = +$@"partial class Program +{{ + {accessibility} struct S + {{ + public readonly bool Initialized; + public S() + {{ + Initialized = true; + }} + }} +}}"; + var sourceB = +@"using System; +partial class Program +{ + static T CreateNew() where T : new() => new T(); + static void Main() + { + Console.WriteLine(CreateNew().Initialized); + } +}"; + CompileAndVerify(new[] { sourceA, sourceB }, expectedOutput: "True"); + } + + [Fact] + public void ThisInitializer_01() + { + var source = +@"using static System.Console; +using static Program; +struct S1 +{ + internal int Value; + public S1() { Value = Two(); } + internal S1(object obj) : this() { } +} +struct S2 +{ + internal int Value; + public S2() : this(null) { } + S2(object obj) { Value = Three(); } +} +class Program +{ + internal static int Two() { WriteLine(""Two()""); return 2; } + internal static int Three() { WriteLine(""Three()""); return 3; } + static void Main() + { + WriteLine($""new S1().Value: {new S1().Value}""); + WriteLine($""new S1(null).Value: {new S1(null).Value}""); + WriteLine($""new S2().Value: {new S2().Value}""); + } +}"; + + var verifier = CompileAndVerify(source, expectedOutput: +@"Two() +new S1().Value: 2 +Two() +new S1(null).Value: 2 +Three() +new S2().Value: 3 +"); + + verifier.VerifyIL("S1..ctor()", +@"{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""int Program.Two()"" + IL_0006: stfld ""int S1.Value"" + IL_000b: ret +}"); + verifier.VerifyIL("S1..ctor(object)", +@"{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""S1..ctor()"" + IL_0006: ret +}"); + verifier.VerifyIL("S2..ctor()", +@"{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: call ""S2..ctor(object)"" + IL_0007: ret +}"); + verifier.VerifyIL("S2..ctor(object)", +@"{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""int Program.Three()"" + IL_0006: stfld ""int S2.Value"" + IL_000b: ret +}"); + } + + [Fact] + public void ThisInitializer_02() + { + var source = +@"using static System.Console; +using static Program; +struct S1 +{ + internal int Value; + public S1() { Value = Two(); } + internal S1(object obj) : this() { Value = Three(); } +} +struct S2 +{ + internal int Value; + public S2() : this(null) { Value = Two(); } + S2(object obj) { Value = Three(); } +} +class Program +{ + internal static int Two() { WriteLine(""Two()""); return 2; } + internal static int Three() { WriteLine(""Three()""); return 3; } + static void Main() + { + WriteLine($""new S1().Value: {new S1().Value}""); + WriteLine($""new S1(null).Value: {new S1(null).Value}""); + WriteLine($""new S2().Value: {new S2().Value}""); + } +}"; + + var verifier = CompileAndVerify(source, expectedOutput: +@"Two() +new S1().Value: 2 +Two() +Three() +new S1(null).Value: 3 +Three() +Two() +new S2().Value: 2 +"); + + verifier.VerifyIL("S1..ctor()", +@"{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""int Program.Two()"" + IL_0006: stfld ""int S1.Value"" + IL_000b: ret +}"); + verifier.VerifyIL("S1..ctor(object)", +@"{ + // Code size 18 (0x12) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""S1..ctor()"" + IL_0006: ldarg.0 + IL_0007: call ""int Program.Three()"" + IL_000c: stfld ""int S1.Value"" + IL_0011: ret +}"); + verifier.VerifyIL("S2..ctor()", +@"{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: call ""S2..ctor(object)"" + IL_0007: ldarg.0 + IL_0008: call ""int Program.Two()"" + IL_000d: stfld ""int S2.Value"" + IL_0012: ret +}"); + verifier.VerifyIL("S2..ctor(object)", +@"{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""int Program.Three()"" + IL_0006: stfld ""int S2.Value"" + IL_000b: ret +}"); + } + + [Fact] + public void ThisInitializer_03() + { + var source = +@"using static System.Console; +using static Program; +struct S0 +{ + internal int Value = One(); + internal S0(object obj) : this() { } +} +struct S1 +{ + internal int Value = One(); + public S1() { } + internal S1(object obj) : this() { } +} +struct S2 +{ + internal int Value = One(); + public S2() : this(null) { } + S2(object obj) { } +} +class Program +{ + internal static int One() { WriteLine(""One()""); return 1; } + static void Main() + { + WriteLine($""new S0().Value: {new S0().Value}""); + WriteLine($""new S0(null).Value: {new S0(null).Value}""); + WriteLine($""new S1().Value: {new S1().Value}""); + WriteLine($""new S1(null).Value: {new S1(null).Value}""); + WriteLine($""new S2().Value: {new S2().Value}""); + } +}"; + + // PROTOTYPE: S0(object) should set Value after initobj, not before, + // and should report: new S0(null).Value: 1. + var verifier = CompileAndVerify(source, expectedOutput: +@"new S0().Value: 0 +One() +new S0(null).Value: 0 +One() +new S1().Value: 1 +One() +new S1(null).Value: 1 +One() +new S2().Value: 1 +"); + + verifier.VerifyMissing("S0..ctor()"); + verifier.VerifyIL("S0..ctor(object)", +@"{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""int Program.One()"" + IL_0006: stfld ""int S0.Value"" + IL_000b: ldarg.0 + IL_000c: initobj ""S0"" + IL_0012: ret +}"); + verifier.VerifyIL("S1..ctor()", +@"{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""int Program.One()"" + IL_0006: stfld ""int S1.Value"" + IL_000b: ret +}"); + verifier.VerifyIL("S1..ctor(object)", +@"{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""S1..ctor()"" + IL_0006: ret +}"); + verifier.VerifyIL("S2..ctor()", +@"{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: call ""S2..ctor(object)"" + IL_0007: ret +}"); + verifier.VerifyIL("S2..ctor(object)", +@"{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""int Program.One()"" + IL_0006: stfld ""int S2.Value"" + IL_000b: ret +}"); + } + + [Fact] + public void ThisInitializer_04() + { + var source = +@"using static System.Console; +using static Program; +struct S0 +{ + internal int Value = One(); + internal S0(object obj) : this() { Value = Two(); } +} +struct S1 +{ + internal int Value = One(); + public S1() { Value = Two(); } + internal S1(object obj) : this() { Value = Three(); } +} +struct S2 +{ + internal int Value = One(); + public S2() : this(null) { Value = Two(); } + S2(object obj) { Value = Three(); } +} +class Program +{ + internal static int One() { WriteLine(""One()""); return 1; } + internal static int Two() { WriteLine(""Two()""); return 2; } + internal static int Three() { WriteLine(""Three()""); return 3; } + static void Main() + { + WriteLine($""new S0().Value: {new S0().Value}""); + WriteLine($""new S0(null).Value: {new S0(null).Value}""); + WriteLine($""new S1().Value: {new S1().Value}""); + WriteLine($""new S1(null).Value: {new S1(null).Value}""); + WriteLine($""new S2().Value: {new S2().Value}""); + } +}"; + + var verifier = CompileAndVerify(source, expectedOutput: +@"new S0().Value: 0 +One() +Two() +new S0(null).Value: 2 +One() +Two() +new S1().Value: 2 +One() +Two() +Three() +new S1(null).Value: 3 +One() +Three() +Two() +new S2().Value: 2 +"); + + verifier.VerifyMissing("S0..ctor()"); + // PROTOTYPE: S0(object) should set Value twice after initobj, not once before, once after. + verifier.VerifyIL("S0..ctor(object)", +@"{ + // Code size 30 (0x1e) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""int Program.One()"" + IL_0006: stfld ""int S0.Value"" + IL_000b: ldarg.0 + IL_000c: initobj ""S0"" + IL_0012: ldarg.0 + IL_0013: call ""int Program.Two()"" + IL_0018: stfld ""int S0.Value"" + IL_001d: ret +}"); + verifier.VerifyIL("S1..ctor()", +@"{ + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""int Program.One()"" + IL_0006: stfld ""int S1.Value"" + IL_000b: ldarg.0 + IL_000c: call ""int Program.Two()"" + IL_0011: stfld ""int S1.Value"" + IL_0016: ret +}"); + verifier.VerifyIL("S1..ctor(object)", +@"{ + // Code size 18 (0x12) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""S1..ctor()"" + IL_0006: ldarg.0 + IL_0007: call ""int Program.Three()"" + IL_000c: stfld ""int S1.Value"" + IL_0011: ret +}"); + verifier.VerifyIL("S2..ctor()", +@"{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: call ""S2..ctor(object)"" + IL_0007: ldarg.0 + IL_0008: call ""int Program.Two()"" + IL_000d: stfld ""int S2.Value"" + IL_0012: ret +}"); + verifier.VerifyIL("S2..ctor(object)", +@"{ + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""int Program.One()"" + IL_0006: stfld ""int S2.Value"" + IL_000b: ldarg.0 + IL_000c: call ""int Program.Three()"" + IL_0011: stfld ""int S2.Value"" + IL_0016: ret +}"); + } + + /// + /// Initializers with default values to verify that the decision whether + /// to execute an initializer is independent of the initializer value. + /// + [Fact] + public void ThisInitializer_05() + { + var source = +@"using static System.Console; +using static Program; +struct S0 +{ + internal int Value = 0; + internal S0(object obj) : this() { Value = Two(); } +} +struct S1 +{ + internal int Value = 0; + public S1() { Value = Two(); } + internal S1(object obj) : this() { Value = Three(); } +} +struct S2 +{ + internal int Value = 0; + public S2() : this(null) { Value = Two(); } + S2(object obj) { Value = Three(); } +} +class Program +{ + internal static int Two() { WriteLine(""Two()""); return 2; } + internal static int Three() { WriteLine(""Three()""); return 3; } + static void Main() + { + WriteLine($""new S0().Value: {new S0().Value}""); + WriteLine($""new S0(null).Value: {new S0(null).Value}""); + WriteLine($""new S1().Value: {new S1().Value}""); + WriteLine($""new S1(null).Value: {new S1(null).Value}""); + WriteLine($""new S2().Value: {new S2().Value}""); + } +}"; + + var verifier = CompileAndVerify(source, expectedOutput: +@"new S0().Value: 0 +Two() +new S0(null).Value: 2 +Two() +new S1().Value: 2 +Two() +Three() +new S1(null).Value: 3 +Three() +Two() +new S2().Value: 2 +"); + + verifier.VerifyMissing("S0..ctor()"); + // PROTOTYPE: S0(object) should set Value twice after initobj, not once before, once after. + verifier.VerifyIL("S0..ctor(object)", +@"{ + // Code size 26 (0x1a) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld ""int S0.Value"" + IL_0007: ldarg.0 + IL_0008: initobj ""S0"" + IL_000e: ldarg.0 + IL_000f: call ""int Program.Two()"" + IL_0014: stfld ""int S0.Value"" + IL_0019: ret +}"); + verifier.VerifyIL("S1..ctor()", +@"{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld ""int S1.Value"" + IL_0007: ldarg.0 + IL_0008: call ""int Program.Two()"" + IL_000d: stfld ""int S1.Value"" + IL_0012: ret +}"); + verifier.VerifyIL("S1..ctor(object)", +@"{ + // Code size 18 (0x12) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""S1..ctor()"" + IL_0006: ldarg.0 + IL_0007: call ""int Program.Three()"" + IL_000c: stfld ""int S1.Value"" + IL_0011: ret +}"); + verifier.VerifyIL("S2..ctor()", +@"{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: call ""S2..ctor(object)"" + IL_0007: ldarg.0 + IL_0008: call ""int Program.Two()"" + IL_000d: stfld ""int S2.Value"" + IL_0012: ret +}"); + verifier.VerifyIL("S2..ctor(object)", +@"{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld ""int S2.Value"" + IL_0007: ldarg.0 + IL_0008: call ""int Program.Three()"" + IL_000d: stfld ""int S2.Value"" + IL_0012: ret +}"); + } + + [Fact] + public void ThisInitializer_06() + { + var source = +@"using static System.Console; +using static Program; +struct S0 +{ + internal int Value; + public S0(params object[] args) { Value = Two(); } + public S0(int i) : this() { } +} +struct S1 +{ + internal int Value; + public S1(object obj = null) { Value = Two(); } + public S1(int i) : this() { } +} +class Program +{ + internal static int Two() { WriteLine(""Two()""); return 2; } + static void Main() + { + WriteLine($""new S0().Value: {new S0().Value}""); + WriteLine($""new S0(0).Value: {new S0(0).Value}""); + WriteLine($""new S0((object)0).Value: {new S0((object)0).Value}""); + WriteLine($""new S1().Value: {new S1().Value}""); + WriteLine($""new S1(1).Value: {new S1(1).Value}""); + WriteLine($""new S1((object)1).Value: {new S1((object)1).Value}""); + } +}"; + + var verifier = CompileAndVerify(source, expectedOutput: +@"new S0().Value: 0 +new S0(0).Value: 0 +Two() +new S0((object)0).Value: 2 +new S1().Value: 0 +new S1(1).Value: 0 +Two() +new S1((object)1).Value: 2 +"); + + verifier.VerifyMissing("S0..ctor()"); + verifier.VerifyIL("S0..ctor(int)", +@"{ + // Code size 8 (0x8) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: initobj ""S0"" + IL_0007: ret +}"); + verifier.VerifyMissing("S1..ctor()"); + verifier.VerifyIL("S1..ctor(int)", +@"{ + // Code size 8 (0x8) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: initobj ""S1"" + IL_0007: ret +}"); + } + + [Fact] + public void FieldInitializers_None() + { + var source = +@"#pragma warning disable 649 +using System; +struct S0 +{ + object X; + object Y; + public override string ToString() => (X, Y).ToString(); +} +struct S1 +{ + object X; + object Y; + public S1() { Y = 1; } + public override string ToString() => (X, Y).ToString(); +} +struct S2 +{ + object X; + object Y; + public S2(object y) { Y = y; } + public override string ToString() => (X, Y).ToString(); +} +class Program +{ + static void Main() + { + Console.WriteLine(new S0()); + Console.WriteLine(new S1()); + Console.WriteLine(new S2()); + } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (13,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // public S1() { Y = 1; } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S1").WithArguments("parameterless struct constructors", "10.0").WithLocation(13, 12), + // (13,12): error CS0171: Field 'S1.X' must be fully assigned before control is returned to the caller + // public S1() { Y = 1; } + Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.X").WithLocation(13, 12), + // (20,12): error CS0171: Field 'S2.X' must be fully assigned before control is returned to the caller + // public S2(object y) { Y = y; } + Diagnostic(ErrorCode.ERR_UnassignedThis, "S2").WithArguments("S2.X").WithLocation(20, 12)); + + comp = CreateCompilation(source, options: TestOptions.ReleaseExe); + comp.VerifyDiagnostics( + // (13,12): error CS0171: Field 'S1.X' must be fully assigned before control is returned to the caller + // public S1() { Y = 1; } + Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.X").WithLocation(13, 12), + // (20,12): error CS0171: Field 'S2.X' must be fully assigned before control is returned to the caller + // public S2(object y) { Y = y; } + Diagnostic(ErrorCode.ERR_UnassignedThis, "S2").WithArguments("S2.X").WithLocation(20, 12)); + } + + [Fact] + public void FieldInitializers_01() + { + var source = +@"#pragma warning disable 649 +using System; +struct S1 +{ + object X = null; + object Y; + public override string ToString() => (X, Y).ToString(); +} +struct S2 +{ + object X = null; + object Y; + public S2() { Y = 1; } + public override string ToString() => (X, Y).ToString(); +} +struct S3 +{ + object X; + object Y = null; + public S3(object x) { X = x; } + public override string ToString() => (X, Y).ToString(); +} +class Program +{ + static void Main() + { + Console.WriteLine(new S1()); + Console.WriteLine(new S2()); + Console.WriteLine(new S3()); + } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (5,12): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // object X = null; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "X").WithArguments("struct field initializers", "10.0").WithLocation(5, 12), + // (11,12): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // object X = null; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "X").WithArguments("struct field initializers", "10.0").WithLocation(11, 12), + // (13,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // public S2() { Y = 1; } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S2").WithArguments("parameterless struct constructors", "10.0").WithLocation(13, 12), + // (19,12): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // object Y = null; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "Y").WithArguments("struct field initializers", "10.0").WithLocation(19, 12)); + + comp = CreateCompilation(source, options: TestOptions.ReleaseExe); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(comp, expectedOutput: +@"(, ) +(, 1) +(, )"); + verifier.VerifyIL("Program.Main", +@"{ + // Code size 50 (0x32) + .maxstack 1 + .locals init (S3 V_0) + IL_0000: newobj ""S1..ctor()"" + IL_0005: box ""S1"" + IL_000a: call ""void System.Console.WriteLine(object)"" + IL_000f: newobj ""S2..ctor()"" + IL_0014: box ""S2"" + IL_0019: call ""void System.Console.WriteLine(object)"" + IL_001e: ldloca.s V_0 + IL_0020: initobj ""S3"" + IL_0026: ldloc.0 + IL_0027: box ""S3"" + IL_002c: call ""void System.Console.WriteLine(object)"" + IL_0031: ret +}"); + verifier.VerifyIL("S1..ctor()", +@"{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld ""object S1.X"" + IL_0007: ret +}"); + verifier.VerifyIL("S2..ctor()", +@"{ + // Code size 20 (0x14) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld ""object S2.X"" + IL_0007: ldarg.0 + IL_0008: ldc.i4.1 + IL_0009: box ""int"" + IL_000e: stfld ""object S2.Y"" + IL_0013: ret +}"); + verifier.VerifyMissing("S3..ctor()"); + verifier.VerifyIL("S3..ctor(object)", +@"{ + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld ""object S3.Y"" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: stfld ""object S3.X"" + IL_000e: ret +}"); + } + + [Fact] + public void FieldInitializers_02() + { + var source = +@"#pragma warning disable 649 +using System; +struct S1 +{ + internal object X = 1; + internal object Y; + public override string ToString() => (X, Y).ToString(); +} +struct S2 +{ + internal object X = 2; + internal object Y; + public S2() { Y = 2; } + public override string ToString() => (X, Y).ToString(); +} +struct S3 +{ + internal object X = 3; + internal object Y; + public S3(object _) { Y = 3; } + public override string ToString() => (X, Y).ToString(); +} +class Program +{ + static void Main() + { + Console.WriteLine(new S1()); + Console.WriteLine(new S2()); + Console.WriteLine(new S3()); + Console.WriteLine(new S1 { }); + Console.WriteLine(new S2 { }); + Console.WriteLine(new S3 { }); + Console.WriteLine(new S1 { Y = 2 }); + Console.WriteLine(new S2 { Y = 4 }); + Console.WriteLine(new S3 { Y = 6 }); + } +}"; + + var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: +@"(1, ) +(2, 2) +(, ) +(1, ) +(2, 2) +(, ) +(1, 2) +(2, 4) +(, 6)"); + verifier.VerifyIL("Program.Main", +@"{ + // Code size 193 (0xc1) + .maxstack 2 + .locals init (S3 V_0, + S1 V_1, + S2 V_2) + IL_0000: newobj ""S1..ctor()"" + IL_0005: box ""S1"" + IL_000a: call ""void System.Console.WriteLine(object)"" + IL_000f: newobj ""S2..ctor()"" + IL_0014: box ""S2"" + IL_0019: call ""void System.Console.WriteLine(object)"" + IL_001e: ldloca.s V_0 + IL_0020: initobj ""S3"" + IL_0026: ldloc.0 + IL_0027: box ""S3"" + IL_002c: call ""void System.Console.WriteLine(object)"" + IL_0031: newobj ""S1..ctor()"" + IL_0036: box ""S1"" + IL_003b: call ""void System.Console.WriteLine(object)"" + IL_0040: newobj ""S2..ctor()"" + IL_0045: box ""S2"" + IL_004a: call ""void System.Console.WriteLine(object)"" + IL_004f: ldloca.s V_0 + IL_0051: initobj ""S3"" + IL_0057: ldloc.0 + IL_0058: box ""S3"" + IL_005d: call ""void System.Console.WriteLine(object)"" + IL_0062: ldloca.s V_1 + IL_0064: call ""S1..ctor()"" + IL_0069: ldloca.s V_1 + IL_006b: ldc.i4.2 + IL_006c: box ""int"" + IL_0071: stfld ""object S1.Y"" + IL_0076: ldloc.1 + IL_0077: box ""S1"" + IL_007c: call ""void System.Console.WriteLine(object)"" + IL_0081: ldloca.s V_2 + IL_0083: call ""S2..ctor()"" + IL_0088: ldloca.s V_2 + IL_008a: ldc.i4.4 + IL_008b: box ""int"" + IL_0090: stfld ""object S2.Y"" + IL_0095: ldloc.2 + IL_0096: box ""S2"" + IL_009b: call ""void System.Console.WriteLine(object)"" + IL_00a0: ldloca.s V_0 + IL_00a2: initobj ""S3"" + IL_00a8: ldloca.s V_0 + IL_00aa: ldc.i4.6 + IL_00ab: box ""int"" + IL_00b0: stfld ""object S3.Y"" + IL_00b5: ldloc.0 + IL_00b6: box ""S3"" + IL_00bb: call ""void System.Console.WriteLine(object)"" + IL_00c0: ret +}"); + verifier.VerifyIL("S1..ctor()", +@"{ + // Code size 13 (0xd) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.1 + IL_0002: box ""int"" + IL_0007: stfld ""object S1.X"" + IL_000c: ret +}"); + verifier.VerifyIL("S2..ctor()", +@"{ + // Code size 25 (0x19) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.2 + IL_0002: box ""int"" + IL_0007: stfld ""object S2.X"" + IL_000c: ldarg.0 + IL_000d: ldc.i4.2 + IL_000e: box ""int"" + IL_0013: stfld ""object S2.Y"" + IL_0018: ret +}"); + verifier.VerifyMissing("S3..ctor()"); + verifier.VerifyIL("S3..ctor(object)", +@"{ + // Code size 25 (0x19) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.3 + IL_0002: box ""int"" + IL_0007: stfld ""object S3.X"" + IL_000c: ldarg.0 + IL_000d: ldc.i4.3 + IL_000e: box ""int"" + IL_0013: stfld ""object S3.Y"" + IL_0018: ret +}"); + } + + // As above but with auto-properties. + [Fact] + public void FieldInitializers_03() + { + var source = +@"#pragma warning disable 649 +using System; +struct S1 +{ + internal object X { get; } = 1; + internal object Y { get; } + public override string ToString() => (X, Y).ToString(); +} +struct S2 +{ + internal object X { get; init; } = 2; + internal object Y { get; init; } + public S2() { Y = 2; } + public override string ToString() => (X, Y).ToString(); +} +struct S3 +{ + internal object X { get; private set; } = 3; + internal object Y { get; set; } + public S3(object _) { Y = 3; } + public override string ToString() => (X, Y).ToString(); +} +class Program +{ + static void Main() + { + Console.WriteLine(new S1()); + Console.WriteLine(new S2()); + Console.WriteLine(new S3()); + Console.WriteLine(new S1 { }); + Console.WriteLine(new S2 { }); + Console.WriteLine(new S3 { }); + Console.WriteLine(new S2 { Y = 4 }); + Console.WriteLine(new S3 { Y = 6 }); + } +}"; + + var verifier = CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, options: TestOptions.ReleaseExe, verify: Verification.Skipped, expectedOutput: +@"(1, ) +(2, 2) +(, ) +(1, ) +(2, 2) +(, ) +(2, 4) +(, 6)"); + verifier.VerifyIL("Program.Main", +@"{ + // Code size 162 (0xa2) + .maxstack 2 + .locals init (S3 V_0, + S2 V_1) + IL_0000: newobj ""S1..ctor()"" + IL_0005: box ""S1"" + IL_000a: call ""void System.Console.WriteLine(object)"" + IL_000f: newobj ""S2..ctor()"" + IL_0014: box ""S2"" + IL_0019: call ""void System.Console.WriteLine(object)"" + IL_001e: ldloca.s V_0 + IL_0020: initobj ""S3"" + IL_0026: ldloc.0 + IL_0027: box ""S3"" + IL_002c: call ""void System.Console.WriteLine(object)"" + IL_0031: newobj ""S1..ctor()"" + IL_0036: box ""S1"" + IL_003b: call ""void System.Console.WriteLine(object)"" + IL_0040: newobj ""S2..ctor()"" + IL_0045: box ""S2"" + IL_004a: call ""void System.Console.WriteLine(object)"" + IL_004f: ldloca.s V_0 + IL_0051: initobj ""S3"" + IL_0057: ldloc.0 + IL_0058: box ""S3"" + IL_005d: call ""void System.Console.WriteLine(object)"" + IL_0062: ldloca.s V_1 + IL_0064: call ""S2..ctor()"" + IL_0069: ldloca.s V_1 + IL_006b: ldc.i4.4 + IL_006c: box ""int"" + IL_0071: call ""void S2.Y.init"" + IL_0076: ldloc.1 + IL_0077: box ""S2"" + IL_007c: call ""void System.Console.WriteLine(object)"" + IL_0081: ldloca.s V_0 + IL_0083: initobj ""S3"" + IL_0089: ldloca.s V_0 + IL_008b: ldc.i4.6 + IL_008c: box ""int"" + IL_0091: call ""void S3.Y.set"" + IL_0096: ldloc.0 + IL_0097: box ""S3"" + IL_009c: call ""void System.Console.WriteLine(object)"" + IL_00a1: ret +}"); + verifier.VerifyIL("S1..ctor()", +@"{ + // Code size 13 (0xd) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.1 + IL_0002: box ""int"" + IL_0007: stfld ""object S1.k__BackingField"" + IL_000c: ret +}"); + verifier.VerifyIL("S2..ctor()", +@"{ + // Code size 25 (0x19) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.2 + IL_0002: box ""int"" + IL_0007: stfld ""object S2.k__BackingField"" + IL_000c: ldarg.0 + IL_000d: ldc.i4.2 + IL_000e: box ""int"" + IL_0013: call ""void S2.Y.init"" + IL_0018: ret +}"); + verifier.VerifyMissing("S3..ctor()"); + verifier.VerifyIL("S3..ctor(object)", +@"{ + // Code size 25 (0x19) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.3 + IL_0002: box ""int"" + IL_0007: stfld ""object S3.k__BackingField"" + IL_000c: ldarg.0 + IL_000d: ldc.i4.3 + IL_000e: box ""int"" + IL_0013: call ""void S3.Y.set"" + IL_0018: ret +}"); + } + + [Fact] + public void FieldInitializers_04() + { + var source = +@"#pragma warning disable 649 +using System; +struct S1 { internal int X = 1; } +struct S2 { internal int X = 2; public S2() { } } +struct S3 { internal int X = 3; public S3(int _) { } } +class Program +{ + static void Main() + { + Console.WriteLine(new S1().X); + Console.WriteLine(new S2().X); + Console.WriteLine(new S3().X); + } +}"; + + var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: +@"1 +2 +0"); + verifier.VerifyIL("Program.Main", +@"{ + // Code size 50 (0x32) + .maxstack 1 + .locals init (S3 V_0) + IL_0000: newobj ""S1..ctor()"" + IL_0005: ldfld ""int S1.X"" + IL_000a: call ""void System.Console.WriteLine(int)"" + IL_000f: newobj ""S2..ctor()"" + IL_0014: ldfld ""int S2.X"" + IL_0019: call ""void System.Console.WriteLine(int)"" + IL_001e: ldloca.s V_0 + IL_0020: initobj ""S3"" + IL_0026: ldloc.0 + IL_0027: ldfld ""int S3.X"" + IL_002c: call ""void System.Console.WriteLine(int)"" + IL_0031: ret +}"); + } + + [Fact] + public void FieldInitializers_05() + { + var source = +@"#pragma warning disable 649 +using System; +class A +{ + internal struct S1 { internal int X { get; } = 1; } + internal struct S2 { internal int X { get; init; } = 2; public S2() { } } + internal struct S3 { internal int X { get; set; } = 3; public S3(int _) { } } +} +class Program +{ + static void Main() + { + Console.WriteLine(new A.S1().X); + Console.WriteLine(new A.S2().X); + Console.WriteLine(new A.S3().X); + } +}"; + + var verifier = CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, options: TestOptions.ReleaseExe, verify: Verification.Skipped, expectedOutput: +@"1 +2 +0"); + verifier.VerifyIL("Program.Main", +@"{ + // Code size 56 (0x38) + .maxstack 2 + .locals init (A.S1 V_0, + A.S2 V_1, + A.S3 V_2) + IL_0000: newobj ""A.S1..ctor()"" + IL_0005: stloc.0 + IL_0006: ldloca.s V_0 + IL_0008: call ""readonly int A.S1.X.get"" + IL_000d: call ""void System.Console.WriteLine(int)"" + IL_0012: newobj ""A.S2..ctor()"" + IL_0017: stloc.1 + IL_0018: ldloca.s V_1 + IL_001a: call ""readonly int A.S2.X.get"" + IL_001f: call ""void System.Console.WriteLine(int)"" + IL_0024: ldloca.s V_2 + IL_0026: dup + IL_0027: initobj ""A.S3"" + IL_002d: call ""readonly int A.S3.X.get"" + IL_0032: call ""void System.Console.WriteLine(int)"" + IL_0037: ret +}"); + verifier.VerifyIL("A.S1..ctor()", +@"{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.1 + IL_0002: stfld ""int A.S1.k__BackingField"" + IL_0007: ret +}"); + verifier.VerifyIL("A.S2..ctor()", +@"{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.2 + IL_0002: stfld ""int A.S2.k__BackingField"" + IL_0007: ret +}"); + verifier.VerifyIL("A.S3..ctor(int)", +@"{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.3 + IL_0002: stfld ""int A.S3.k__BackingField"" + IL_0007: ret +}"); + } + + [Fact] + public void ExpressionTrees() + { + var source = +@"#pragma warning disable 649 +using System; +using System.Linq.Expressions; +struct S0 +{ + int X; + public override string ToString() => X.ToString(); +} +struct S1 +{ + int X = 1; + public override string ToString() => X.ToString(); +} +struct S2 +{ + int X = 2; + public S2() { } + public override string ToString() => X.ToString(); +} +class Program +{ + static void Main() + { + Report(() => new S0()); + Report(() => new S1()); + Report(() => new S2()); + } + static void Report(Expression> e) + { + var t = e.Compile().Invoke(); + Console.WriteLine(t); + } +}"; + + var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: +@"0 +1 +2"); + verifier.VerifyIL("Program.Main", +@"{ + // Code size 111 (0x6f) + .maxstack 2 + IL_0000: ldtoken ""S0"" + IL_0005: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" + IL_000a: call ""System.Linq.Expressions.NewExpression System.Linq.Expressions.Expression.New(System.Type)"" + IL_000f: call ""System.Linq.Expressions.ParameterExpression[] System.Array.Empty()"" + IL_0014: call ""System.Linq.Expressions.Expression> System.Linq.Expressions.Expression.Lambda>(System.Linq.Expressions.Expression, params System.Linq.Expressions.ParameterExpression[])"" + IL_0019: call ""void Program.Report(System.Linq.Expressions.Expression>)"" + IL_001e: ldtoken ""S1..ctor()"" + IL_0023: call ""System.Reflection.MethodBase System.Reflection.MethodBase.GetMethodFromHandle(System.RuntimeMethodHandle)"" + IL_0028: castclass ""System.Reflection.ConstructorInfo"" + IL_002d: call ""System.Linq.Expressions.Expression[] System.Array.Empty()"" + IL_0032: call ""System.Linq.Expressions.NewExpression System.Linq.Expressions.Expression.New(System.Reflection.ConstructorInfo, System.Collections.Generic.IEnumerable)"" + IL_0037: call ""System.Linq.Expressions.ParameterExpression[] System.Array.Empty()"" + IL_003c: call ""System.Linq.Expressions.Expression> System.Linq.Expressions.Expression.Lambda>(System.Linq.Expressions.Expression, params System.Linq.Expressions.ParameterExpression[])"" + IL_0041: call ""void Program.Report(System.Linq.Expressions.Expression>)"" + IL_0046: ldtoken ""S2..ctor()"" + IL_004b: call ""System.Reflection.MethodBase System.Reflection.MethodBase.GetMethodFromHandle(System.RuntimeMethodHandle)"" + IL_0050: castclass ""System.Reflection.ConstructorInfo"" + IL_0055: call ""System.Linq.Expressions.Expression[] System.Array.Empty()"" + IL_005a: call ""System.Linq.Expressions.NewExpression System.Linq.Expressions.Expression.New(System.Reflection.ConstructorInfo, System.Collections.Generic.IEnumerable)"" + IL_005f: call ""System.Linq.Expressions.ParameterExpression[] System.Array.Empty()"" + IL_0064: call ""System.Linq.Expressions.Expression> System.Linq.Expressions.Expression.Lambda>(System.Linq.Expressions.Expression, params System.Linq.Expressions.ParameterExpression[])"" + IL_0069: call ""void Program.Report(System.Linq.Expressions.Expression>)"" + IL_006e: ret +}"); + } + + [Fact] + public void Retargeting_01() + { + var sourceA = +@"public struct S1 +{ + public int X = 1; +} +public struct S2 +{ + public int X = 2; + public S2() { } +} +public struct S3 +{ + public int X = 3; + public S3(object _) { } +}"; + var comp = CreateCompilation(sourceA, targetFramework: TargetFramework.Mscorlib40); + var refA = comp.ToMetadataReference(); + + var typeA = comp.GetMember("S1.X").Type; + var corLibA = comp.Assembly.CorLibrary; + Assert.Equal(corLibA, typeA.ContainingAssembly); + + var sourceB = +@"using System; +class Program +{ + static void Main() + { + Console.WriteLine(new S1().X); + Console.WriteLine(new S2().X); + Console.WriteLine(new S3().X); + } +}"; + comp = CreateCompilation(sourceB, references: new[] { refA }, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular9, targetFramework: TargetFramework.Mscorlib45); + CompileAndVerify(comp, expectedOutput: +@"1 +2 +0"); + + var corLibB = comp.Assembly.CorLibrary; + Assert.NotEqual(corLibA, corLibB); + + var field = comp.GetMember("S1.X"); + Assert.IsType(field); + var typeB = (NamedTypeSymbol)field.Type; + Assert.Equal(corLibB, typeB.ContainingAssembly); + } + + [Fact] + public void NullableAnalysis_01() + { + var source = +@"#pragma warning disable 169 +#nullable enable +struct S0 +{ + object F0; +} +struct S1 +{ + object F1; + public S1() { } +} +struct S2 +{ + object F2; + public S2() : this(null) { } + S2(object? obj) { } +} +struct S3 +{ + object F3; + public S3() { F3 = GetValue(); } + static object? GetValue() => null; +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (10,12): error CS0171: Field 'S1.F1' must be fully assigned before control is returned to the caller + // public S1() { } + Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.F1").WithLocation(10, 12), + // (16,5): error CS0171: Field 'S2.F2' must be fully assigned before control is returned to the caller + // S2(object? obj) { } + Diagnostic(ErrorCode.ERR_UnassignedThis, "S2").WithArguments("S2.F2").WithLocation(16, 5), + // (21,12): warning CS8618: Non-nullable field 'F3' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // public S3() { F3 = GetValue(); } + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S3").WithArguments("field", "F3").WithLocation(21, 12), + // (21,24): warning CS8601: Possible null reference assignment. + // public S3() { F3 = GetValue(); } + Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "GetValue()").WithLocation(21, 24)); + } + + [Fact] + public void NullableAnalysis_02() + { + var source = +@"#nullable enable +struct S0 +{ + object F0 = Utils.GetValue(); +} +struct S1 +{ + object F1 = Utils.GetValue(); + public S1() { } +} +struct S2 +{ + object F2 = Utils.GetValue(); + public S2() : this(null) { } + S2(object obj) { } +} +static class Utils +{ + internal static object? GetValue() => null; +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,17): warning CS8601: Possible null reference assignment. + // object F0 = Utils.GetValue(); + Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "Utils.GetValue()").WithLocation(4, 17), + // (8,17): warning CS8601: Possible null reference assignment. + // object F1 = Utils.GetValue(); + Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "Utils.GetValue()").WithLocation(8, 17), + // (13,17): warning CS8601: Possible null reference assignment. + // object F2 = Utils.GetValue(); + Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "Utils.GetValue()").WithLocation(13, 17), + // (14,24): warning CS8625: Cannot convert null literal to non-nullable reference type. + // public S2() : this(null) { } + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(14, 24)); + } + + [Fact] + public void DefiniteAssignment() + { + var source = +@"#pragma warning disable 169 +unsafe struct S0 +{ + fixed int Y[1]; +} +unsafe struct S1 +{ + fixed int Y[1]; + public S1() { } +} +unsafe struct S2 +{ + int X; + fixed int Y[1]; +} +unsafe struct S3 +{ + int X; + fixed int Y[1]; + public S3() { } +} +unsafe struct S4 +{ + int X = 4; + fixed int Y[1]; +} +unsafe struct S5 +{ + int X; + fixed int Y[1]; + public S5() { X = 5; } +}"; + + var comp = CreateCompilation(source, options: TestOptions.UnsafeReleaseDll); + comp.VerifyDiagnostics( + // (20,12): error CS0171: Field 'S3.X' must be fully assigned before control is returned to the caller + // public S3() { } + Diagnostic(ErrorCode.ERR_UnassignedThis, "S3").WithArguments("S3.X").WithLocation(20, 12), + // (24,9): warning CS0414: The field 'S4.X' is assigned but its value is never used + // int X = 4; + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "X").WithArguments("S4.X").WithLocation(24, 9), + // (29,9): warning CS0414: The field 'S5.X' is assigned but its value is never used + // int X; + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "X").WithArguments("S5.X").WithLocation(29, 9)); + } + + [Fact] + public void ParameterDefaultValues_01() + { + var source = +@"struct S1 { } +struct S2 { public S2() { } } +struct S3 { internal S3() { } } +struct S4 { private S4() { } } +class Program +{ + static void F1(S1 s = default) { } + static void F2(S2 s = default) { } + static void F3(S3 s = default) { } + static void F4(S4 s = default) { } + static void G1(S1 s = new()) { } + static void G2(S2 s = new()) { } + static void G3(S3 s = new()) { } + static void G4(S4 s = new()) { } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,22): error CS8938: The parameterless struct constructor must be 'public'. + // struct S3 { internal S3() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "S3").WithLocation(3, 22), + // (4,21): error CS8938: The parameterless struct constructor must be 'public'. + // struct S4 { private S4() { } } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "S4").WithLocation(4, 21), + // (12,27): error CS1736: Default parameter value for 's' must be a compile-time constant + // static void G2(S2 s = new()) { } + Diagnostic(ErrorCode.ERR_DefaultValueMustBeConstant, "new()").WithArguments("s").WithLocation(12, 27), + // (13,27): error CS1736: Default parameter value for 's' must be a compile-time constant + // static void G3(S3 s = new()) { } + Diagnostic(ErrorCode.ERR_DefaultValueMustBeConstant, "new()").WithArguments("s").WithLocation(13, 27), + // (14,27): error CS0122: 'S4.S4()' is inaccessible due to its protection level + // static void G4(S4 s = new()) { } + Diagnostic(ErrorCode.ERR_BadAccess, "new()").WithArguments("S4.S4()").WithLocation(14, 27), + // (14,27): error CS1736: Default parameter value for 's' must be a compile-time constant + // static void G4(S4 s = new()) { } + Diagnostic(ErrorCode.ERR_DefaultValueMustBeConstant, "new()").WithArguments("s").WithLocation(14, 27)); + } + + [Fact] + public void ParameterDefaultValues_02() + { + var source = +@"struct S1 +{ + object X = 1; +} +struct S2 +{ + object X = 2; + public S2() { } +} +struct S3 +{ + object X = 3; + public S3(object x) { X = x; } +} +class Program +{ + static void F1(S1 s = default) { } + static void F2(S2 s = default) { } + static void F3(S3 s = default) { } + static void G1(S1 s = new()) { } + static void G2(S2 s = new()) { } + static void G3(S3 s = new()) { } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (20,27): error CS1736: Default parameter value for 's' must be a compile-time constant + // static void G1(S1 s = new()) { } + Diagnostic(ErrorCode.ERR_DefaultValueMustBeConstant, "new()").WithArguments("s").WithLocation(20, 27), + // (21,27): error CS1736: Default parameter value for 's' must be a compile-time constant + // static void G2(S2 s = new()) { } + Diagnostic(ErrorCode.ERR_DefaultValueMustBeConstant, "new()").WithArguments("s").WithLocation(21, 27)); + } + + [Fact] + public void Constants() + { + var source = +@"struct S0 +{ +} +struct S1 +{ + object X = 1; +} +struct S2 +{ + public S2() { } +} +class Program +{ + const object d0 = default(S0); + const object d1 = default(S1); + const object d2 = default(S2); + const object s0 = new S0(); + const object s1 = new S1(); + const object s2 = new S2(); +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (14,23): error CS0133: The expression being assigned to 'Program.d0' must be constant + // const object d0 = default(S0); + Diagnostic(ErrorCode.ERR_NotConstantExpression, "default(S0)").WithArguments("Program.d0").WithLocation(14, 23), + // (15,23): error CS0133: The expression being assigned to 'Program.d1' must be constant + // const object d1 = default(S1); + Diagnostic(ErrorCode.ERR_NotConstantExpression, "default(S1)").WithArguments("Program.d1").WithLocation(15, 23), + // (16,23): error CS0133: The expression being assigned to 'Program.d2' must be constant + // const object d2 = default(S2); + Diagnostic(ErrorCode.ERR_NotConstantExpression, "default(S2)").WithArguments("Program.d2").WithLocation(16, 23), + // (17,23): error CS0133: The expression being assigned to 'Program.s0' must be constant + // const object s0 = new S0(); + Diagnostic(ErrorCode.ERR_NotConstantExpression, "new S0()").WithArguments("Program.s0").WithLocation(17, 23), + // (18,23): error CS0133: The expression being assigned to 'Program.s1' must be constant + // const object s1 = new S1(); + Diagnostic(ErrorCode.ERR_NotConstantExpression, "new S1()").WithArguments("Program.s1").WithLocation(18, 23), + // (19,23): error CS0133: The expression being assigned to 'Program.s2' must be constant + // const object s2 = new S2(); + Diagnostic(ErrorCode.ERR_NotConstantExpression, "new S2()").WithArguments("Program.s2").WithLocation(19, 23)); + } + + [Fact] + public void NoPIA() + { + var sourceA = +@"using System.Runtime.InteropServices; +[assembly: ImportedFromTypeLib(""_.dll"")] +[assembly: Guid(""9758B46C-5297-4832-BB58-F2B5B78B0D01"")] +[ComImport()] +[Guid(""6D947BE5-75B1-4D97-B444-2720624761D7"")] +public interface I +{ + S0 F0(); + S1 F1(); + S2 F2(); +} +public struct S0 +{ +} +public struct S1 +{ + public S1() { } +} +public struct S2 +{ + object F = 2; +}"; + var comp = CreateCompilationWithMscorlib40(sourceA); + var refA = comp.EmitToImageReference(embedInteropTypes: true); + + var sourceB = +@"class Program +{ + static void M(I i) + { + var s0 = i.F0(); + var s1 = i.F1(); + var s2 = i.F2(); + } +}"; + comp = CreateCompilationWithMscorlib40(sourceB, references: new[] { refA }); + comp.VerifyEmitDiagnostics( + // (6,18): error CS1757: Embedded interop struct 'S1' can contain only public instance fields. + // var s1 = i.F1(); + Diagnostic(ErrorCode.ERR_InteropStructContainsMethods, "i.F1()").WithArguments("S1").WithLocation(6, 18), + // (7,18): error CS1757: Embedded interop struct 'S2' can contain only public instance fields. + // var s2 = i.F2(); + Diagnostic(ErrorCode.ERR_InteropStructContainsMethods, "i.F2()").WithArguments("S2").WithLocation(7, 18)); + } + } +} diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StructsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StructsTests.cs index 083a1c1bf07aa..fe3f30038aab7 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StructsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StructsTests.cs @@ -7,9 +7,7 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; -using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -17,7 +15,6 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics { public class StructsTests : CompilingTestBase { - // Cannot have instance field initializers in structs [WorkItem(540982, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540982")] [Fact()] public void TestInitFieldStruct() @@ -29,17 +26,16 @@ public struct A public static int Main() { return 1; } } "; - CreateCompilation(text).VerifyDiagnostics( - // (4,7): error CS0573: 'A': cannot have instance property or field initializers in structs - // A a = new A(); // CS8036 - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "a").WithArguments("A").WithLocation(4, 7), - // (4,7): error CS0523: Struct member 'A.a' of type 'A' causes a cycle in the struct layout - // A a = new A(); // CS8036 - Diagnostic(ErrorCode.ERR_StructLayoutCycle, "a").WithArguments("A.a", "A").WithLocation(4, 7), - // (4,7): warning CS0169: The field 'A.a' is never used - // A a = new A(); // CS8036 - Diagnostic(ErrorCode.WRN_UnreferencedField, "a").WithArguments("A.a").WithLocation(4, 7) - ); + CreateCompilation(text, parseOptions: TestOptions.Regular9).VerifyDiagnostics( + // (4,7): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // A a = new A(); // CS8036 + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "a").WithArguments("struct field initializers", "10.0").WithLocation(4, 7), + // (4,7): error CS0523: Struct member 'A.a' of type 'A' causes a cycle in the struct layout + // A a = new A(); // CS8036 + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "a").WithArguments("A.a", "A").WithLocation(4, 7), + // (4,7): warning CS0414: The field 'A.a' is assigned but its value is never used + // A a = new A(); // CS8036 + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "a").WithArguments("A.a").WithLocation(4, 7)); } [WorkItem(1075325, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1075325"), WorkItem(343, "CodePlex")] @@ -56,11 +52,12 @@ void M() } } "; - CreateCompilation(text).VerifyDiagnostics( - // (3,25): error CS0573: 'S': cannot have instance property or field initializers in structs - // event System.Action E = null; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "E").WithArguments("S").WithLocation(3, 25) - ); + CreateCompilation(text, parseOptions: TestOptions.Regular9).VerifyDiagnostics( + // (3,25): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // event System.Action E = null; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "E").WithArguments("struct field initializers", "10.0").WithLocation(3, 25)); + + CreateCompilation(text).VerifyDiagnostics(); } [WorkItem(1075325, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1075325"), WorkItem(343, "CodePlex")] @@ -363,7 +360,7 @@ public void RetargetedSynthesizedStructConstructor() var @struct = c2.GlobalNamespace.GetMember("S"); var method = (RetargetingMethodSymbol)@struct.GetMembers().Single(); - Assert.True(method.IsDefaultValueTypeConstructor()); + Assert.True(method.IsDefaultValueTypeConstructor(requireZeroInit: false)); //TODO (tomat) CompileAndVerify(c2).VerifyIL("C.M", @" @@ -499,7 +496,6 @@ void M() // CONSIDER: This is the dev10 behavior, but it seems like a bug. // Shouldn't there be an error for trying to call an inaccessible ctor? var comp = CreateCompilationWithILAndMscorlib40(csharpSource, ilSource); - CompileAndVerify(comp).VerifyIL("C.M", @" { // Code size 39 (0x27) @@ -570,7 +566,6 @@ public static void Main() ); } - [WorkItem(545498, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545498")] [Fact] public void StructMemberNullableTypeCausesCycle() @@ -605,16 +600,28 @@ public struct X1 { } } - "; - CreateCompilationWithMscorlib45(source).VerifyDiagnostics( - // (11,5): error CS0568: Structs cannot contain explicit parameterless constructors - // X1() - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "X1").WithLocation(11, 5), - // (4,13): error CS0568: Structs cannot contain explicit parameterless constructors - // private X() - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "X").WithLocation(4, 13) - ); + CreateCompilation(source, parseOptions: TestOptions.Regular9).VerifyDiagnostics( + // (4,13): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // private X() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "X").WithArguments("parameterless struct constructors", "10.0").WithLocation(4, 13), + // (4,13): error CS8938: The parameterless struct constructor must be 'public'. + // private X() + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "X").WithLocation(4, 13), + // (11,5): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // X1() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "X1").WithArguments("parameterless struct constructors", "10.0").WithLocation(11, 5), + // (11,5): error CS8938: The parameterless struct constructor must be 'public'. + // X1() + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "X1").WithLocation(11, 5)); + + CreateCompilation(source).VerifyDiagnostics( + // (4,13): error CS8918: The parameterless struct constructor must be 'public'. + // private X() + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "X").WithLocation(4, 13), + // (11,5): error CS8918: The parameterless struct constructor must be 'public'. + // X1() + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "X1").WithLocation(11, 5)); } [Fact] @@ -625,15 +632,20 @@ public void StructNonAutoPropertyInitializer() public int I { get { throw null; } set {} } = 9; }"; - var comp = CreateCompilation(text); + var comp = CreateCompilation(text, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (3,16): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // public int I { get { throw null; } set {} } = 9; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "I").WithArguments("struct field initializers", "10.0").WithLocation(3, 16), + // (3,16): error CS8050: Only auto-implemented properties can have initializers. + // public int I { get { throw null; } set {} } = 9; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "I").WithArguments("S.I").WithLocation(3, 16)); + + comp = CreateCompilation(text); comp.VerifyDiagnostics( - // (3,16): error CS8050: Only auto-implemented properties can have initializers. - // public int I {get { throw null; } set {} } = 9; - Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "I").WithArguments("S.I").WithLocation(3, 16), - // (3,16): error CS0573: 'S': cannot have instance property or field initializers in structs - // public int I {get { throw null; } set {} } = 9; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "I").WithArguments("S").WithLocation(3, 16) -); + // (3,16): error CS8050: Only auto-implemented properties can have initializers. + // public int I { get { throw null; } set {} } = 9; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "I").WithArguments("S.I").WithLocation(3, 16)); } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs index 13a6c74374247..f12534e5449fa 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs @@ -857,6 +857,9 @@ public S1(string s1, string s2) : this(s1) // (5,12): error CS0843: Auto-implemented property 'S1.Prop' must be fully assigned before control is returned to the caller. // public S1(string s) // 1 Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S1").WithArguments("S1.Prop").WithLocation(5, 12), + // (7,9): warning CS8602: Dereference of a possibly null reference. + // Prop.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(7, 9), // (7,9): error CS8079: Use of possibly unassigned auto-implemented property 'Prop' // Prop.ToString(); // 2 Diagnostic(ErrorCode.ERR_UseDefViolationProperty, "Prop").WithArguments("Prop").WithLocation(7, 9), @@ -895,7 +898,7 @@ public S1(string s1, string s2) : this(s1) } } "; - + // PROTOTYPE: NullableWalker.Scan() overwrites initial field state when calling EnterParameter(methodThisParameter). var comp = CreateCompilation(source, options: WithNullableEnable()); comp.VerifyDiagnostics( // (4,19): warning CS0649: Field 'S1.field' is never assigned to, and will always have its default value null @@ -906,14 +909,13 @@ public S1(string s1, string s2) : this(s1) Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.field").WithLocation(5, 12), // (7,9): error CS0170: Use of possibly unassigned field 'field' // field.ToString(); // 2 - Diagnostic(ErrorCode.ERR_UseDefViolationField, "field").WithArguments("field").WithLocation(7, 9), + Diagnostic(ErrorCode.ERR_UseDefViolationField, "field").WithArguments("field").WithLocation(7, 9)/*, // (12,9): warning CS8602: Dereference of a possibly null reference. // field.ToString(); // 3 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "field").WithLocation(12, 9), // (15,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. // public S1(object obj1, object obj2) : this() // 4 - Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("field", "field").WithLocation(15, 12) - ); + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("field", "field").WithLocation(15, 12)*/); } [Fact, WorkItem(43215, "https://github.com/dotnet/roslyn/issues/43215")] @@ -1525,6 +1527,9 @@ internal S(string s) }"; var comp = CreateCompilation(new[] { source }, options: WithNullableEnable(), parseOptions: TestOptions.Regular8); comp.VerifyDiagnostics( + // (6,14): warning CS8618: Non-nullable property 'P' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. + // internal S(string s) + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("property", "P").WithLocation(6, 14), // (6,14): error CS0843: Auto-implemented property 'S.P' must be fully assigned before control is returned to the caller. // internal S(string s) Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S").WithArguments("S.P").WithLocation(6, 14), diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FieldTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FieldTests.cs index 44054156a31e2..def7246fc9de2 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FieldTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FieldTests.cs @@ -6,10 +6,8 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; @@ -27,11 +25,10 @@ public void InitializerInStruct() public S(int i) {} }"; - CreateCompilation(text).VerifyDiagnostics( - // (3,16): error CS0573: 'S': cannot have instance property or field initializers in structs - // public int I = 9; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "I").WithArguments("S").WithLocation(3, 16) -); + CreateCompilation(text, parseOptions: TestOptions.Regular9).VerifyDiagnostics( + // (3,16): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // public int I = 9; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "I").WithArguments("struct field initializers", "10.0").WithLocation(3, 16)); } [Fact] @@ -44,15 +41,11 @@ public void InitializerInStruct2() public S(int i) : this() {} }"; - var comp = CreateCompilation(text); + var comp = CreateCompilation(text, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (3,16): error CS0573: 'S': cannot have instance property or field initializers in structs - // public int I = 9; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "I").WithArguments("S").WithLocation(3, 16), - // (3,16): warning CS0649: Field 'S.I' is never assigned to, and will always have its default value 0 - // public int I = 9; - Diagnostic(ErrorCode.WRN_UnassignedInternalField, "I").WithArguments("S.I", "0").WithLocation(3, 16) -); + // (3,16): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // public int I = 9; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "I").WithArguments("struct field initializers", "10.0").WithLocation(3, 16)); } [Fact] diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs index 056af7e3cf087..cc11a51838e32 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs @@ -12,7 +12,6 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Emit; -using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -49,8 +48,14 @@ void M(int x) {} public void NoParameterlessCtorForStruct() { var text = "struct A { A() {} }"; - var comp = CreateCompilation(text); - Assert.Equal(1, comp.GetDeclarationDiagnostics().Count()); + var comp = CreateCompilation(text, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (1,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // struct A { A() {} } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "A").WithArguments("parameterless struct constructors", "10.0").WithLocation(1, 12), + // (1,12): error CS8938: The parameterless struct constructor must be 'public'. + // struct A { A() {} } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "A").WithLocation(1, 12)); } [WorkItem(537194, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/537194")] diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs index 21a09ff22a202..9d79c9a48db7a 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs @@ -78,7 +78,6 @@ public C() // (13,25): error CS8080: "Auto-implemented properties must override all accessors of the overridden property." // public override int P1 { get; } Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P1").WithArguments("C.P1").WithLocation(13, 25) - ); } @@ -121,30 +120,28 @@ public void M() } } -").VerifyDiagnostics( - // (24,12): error CS0568: Structs cannot contain explicit parameterless constructors - // public S() - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "S").WithLocation(24, 12), - // (9,9): error CS0200: Property or indexer 'C.Ps' cannot be assigned to -- it is read only - // Ps = 3; - Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "Ps").WithArguments("C.Ps").WithLocation(9, 9), - // (27,9): error CS0200: Property or indexer 'S.Ps' cannot be assigned to -- it is read only - // Ps = 5; - Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "Ps").WithArguments("S.Ps").WithLocation(27, 9), - // (14,9): error CS0200: Property or indexer 'C.P' cannot be assigned to -- it is read only - // P = 10; - Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "P").WithArguments("C.P").WithLocation(14, 9), - // (15,9): error CS0200: Property or indexer 'C.Ps' cannot be assigned to -- it is read only - // C.Ps = 1; - Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "C.Ps").WithArguments("C.Ps").WithLocation(15, 9), - // (32,9): error CS0200: Property or indexer 'S.P' cannot be assigned to -- it is read only - // P = 10; - Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "P").WithArguments("S.P").WithLocation(32, 9), - // (33,9): error CS0200: Property or indexer 'S.Ps' cannot be assigned to -- it is read only - // S.Ps = 1; - Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "S.Ps").WithArguments("S.Ps").WithLocation(33, 9) - - ); +", parseOptions: TestOptions.Regular9).VerifyDiagnostics( + // (9,9): error CS0200: Property or indexer 'C.Ps' cannot be assigned to -- it is read only + // Ps = 3; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "Ps").WithArguments("C.Ps").WithLocation(9, 9), + // (14,9): error CS0200: Property or indexer 'C.P' cannot be assigned to -- it is read only + // P = 10; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "P").WithArguments("C.P").WithLocation(14, 9), + // (15,9): error CS0200: Property or indexer 'C.Ps' cannot be assigned to -- it is read only + // C.Ps = 1; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "C.Ps").WithArguments("C.Ps").WithLocation(15, 9), + // (24,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // public S() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S").WithArguments("parameterless struct constructors", "10.0").WithLocation(24, 12), + // (27,9): error CS0200: Property or indexer 'S.Ps' cannot be assigned to -- it is read only + // Ps = 5; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "Ps").WithArguments("S.Ps").WithLocation(27, 9), + // (32,9): error CS0200: Property or indexer 'S.P' cannot be assigned to -- it is read only + // P = 10; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "P").WithArguments("S.P").WithLocation(32, 9), + // (33,9): error CS0200: Property or indexer 'S.Ps' cannot be assigned to -- it is read only + // S.Ps = 1; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "S.Ps").WithArguments("S.Ps").WithLocation(33, 9)); } [Fact] @@ -156,17 +153,16 @@ struct S int a = 2; int a { get { return 1; } set {} } }"; - CreateCompilation(text).VerifyDiagnostics( - // (4,9): error CS0573: 'S': cannot have instance property or field initializers in structs - // int a = 2; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "a").WithArguments("S").WithLocation(4, 9), - // (5,9): error CS0102: The type 'S' already contains a definition for 'a' - // int a { get { return 1; } set {} } - Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "a").WithArguments("S", "a").WithLocation(5, 9), - // (4,9): warning CS0169: The field 'S.a' is never used - // int a = 2; - Diagnostic(ErrorCode.WRN_UnreferencedField, "a").WithArguments("S.a").WithLocation(4, 9) - ); + CreateCompilation(text, parseOptions: TestOptions.Regular9).VerifyDiagnostics( + // (4,9): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // int a = 2; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "a").WithArguments("struct field initializers", "10.0").WithLocation(4, 9), + // (5,9): error CS0102: The type 'S' already contains a definition for 'a' + // int a { get { return 1; } set {} } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "a").WithArguments("S", "a").WithLocation(5, 9), + // (4,9): warning CS0414: The field 'S.a' is assigned but its value is never used + // int a = 2; + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "a").WithArguments("S.a").WithLocation(4, 9)); } [Fact] @@ -207,15 +203,14 @@ struct S public decimal R { get; } = 300; }"; - var comp = CreateCompilation(text, parseOptions: TestOptions.Regular); + var comp = CreateCompilation(text, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (4,16): error CS0573: 'S': cannot have instance property or field initializers in structs - // public int P { get; set; } = 1; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "P").WithArguments("S").WithLocation(4, 16), - // (6,20): error CS0573: 'S': cannot have instance property or field initializers in structs - // public decimal R { get; } = 300; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "R").WithArguments("S").WithLocation(6, 20) - ); + // (4,16): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // public int P { get; set; } = 1; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "P").WithArguments("struct field initializers", "10.0").WithLocation(4, 16), + // (6,20): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // public decimal R { get; } = 300; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "R").WithArguments("struct field initializers", "10.0").WithLocation(6, 20)); } [Fact] @@ -230,15 +225,14 @@ public void AutoWithInitializerInStruct2() public S(int i) : this() {} }"; - var comp = CreateCompilation(text, parseOptions: TestOptions.Regular); + var comp = CreateCompilation(text, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (3,16): error CS0573: 'S': cannot have instance property or field initializers in structs - // public int P { get; set; } = 1; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "P").WithArguments("S").WithLocation(3, 16), - // (5,20): error CS0573: 'S': cannot have instance property or field initializers in structs - // public decimal R { get; } = 300; - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "R").WithArguments("S").WithLocation(5, 20) -); + // (3,16): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // public int P { get; set; } = 1; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "P").WithArguments("struct field initializers", "10.0").WithLocation(3, 16), + // (5,20): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // public decimal R { get; } = 300; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "R").WithArguments("struct field initializers", "10.0").WithLocation(5, 20)); var global = comp.GlobalNamespace; var s = global.GetTypeMember("S"); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs index 167aad83f0519..cb71d33404f80 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs @@ -10654,24 +10654,29 @@ public struct cly } } "; - var comp = CreateCompilation(text); + var comp = CreateCompilation(text, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (12,13): error CS0573: 'cly': cannot have instance property or field initializers in structs + // (12,13): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. // clx a = new clx(); // CS8036 - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "a").WithArguments("x.cly").WithLocation(12, 13), - // (13,13): error CS0573: 'cly': cannot have instance property or field initializers in structs + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "a").WithArguments("struct field initializers", "10.0").WithLocation(12, 13), + // (13,13): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. // int i = 7; // CS8036 - Diagnostic(ErrorCode.ERR_FieldInitializerInStruct, "i").WithArguments("x.cly").WithLocation(13, 13), - // (12,13): warning CS0169: The field 'cly.a' is never used - // clx a = new clx(); // CS8036 - Diagnostic(ErrorCode.WRN_UnreferencedField, "a").WithArguments("x.cly.a").WithLocation(12, 13), - // (13,13): warning CS0169: The field 'cly.i' is never used + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "i").WithArguments("struct field initializers", "10.0").WithLocation(13, 13), + // (13,13): warning CS0414: The field 'cly.i' is assigned but its value is never used // int i = 7; // CS8036 - Diagnostic(ErrorCode.WRN_UnreferencedField, "i").WithArguments("x.cly.i").WithLocation(13, 13), + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "i").WithArguments("x.cly.i").WithLocation(13, 13), // (15,20): warning CS0414: The field 'cly.s' is assigned but its value is never used // static int s = 2; // no error - Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "s").WithArguments("x.cly.s").WithLocation(15, 20) - ); + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "s").WithArguments("x.cly.s").WithLocation(15, 20)); + + comp = CreateCompilation(text); + comp.VerifyDiagnostics( + // (13,13): warning CS0414: The field 'cly.i' is assigned but its value is never used + // int i = 7; // CS8036 + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "i").WithArguments("x.cly.i").WithLocation(13, 13), + // (15,20): warning CS0414: The field 'cly.s' is assigned but its value is never used + // static int s = 2; // no error + Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "s").WithArguments("x.cly.s").WithLocation(15, 20)); } [Fact] @@ -10692,13 +10697,21 @@ struct S2 "; var comp = CreateCompilation(text, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp5)); comp.VerifyDiagnostics( - // (5,16): error CS0568: Structs cannot contain explicit parameterless constructors - // public S1() {} - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "S1").WithLocation(5, 16), - // (9,13): error CS0568: Structs cannot contain explicit parameterless constructors - // S2() { } - Diagnostic(ErrorCode.ERR_StructsCantContainDefaultConstructor, "S2").WithLocation(9, 13) - ); + // (5,16): error CS8026: Feature 'parameterless struct constructors' is not available in C# 5. Please use language version 10.0 or greater. + // public S1() {} + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion5, "S1").WithArguments("parameterless struct constructors", "10.0").WithLocation(5, 16), + // (9,13): error CS8026: Feature 'parameterless struct constructors' is not available in C# 5. Please use language version 10.0 or greater. + // S2() { } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion5, "S2").WithArguments("parameterless struct constructors", "10.0").WithLocation(9, 13), + // (9,13): error CS8938: The parameterless struct constructor must be 'public'. + // S2() { } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "S2").WithLocation(9, 13)); + + comp = CreateCompilation(text); + comp.VerifyDiagnostics( + // (9,13): error CS8918: The parameterless struct constructor must be 'public'. + // S2() { } + Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "S2").WithLocation(9, 13)); } [Fact] diff --git a/src/Compilers/Test/Core/Compilation/CompilationTestDataExtensions.cs b/src/Compilers/Test/Core/Compilation/CompilationTestDataExtensions.cs index 611e8c6f10ac3..11e63278c95ab 100644 --- a/src/Compilers/Test/Core/Compilation/CompilationTestDataExtensions.cs +++ b/src/Compilers/Test/Core/Compilation/CompilationTestDataExtensions.cs @@ -45,29 +45,48 @@ internal static void VerifyIL( return data.Methods.Where(m => !m.Key.IsImplicitlyDeclared).ToImmutableArray(); } - internal static CompilationTestData.MethodData GetMethodData(this CompilationTestData data, string qualifiedMethodName) + private static bool TryGetMethodData(ImmutableDictionary map, string qualifiedMethodName, out CompilationTestData.MethodData methodData) { - var map = data.GetMethodsByName(); + if (map.TryGetValue(qualifiedMethodName, out methodData)) + { + return true; + } + + // caller may not have specified parameter list, so try to match parameterless method + if (map.TryGetValue(qualifiedMethodName + "()", out methodData)) + { + return true; + } - if (!map.TryGetValue(qualifiedMethodName, out var methodData)) + // now try to match single method with any parameter list + var keys = map.Keys.Where(k => k.StartsWith(qualifiedMethodName + "(", StringComparison.Ordinal)); + if (keys.Count() == 1) + { + methodData = map[keys.First()]; + return true; + } + else if (keys.Count() > 1) + { + throw new AmbiguousMatchException( + "Could not determine best match for method named: " + qualifiedMethodName + Environment.NewLine + + string.Join(Environment.NewLine, keys.Select(s => " " + s)) + Environment.NewLine); + } + else { - // caller may not have specified parameter list, so try to match parameterless method - if (!map.TryGetValue(qualifiedMethodName + "()", out methodData)) - { - // now try to match single method with any parameter list - var keys = map.Keys.Where(k => k.StartsWith(qualifiedMethodName + "(", StringComparison.Ordinal)); - if (keys.Count() == 1) - { - methodData = map[keys.First()]; - } - else if (keys.Count() > 1) - { - throw new AmbiguousMatchException( - "Could not determine best match for method named: " + qualifiedMethodName + Environment.NewLine + - string.Join(Environment.NewLine, keys.Select(s => " " + s)) + Environment.NewLine); - } - } + return false; } + } + + internal static bool TryGetMethodData(this CompilationTestData data, string qualifiedMethodName, out CompilationTestData.MethodData methodData) + { + var map = data.GetMethodsByName(); + return TryGetMethodData(map, qualifiedMethodName, out methodData); + } + + internal static CompilationTestData.MethodData GetMethodData(this CompilationTestData data, string qualifiedMethodName) + { + var map = data.GetMethodsByName(); + TryGetMethodData(data, qualifiedMethodName, out var methodData); if (methodData.ILBuilder == null) { diff --git a/src/Compilers/Test/Core/CompilationVerifier.cs b/src/Compilers/Test/Core/CompilationVerifier.cs index 16c909826ab46..74ed7b617a0a0 100644 --- a/src/Compilers/Test/Core/CompilationVerifier.cs +++ b/src/Compilers/Test/Core/CompilationVerifier.cs @@ -267,6 +267,13 @@ public CompilationVerifier VerifyIL( return VerifyILImpl(qualifiedMethodName, expectedIL, realIL, sequencePoints, callerPath, callerLine, escapeQuotes: true, source: source); } + public CompilationVerifier VerifyMissing( + string qualifiedMethodName) + { + Assert.False(_testData.TryGetMethodData(qualifiedMethodName, out _)); + return this; + } + public void VerifyLocalSignature( string qualifiedMethodName, string expectedSignature, From 9057dcf89d6c769fd95ef5094b66bcfd05a05f8f Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 21 Jul 2021 17:53:01 -0700 Subject: [PATCH 2/2] Parameterless struct constructors: Remaining work items (#54811) --- .../Portable/Binder/Binder_Statements.cs | 23 ++- .../CSharp/Portable/BoundTree/BoundNodes.xml | 2 +- .../Compilation/MemberSemanticModel.cs | 4 +- .../Compilation/MethodBodySemanticModel.cs | 4 +- .../Portable/Compiler/MethodCompiler.cs | 25 ++- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 3 - .../Portable/FlowAnalysis/NullableWalker.cs | 27 +-- .../Generated/BoundNodes.xml.Generated.cs | 10 +- .../Symbols/MemberSymbolExtensions.cs | 21 +- .../Semantics/NullableContextTests.cs | 6 +- .../Semantics/NullableReferenceTypesTests.cs | 43 ++++ .../Semantic/Semantics/RecordStructTests.cs | 111 +++++++++- .../Semantics/StructConstructorTests.cs | 42 ++-- .../UninitializedNonNullableFieldTests.cs | 192 +++++++++++++++--- 14 files changed, 409 insertions(+), 104 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 71e10dcfc1e58..44d34bbc55ff1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -3306,7 +3306,7 @@ private BindValueKind GetRequiredReturnValueKind(RefKind refKind) return requiredValueKind; } - public virtual BoundNode BindMethodBody(CSharpSyntaxNode syntax, BindingDiagnosticBag diagnostics) + public virtual BoundNode BindMethodBody(CSharpSyntaxNode syntax, BindingDiagnosticBag diagnostics, bool includesFieldInitializers = false) { switch (syntax) { @@ -3316,7 +3316,7 @@ public virtual BoundNode BindMethodBody(CSharpSyntaxNode syntax, BindingDiagnost case BaseMethodDeclarationSyntax method: if (method.Kind() == SyntaxKind.ConstructorDeclaration) { - return BindConstructorBody((ConstructorDeclarationSyntax)method, diagnostics); + return BindConstructorBody((ConstructorDeclarationSyntax)method, diagnostics, includesFieldInitializers); } return BindMethodBody(method, method.Body, method.ExpressionBody, diagnostics); @@ -3388,9 +3388,10 @@ internal virtual BoundExpressionStatement BindConstructorInitializer(PrimaryCons return constructorInitializer; } - private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, BindingDiagnosticBag diagnostics) + private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, BindingDiagnosticBag diagnostics, bool includesFieldInitializers) { - if (constructor.Initializer == null && constructor.Body == null && constructor.ExpressionBody == null) + ConstructorInitializerSyntax initializer = constructor.Initializer; + if (initializer == null && constructor.Body == null && constructor.ExpressionBody == null) { return null; } @@ -3398,7 +3399,8 @@ private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, Binder bodyBinder = this.GetBinder(constructor); Debug.Assert(bodyBinder != null); - if (constructor.Initializer?.IsKind(SyntaxKind.ThisConstructorInitializer) != true && + bool thisInitializer = initializer?.IsKind(SyntaxKind.ThisConstructorInitializer) == true; + if (!thisInitializer && ContainingType.GetMembersUnordered().OfType().Any()) { var constructorSymbol = (MethodSymbol)this.ContainingMember(); @@ -3406,14 +3408,21 @@ private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, !SynthesizedRecordCopyCtor.IsCopyConstructor(constructorSymbol)) { // Note: we check the constructor initializer of copy constructors elsewhere - Error(diagnostics, ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, constructor.Initializer?.ThisOrBaseKeyword ?? constructor.Identifier); + Error(diagnostics, ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, initializer?.ThisOrBaseKeyword ?? constructor.Identifier); } } + // The `: this()` initializer is ignored when it is a default value type constructor + // and we need to include field initializers into the constructor. + bool skipInitializer = includesFieldInitializers + && thisInitializer + && ContainingType.IsDefaultValueTypeConstructor(initializer); + // Using BindStatement to bind block to make sure we are reusing results of partial binding in SemanticModel return new BoundConstructorMethodBody(constructor, bodyBinder.GetDeclaredLocalsForScope(constructor), - constructor.Initializer == null ? null : bodyBinder.BindConstructorInitializer(constructor.Initializer, diagnostics), + skipInitializer ? new BoundNoOpStatement(constructor, NoOpStatementFlavor.Default) + : initializer == null ? null : bodyBinder.BindConstructorInitializer(initializer, diagnostics), constructor.Body == null ? null : (BoundBlock)bodyBinder.BindStatement(constructor.Body, diagnostics), constructor.ExpressionBody == null ? null : diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index cef075a396654..9b6546a0ecaa2 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -2174,7 +2174,7 @@ - + diff --git a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs index a106610962e31..0de4972cd45df 100644 --- a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs @@ -2456,7 +2456,7 @@ private BoundNode TryGetBoundNodeFromMap(CSharpSyntaxNode node) return null; } - public override BoundNode BindMethodBody(CSharpSyntaxNode node, BindingDiagnosticBag diagnostics) + public override BoundNode BindMethodBody(CSharpSyntaxNode node, BindingDiagnosticBag diagnostics, bool includeInitializersInBody) { BoundNode boundNode = TryGetBoundNodeFromMap(node); @@ -2465,7 +2465,7 @@ public override BoundNode BindMethodBody(CSharpSyntaxNode node, BindingDiagnosti return boundNode; } - boundNode = base.BindMethodBody(node, diagnostics); + boundNode = base.BindMethodBody(node, diagnostics, includeInitializersInBody); return boundNode; } diff --git a/src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs index 7d6df06e584b2..7b425fcee19c8 100644 --- a/src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs @@ -23,14 +23,14 @@ internal readonly struct InitialState { internal readonly CSharpSyntaxNode Syntax; internal readonly BoundNode? Body; - internal readonly ExecutableCodeBinder? Binder; + internal readonly Binder? Binder; internal readonly NullableWalker.SnapshotManager? SnapshotManager; internal readonly ImmutableDictionary? RemappedSymbols; internal InitialState( CSharpSyntaxNode syntax, BoundNode? bodyOpt = null, - ExecutableCodeBinder? binder = null, + Binder? binder = null, NullableWalker.SnapshotManager? snapshotManager = null, ImmutableDictionary? remappedSymbols = null) { diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 228d185fb7ba3..03b62dfb864e3 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -1036,7 +1036,11 @@ private void CompileMethod( getFinalNullableState: true, out processedInitializers.AfterInitializersState); } - body = BindMethodBody(methodSymbol, compilationState, diagsForCurrentMethod, processedInitializers.AfterInitializersState, out importChain, out originalBodyNested, out forSemanticModel); + + body = BindMethodBody(methodSymbol, compilationState, diagsForCurrentMethod, processedInitializers.AfterInitializersState, + includeInitializersInBody && !processedInitializers.BoundInitializers.IsEmpty, + out importChain, out originalBodyNested, out forSemanticModel); + if (diagsForCurrentMethod.HasAnyErrors() && body != null) { body = (BoundBlock)body.WithHasErrors(); @@ -1673,12 +1677,13 @@ private static void GetStateMachineSlotDebugInfo( // NOTE: can return null if the method has no body. internal static BoundBlock BindMethodBody(MethodSymbol method, TypeCompilationState compilationState, BindingDiagnosticBag diagnostics) { - return BindMethodBody(method, compilationState, diagnostics, nullableInitialState: null, out _, out _, out _); + return BindMethodBody(method, compilationState, diagnostics, nullableInitialState: null, includesFieldInitializers: false, out _, out _, out _); } // NOTE: can return null if the method has no body. private static BoundBlock BindMethodBody(MethodSymbol method, TypeCompilationState compilationState, BindingDiagnosticBag diagnostics, NullableWalker.VariableState nullableInitialState, + bool includesFieldInitializers, out ImportChain importChain, out bool originalBodyNested, out MethodBodySemanticModel.InitialState forSemanticModel) { @@ -1713,12 +1718,11 @@ syntaxNode is ConstructorDeclarationSyntax constructorSyntax && return null; } - ExecutableCodeBinder bodyBinder = sourceMethod.TryGetBodyBinder(); + Binder bodyBinder = sourceMethod.TryGetBodyBinder(); if (bodyBinder != null) { importChain = bodyBinder.ImportChain; - - BoundNode methodBody = bodyBinder.BindMethodBody(syntaxNode, diagnostics); + BoundNode methodBody = bodyBinder.BindMethodBody(syntaxNode, diagnostics, includesFieldInitializers); BoundNode methodBodyForSemanticModel = methodBody; NullableWalker.SnapshotManager snapshotManager = null; ImmutableDictionary remappedSymbols = null; @@ -1760,9 +1764,15 @@ syntaxNode is ConstructorDeclarationSyntax constructorSyntax && var constructor = (BoundConstructorMethodBody)methodBody; body = constructor.BlockBody ?? constructor.ExpressionBody; - if (constructor.Initializer != null) + if (constructor.Initializer is BoundNoOpStatement) + { + // We have field initializers and `: this()` is a default value type constructor. + Debug.Assert(body is not null); + return body; + } + else if (constructor.Initializer is BoundExpressionStatement expressionStatement) { - ReportCtorInitializerCycles(method, constructor.Initializer.Expression, compilationState, diagnostics); + ReportCtorInitializerCycles(method, expressionStatement.Expression, compilationState, diagnostics); if (body == null) { @@ -1778,6 +1788,7 @@ syntaxNode is ConstructorDeclarationSyntax constructorSyntax && } else { + Debug.Assert(constructor.Initializer is null); Debug.Assert(constructor.Locals.IsEmpty); } break; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 69069edb84c27..df4d53bdf8607 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -442,9 +442,6 @@ protected ImmutableArray Analyze(ref bool badRegion, Optional 0) + if (parameter.RefKind != RefKind.Out) { - var state = GetParameterState(parameterType, parameter.FlowAnalysisAnnotations).State; - this.State[slot] = state; - if (EmptyStructTypeCache.IsTrackableStructType(parameterType.Type)) + int slot = GetOrCreateSlot(parameter); + + Debug.Assert(!IsConditionalState); + if (slot > 0) { - InheritNullableStateOfTrackableStruct( - parameterType.Type, - slot, - valueSlot: -1, - isDefaultValue: parameter.ExplicitDefaultConstantValue?.IsNull == true); + var state = GetParameterState(parameterType, parameter.FlowAnalysisAnnotations).State; + this.State[slot] = state; + if (EmptyStructTypeCache.IsTrackableStructType(parameterType.Type)) + { + InheritNullableStateOfTrackableStruct( + parameterType.Type, + slot, + valueSlot: -1, + isDefaultValue: parameter.ExplicitDefaultConstantValue?.IsNull == true); + } } } } @@ -3071,7 +3075,6 @@ private void VisitObjectOrDynamicObjectCreation( { var boundObjectCreationExpression = node as BoundObjectCreationExpression; var constructor = boundObjectCreationExpression?.Constructor; - // PROTOTYPE: Test with structs with parameterless constructors. bool isDefaultValueTypeConstructor = constructor?.IsDefaultValueTypeConstructor(requireZeroInit: true) == true; if (EmptyStructTypeCache.IsTrackableStructType(type)) diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index b6df30a1a81c0..cbcab61dd1efe 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -7985,7 +7985,7 @@ public BoundNonConstructorMethodBody Update(BoundBlock? blockBody, BoundBlock? e internal sealed partial class BoundConstructorMethodBody : BoundMethodBodyBase { - public BoundConstructorMethodBody(SyntaxNode syntax, ImmutableArray locals, BoundExpressionStatement? initializer, BoundBlock? blockBody, BoundBlock? expressionBody, bool hasErrors = false) + public BoundConstructorMethodBody(SyntaxNode syntax, ImmutableArray locals, BoundStatement? initializer, BoundBlock? blockBody, BoundBlock? expressionBody, bool hasErrors = false) : base(BoundKind.ConstructorMethodBody, syntax, blockBody, expressionBody, hasErrors || initializer.HasErrors() || blockBody.HasErrors() || expressionBody.HasErrors()) { @@ -7998,11 +7998,11 @@ public BoundConstructorMethodBody(SyntaxNode syntax, ImmutableArray public ImmutableArray Locals { get; } - public BoundExpressionStatement? Initializer { get; } + public BoundStatement? Initializer { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitConstructorMethodBody(this); - public BoundConstructorMethodBody Update(ImmutableArray locals, BoundExpressionStatement? initializer, BoundBlock? blockBody, BoundBlock? expressionBody) + public BoundConstructorMethodBody Update(ImmutableArray locals, BoundStatement? initializer, BoundBlock? blockBody, BoundBlock? expressionBody) { if (locals != this.Locals || initializer != this.Initializer || blockBody != this.BlockBody || expressionBody != this.ExpressionBody) { @@ -11055,7 +11055,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor } public override BoundNode? VisitConstructorMethodBody(BoundConstructorMethodBody node) { - BoundExpressionStatement? initializer = (BoundExpressionStatement?)this.Visit(node.Initializer); + BoundStatement? initializer = (BoundStatement?)this.Visit(node.Initializer); BoundBlock? blockBody = (BoundBlock?)this.Visit(node.BlockBody); BoundBlock? expressionBody = (BoundBlock?)this.Visit(node.ExpressionBody); return node.Update(node.Locals, initializer, blockBody, expressionBody); @@ -13426,7 +13426,7 @@ public NullabilityRewriter(ImmutableDictionary locals = GetUpdatedArray(node, node.Locals); - BoundExpressionStatement? initializer = (BoundExpressionStatement?)this.Visit(node.Initializer); + BoundStatement? initializer = (BoundStatement?)this.Visit(node.Initializer); BoundBlock? blockBody = (BoundBlock?)this.Visit(node.BlockBody); BoundBlock? expressionBody = (BoundBlock?)this.Visit(node.ExpressionBody); return node.Update(locals, initializer, blockBody, expressionBody); diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs index a7e5623820c57..e08bee6499ab7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs @@ -392,22 +392,23 @@ internal static bool HasThisConstructorInitializer(this MethodSymbol method, out internal static bool IncludeFieldInitializersInBody(this MethodSymbol methodSymbol) { + // A struct constructor that calls ": this()" will need to include field initializers if the + // parameterless constructor is a synthesized default constructor that is not emitted. + return methodSymbol.IsConstructor() - && !(methodSymbol.HasThisConstructorInitializer(out var initializerSyntax) && !isDefaultValueTypeConstructor(methodSymbol.ContainingType, initializerSyntax)) + && !(methodSymbol.HasThisConstructorInitializer(out var initializerSyntax) && !methodSymbol.ContainingType.IsDefaultValueTypeConstructor(initializerSyntax)) && !(methodSymbol is SynthesizedRecordCopyCtor) // A record copy constructor is special, regular initializers are not supposed to be executed by it. && !Binder.IsUserDefinedRecordCopyConstructor(methodSymbol); + } - // A struct constructor that calls ": this()" will need to include field initializers if the - // parameterless constructor is a synthesized default constructor that is not emitted. - static bool isDefaultValueTypeConstructor(NamedTypeSymbol containingType, ConstructorInitializerSyntax initializerSyntax) + internal static bool IsDefaultValueTypeConstructor(this NamedTypeSymbol type, ConstructorInitializerSyntax initializerSyntax) + { + if (initializerSyntax.ArgumentList.Arguments.Count > 0) { - if (initializerSyntax.ArgumentList.Arguments.Count > 0) - { - return false; - } - var constructor = containingType.InstanceConstructors.SingleOrDefault(m => m.ParameterCount == 0); - return constructor?.IsDefaultValueTypeConstructor(requireZeroInit: true) == true; + return false; } + var constructor = type.InstanceConstructors.SingleOrDefault(m => m.ParameterCount == 0); + return constructor?.IsDefaultValueTypeConstructor(requireZeroInit: true) == true; } /// diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs index a68dccf4aca19..5c80ffac51c0b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs @@ -961,11 +961,11 @@ struct S object F1; S(object obj) : this() { } }"; - // PROTOTYPE: NullableWalker.Scan() overwrites initial field state when calling EnterParameter(methodThisParameter). - verify(source, expectedAnalyzedKeys: new[] { ".ctor" }/*, + verify(source, expectedAnalyzedKeys: new[] { ".ctor" }, // (6,5): warning CS8618: Non-nullable field 'F1' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. // S(object obj) : this() { } - Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "F1").WithLocation(6, 5)*/); + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "F1").WithLocation(6, 5) + ); source = @"#pragma warning disable 169 diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 5e4c044ae1c62..1b421630ec390 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -58057,6 +58057,49 @@ static void Main() comp.VerifyDiagnostics(); } + [Fact] + public void ObjectInitializer_ParameterlessStructConstructor() + { + var src = @" +#nullable enable + +var s1 = new S1() { }; +s1.field.ToString(); + +var s2 = new S2() { }; +s2.field.ToString(); // 1 + +var s3 = new S3() { }; +s3.field.ToString(); + +public struct S1 +{ + public string field; + + public S1() + { + field = string.Empty; + } +} + +public struct S2 +{ + public string field; +} + +public struct S3 +{ + public string field = string.Empty; +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (8,1): warning CS8602: Dereference of a possibly null reference. + // s2.field.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s2.field").WithLocation(8, 1) + ); + } + [Fact] public void IdentityConversion_ObjectElementInitializerArgumentsOrder() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs index 7cd09257b8b16..d3160c910d8ae 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs @@ -1048,6 +1048,96 @@ internal S2() { } Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "S2").WithLocation(7, 14)); } + [Fact] + public void TypeDeclaration_ParameterlessConstructor_OtherConstructors() + { + var src = @" +record struct S1 +{ + public S1() { } + S1(object o) { } // ok because no record parameter list +} +record struct S2 +{ + S2(object o) { } +} +record struct S3() +{ + S3(object o) { } // 1 +} +record struct S4() +{ + S4(object o) : this() { } +} +record struct S5(object o) +{ + public S5() { } // 2 +} +record struct S6(object o) +{ + public S6() : this(null) { } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (13,5): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // S3(object o) { } // 1 + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "S3").WithLocation(13, 5), + // (21,12): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S5() { } // 2 + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "S5").WithLocation(21, 12) + ); + } + + [Fact] + public void TypeDeclaration_ParameterlessConstructor_Initializers() + { + var src = @" +var s1 = new S1(); +var s2 = new S2(null); +var s2b = new S2(); +var s3 = new S3(); +var s4 = new S4(new object()); +var s5 = new S5(); +var s6 = new S6(""s6.other""); + +System.Console.Write((s1.field, s2.field, s2b.field is null, s3.field, s4.field, s5.field, s6.field, s6.other)); + +record struct S1 +{ + public string field = ""s1""; + public S1() { } +} +record struct S2 +{ + public string field = ""s2""; + public S2(object o) { } +} +record struct S3() +{ + public string field = ""s3""; +} +record struct S4 +{ + public string field = ""s4""; + public S4(object o) : this() { } +} +record struct S5() +{ + public string field = ""s5""; + public S5(object o) : this() { } +} +record struct S6(string other) +{ + public string field = ""s6.field""; + public S6() : this(""ignored"") { } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "(s1, s2, True, s3, s4, s5, s6.field, s6.other)"); + } + [Fact] public void TypeDeclaration_InstanceInitializers() { @@ -1075,9 +1165,6 @@ public record struct S comp.VerifyDiagnostics(); } - // PROTOTYPE: Verify initializers are executed from synthesized and explicit parameterless constructors. - // PROTOTYPE: Verify explicit parameterless constructor calls 'this(...)' for primary constructor. - [Fact] public void TypeDeclaration_NoDestructor() { @@ -2986,12 +3073,15 @@ public record struct X(int a) public void ParameterlessConstructor() { var src = @" +System.Console.Write(new C().Property); + record struct C() { - int Property { get; set; } = 42; + public int Property { get; set; } = 42; }"; var comp = CreateCompilation(src); comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "42"); } [Fact] @@ -3447,7 +3537,18 @@ static void Main() // case C(): Diagnostic(ErrorCode.ERR_MissingDeconstruct, "()").WithArguments("C", "0").WithLocation(8, 19)); - Assert.Null(comp.GetMember("C.Deconstruct")); + AssertEx.Equal(new[] { + "C..ctor()", + "void C.M(C c)", + "void C.Main()", + "System.String C.ToString()", + "System.Boolean C.PrintMembers(System.Text.StringBuilder builder)", + "System.Boolean C.op_Inequality(C left, C right)", + "System.Boolean C.op_Equality(C left, C right)", + "System.Int32 C.GetHashCode()", + "System.Boolean C.Equals(System.Object obj)", + "System.Boolean C.Equals(C other)" }, + comp.GetMember("C").GetMembers().ToTestDisplayStrings()); } [Fact] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs index 16ba48f44c369..27977bcea55be 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs @@ -478,12 +478,10 @@ static void Main() } }"; - // PROTOTYPE: S0(object) should set Value after initobj, not before, - // and should report: new S0(null).Value: 1. - var verifier = CompileAndVerify(source, expectedOutput: -@"new S0().Value: 0 + var verifier = CompileAndVerify(source, expectedOutput: @" +new S0().Value: 0 One() -new S0(null).Value: 0 +new S0(null).Value: 1 One() new S1().Value: 1 One() @@ -495,14 +493,12 @@ static void Main() verifier.VerifyMissing("S0..ctor()"); verifier.VerifyIL("S0..ctor(object)", @"{ - // Code size 19 (0x13) + // Code size 12 (0xc) .maxstack 2 IL_0000: ldarg.0 IL_0001: call ""int Program.One()"" IL_0006: stfld ""int S0.Value"" - IL_000b: ldarg.0 - IL_000c: initobj ""S0"" - IL_0012: ret + IL_000b: ret }"); verifier.VerifyIL("S1..ctor()", @"{ @@ -598,20 +594,17 @@ static void Main() "); verifier.VerifyMissing("S0..ctor()"); - // PROTOTYPE: S0(object) should set Value twice after initobj, not once before, once after. verifier.VerifyIL("S0..ctor(object)", @"{ - // Code size 30 (0x1e) + // Code size 23 (0x17) .maxstack 2 IL_0000: ldarg.0 IL_0001: call ""int Program.One()"" IL_0006: stfld ""int S0.Value"" IL_000b: ldarg.0 - IL_000c: initobj ""S0"" - IL_0012: ldarg.0 - IL_0013: call ""int Program.Two()"" - IL_0018: stfld ""int S0.Value"" - IL_001d: ret + IL_000c: call ""int Program.Two()"" + IL_0011: stfld ""int S0.Value"" + IL_0016: ret }"); verifier.VerifyIL("S1..ctor()", @"{ @@ -718,20 +711,17 @@ static void Main() "); verifier.VerifyMissing("S0..ctor()"); - // PROTOTYPE: S0(object) should set Value twice after initobj, not once before, once after. verifier.VerifyIL("S0..ctor(object)", @"{ - // Code size 26 (0x1a) + // Code size 19 (0x13) .maxstack 2 IL_0000: ldarg.0 IL_0001: ldc.i4.0 IL_0002: stfld ""int S0.Value"" IL_0007: ldarg.0 - IL_0008: initobj ""S0"" - IL_000e: ldarg.0 - IL_000f: call ""int Program.Two()"" - IL_0014: stfld ""int S0.Value"" - IL_0019: ret + IL_0008: call ""int Program.Two()"" + IL_000d: stfld ""int S0.Value"" + IL_0012: ret }"); verifier.VerifyIL("S1..ctor()", @"{ @@ -1581,9 +1571,15 @@ struct S3 }"; var comp = CreateCompilation(source); comp.VerifyDiagnostics( + // (10,12): warning CS8618: Non-nullable field 'F1' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // public S1() { } + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("field", "F1").WithLocation(10, 12), // (10,12): error CS0171: Field 'S1.F1' must be fully assigned before control is returned to the caller // public S1() { } Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.F1").WithLocation(10, 12), + // (16,5): warning CS8618: Non-nullable field 'F2' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // S2(object? obj) { } + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S2").WithArguments("field", "F2").WithLocation(16, 5), // (16,5): error CS0171: Field 'S2.F2' must be fully assigned before control is returned to the caller // S2(object? obj) { } Diagnostic(ErrorCode.ERR_UnassignedThis, "S2").WithArguments("S2.F2").WithLocation(16, 5), diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs index f12534e5449fa..44a2528b87e2b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs @@ -875,47 +875,188 @@ public S1(string s1, string s2) : this(s1) public void StructConstructorInitializer_UninitializedField() { var source = @" +#nullable enable + +public struct S1 +{ + public string field; + + public S1(object obj) : this() + { + field.ToString(); // 1 + } + + public S1(object obj1, object obj2) : this() // 2 + { + } + + public S1(string s1, string s2) : this(s1) + { + field.ToString(); + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (10,9): warning CS8602: Dereference of a possibly null reference. + // field.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "field").WithLocation(10, 9), + // (13,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // public S1(object obj1, object obj2) : this() // 2 + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("field", "field").WithLocation(13, 12)); + } + + [Fact, WorkItem(48574, "https://github.com/dotnet/roslyn/issues/48574")] + public void StructConstructorInitializer_InitializedFieldViaParameterlessConstructor() + { + var source = @" +#nullable enable + +new S1(new object()); + struct S1 { - public string field; // 0 - public S1(string s) // 1 + public string field; + + public S1() { - field.ToString(); // 2 + field = ""ok ""; } public S1(object obj) : this() { - field.ToString(); // 3 + System.Console.Write(field); } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "ok"); + + verifier.VerifyIL("S1..ctor(object)", @" +{ + // Code size 18 (0x12) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""S1..ctor()"" + IL_0006: ldarg.0 + IL_0007: ldfld ""string S1.field"" + IL_000c: call ""void System.Console.Write(string)"" + IL_0011: ret +} +"); + } - public S1(object obj1, object obj2) : this() // 4 + [Fact, WorkItem(48574, "https://github.com/dotnet/roslyn/issues/48574")] + public void StructConstructorInitializer_UninitializedFieldWithParameterlessConstructor() + { + var source = @" +#nullable enable + +struct S1 +{ + public string field; + + public S1() { + field = ""ok ""; } - public S1(string s1, string s2) : this(s1) + public S1(string s) // 1, 2 { - field.ToString(); + System.Console.Write(field); // 3 } } "; - // PROTOTYPE: NullableWalker.Scan() overwrites initial field state when calling EnterParameter(methodThisParameter). - var comp = CreateCompilation(source, options: WithNullableEnable()); + var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (4,19): warning CS0649: Field 'S1.field' is never assigned to, and will always have its default value null - // public string field; // 0 - Diagnostic(ErrorCode.WRN_UnassignedInternalField, "field").WithArguments("S1.field", "null").WithLocation(4, 19), - // (5,12): error CS0171: Field 'S1.field' must be fully assigned before control is returned to the caller - // public S1(string s) // 1 - Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.field").WithLocation(5, 12), - // (7,9): error CS0170: Use of possibly unassigned field 'field' - // field.ToString(); // 2 - Diagnostic(ErrorCode.ERR_UseDefViolationField, "field").WithArguments("field").WithLocation(7, 9)/*, - // (12,9): warning CS8602: Dereference of a possibly null reference. - // field.ToString(); // 3 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "field").WithLocation(12, 9), - // (15,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. - // public S1(object obj1, object obj2) : this() // 4 - Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("field", "field").WithLocation(15, 12)*/); + // (13,12): warning CS8618: Non-nullable field 'field' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // public S1(string s) // 1, 2 + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S1").WithArguments("field", "field").WithLocation(13, 12), + // (13,12): error CS0171: Field 'S1.field' must be fully assigned before control is returned to the caller + // public S1(string s) // 1, 2 + Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.field").WithLocation(13, 12), + // (15,30): error CS0170: Use of possibly unassigned field 'field' + // System.Console.Write(field); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolationField, "field").WithArguments("field").WithLocation(15, 30) + ); + } + + [Fact, WorkItem(48574, "https://github.com/dotnet/roslyn/issues/48574")] + public void StructConstructorInitializer_InitializedFieldViaInitializer() + { + var source = @" +#nullable enable + +new S1(); +new S1(new object()); +new S1(new object(), new object()); + +struct S1 +{ + public string field = ""ok ""; + + public S1() + { + System.Console.Write(field); + } + + public S1(object s) + { + System.Console.Write(field); + } + + public S1(object obj, object obj2) : this() + { + System.Console.Write(field); + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(comp, expectedOutput: "ok ok ok ok"); + + verifier.VerifyIL("S1..ctor()", @" +{ + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldstr ""ok "" + IL_0006: stfld ""string S1.field"" + IL_000b: ldarg.0 + IL_000c: ldfld ""string S1.field"" + IL_0011: call ""void System.Console.Write(string)"" + IL_0016: ret +} +"); + + verifier.VerifyIL("S1..ctor(object)", @" +{ + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldstr ""ok "" + IL_0006: stfld ""string S1.field"" + IL_000b: ldarg.0 + IL_000c: ldfld ""string S1.field"" + IL_0011: call ""void System.Console.Write(string)"" + IL_0016: ret +} +"); + + verifier.VerifyIL("S1..ctor(object, object)", @" +{ + // Code size 18 (0x12) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""S1..ctor()"" + IL_0006: ldarg.0 + IL_0007: ldfld ""string S1.field"" + IL_000c: call ""void System.Console.Write(string)"" + IL_0011: ret +} +"); } [Fact, WorkItem(43215, "https://github.com/dotnet/roslyn/issues/43215")] @@ -1530,6 +1671,9 @@ internal S(string s) // (6,14): warning CS8618: Non-nullable property 'P' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. // internal S(string s) Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("property", "P").WithLocation(6, 14), + // (6,14): warning CS8618: Non-nullable field 'F' must contain a non-null value when exiting constructor. Consider declaring the field as nullable. + // internal S(string s) + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "S").WithArguments("field", "F").WithLocation(6, 14), // (6,14): error CS0843: Auto-implemented property 'S.P' must be fully assigned before control is returned to the caller. // internal S(string s) Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S").WithArguments("S.P").WithLocation(6, 14),