Skip to content

Allow public parameterless struct constructors declared in source#51679

Merged
cston merged 25 commits intodotnet:features/struct-ctorsfrom
cston:struct-ctor
Jul 12, 2021
Merged

Allow public parameterless struct constructors declared in source#51679
cston merged 25 commits intodotnet:features/struct-ctorsfrom
cston:struct-ctor

Conversation

@cston
Copy link
Contributor

@cston cston commented Mar 4, 2021

Support public parameterless struct constructors and declared in source, and support struct field initializers.

Proposal: parameterless-struct-constructors
Test plan: #51698

@cston cston requested a review from a team as a code owner March 4, 2021 22:52
@ghost ghost added the Area-Compilers label Mar 4, 2021
@AlekseyTs
Copy link
Contributor

Allow parameterless struct constructors from source or metadata

I thought we already handled parameter-less struct constructors defined in metadata and made use of them. Could you please elaborate what behavior difference, if any, with regard to these constructors in this PR?

@cston
Copy link
Contributor Author

cston commented Mar 5, 2021

I thought we already handled parameter-less struct constructors defined in metadata and made use of them.

You're right, the compiler was using public parameterless constructors from metadata.

But private parameterless constructors were ignored, so for a struct with a private parameterless constructor, the compiler would emit initobj when instantiating the struct rather than reporting an error, and type substitution of a type parameter with new() constraint was allowed.

There's a question of whether the compiler should recognize cases where a struct with inaccessible parameterless constructor is being instantiated directly, or substituted for a type parameter with new() constraint, and report an error or a warning for those cases? Or perhaps the compiler should warn, when a struct with non-public parameterless constructor is defined in source, that the constructor may be ignored.


In reply to: 791037969 [](ancestors = 791037969)

private static bool HasPublicParameterlessConstructor(NamedTypeSymbol type)
private static bool? HasPublicParameterlessConstructor(NamedTypeSymbol type)
{
Debug.Assert(type.TypeKind == TypeKind.Class);
Copy link
Member

@Youssef1313 Youssef1313 Mar 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of deleting the assertion, should it be updated to type.TypeKind is TypeKind.Class or TypeKind.Struct? #Resolved

@jcouv jcouv self-assigned this Mar 5, 2021
@cston cston marked this pull request as draft March 10, 2021 00:52
@cston cston force-pushed the struct-ctor branch 2 times, most recently from 2b1524b to f8e3948 Compare March 11, 2021 17:06
@cston cston marked this pull request as ready for review March 11, 2021 19:57
@jcouv
Copy link
Member

jcouv commented Mar 31, 2021

There are some legitimate test failures.


In reply to: 811462702


In reply to: 811462702

@cston cston changed the title Allow parameterless struct constructors from source or metadata Allow public parameterless struct constructors declared in source May 11, 2021

Cci.IMethodReference Cci.ICustomAttribute.Constructor(EmitContext context, bool reportDiagnostics)
{
if (this.AttributeConstructor.IsDefaultValueTypeConstructor())
Copy link
Contributor Author

@cston cston May 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.AttributeConstructor.IsDefaultValueTypeConstructor()

The change ensures we report ERR_NotAnAttributeClass even in the case of an explicit parameterless constructor. #Closed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we allow such attributes when the default struct constructor exists for real (ie. is emitted)?

@cston
Copy link
Contributor Author

cston commented May 21, 2021

@dotnet/roslyn-compiler, please review.

@cston cston requested review from a team and 333fred June 1, 2021 16:07
/// <summary>
/// Returns true if the type is defined in source and contains field initializers.
/// </summary>
internal virtual bool HasFieldInitializers() => false;
Copy link
Member

@333fred 333fred Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like this should follow our general pattern of being abstract here, rather than virtual. #Closed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, I don't see any changes to WrappedNameTypeSymbol, which I would expect to see.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method is only valid when called on a definition. Added an assert.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-opened this for the question about abstract versus virtual.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind. I'm slow on the uptake.

{
public static void M(in int i)
{
System.Console.WriteLine(i);
Copy link
Member

@333fred 333fred Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really certain what this is testing: is the constructor called at runtime or not? Consider putting some side-effecting thing in to inform. #Closed

Copy link
Contributor Author

@cston cston Jul 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're testing the struct type can be used as an "attribute type" if the type has a public constructor. (The attribute type is not instantiated at runtime.) Added a comment to the test.

"System.ITupleInternal.Size" },
m1Tuple.MemberNames.ToArray());
"Item1",
"Item2",
Copy link
Member

@333fred 333fred Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider reverting the changes in this file. #Closed

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: false));
Copy link
Member

@333fred 333fred Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these asserting false? Shouldn't existing code be true? #Closed

CompileAndVerify(sourceB, references: new[] { refA }, expectedOutput:
$@"True
True
{ExecutionConditionUtil.IsCoreClr}"); // Desktop framework ignores constructor in Activator.CreateInstance<T>() where T : struct.
Copy link
Member

@333fred 333fred Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Desktop framework ignores constructor in Activator.CreateInstance() where T : struct.

Per LDM today, this isn't quite correct. #Closed

{{
{accessibility} S() {{ }}
}}";
var comp = CreateCompilation(sourceA, parseOptions: TestOptions.RegularPreview);
Copy link
Member

@333fred 333fred Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this was supposed to be a compiler error? #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an error but the error was not being verified since the focus of the test was using the resulting type. I've added VerifyDiagnostics() so the expected behavior is clear.

}
struct S1
{
internal bool Initialized = false;
Copy link
Member

@333fred 333fred Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialized

Consider using a different type here. I can't tell whether new S1().Initialized ran the field initializer or not. #Closed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a general comment for all the ThisInitializer tests, I can't reason about the behavior of the existing code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cston this appears unaddressed.

Copy link
Contributor Author

@cston cston Jul 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

{
static void Main()
{
Console.WriteLine(new A<object>.S1().X);
Copy link
Member

@333fred 333fred Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S1

Consider verifying HasFieldInitializers() on these types. I'd expect that with the current implementation, we'd see false on the SubstitutedNestedNamedTypeSymbol invovled since there is no override of the default virtual base. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect that with the current implementation, we'd see false on the SubstitutedNestedNamedTypeSymbol ...

HasFieldInitializers() should only be called on a definition. Added an assert to that method to make it clear.

@333fred
Copy link
Member

333fred commented Jun 7, 2021

Done review pass (commit 17)


In reply to: 856257577

@cston
Copy link
Contributor Author

cston commented Jul 8, 2021

@jcouv, @333fred, all feedback has been addressed, thanks.

@333fred
Copy link
Member

333fred commented Jul 8, 2021

Done review pass (commit 21).

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (commit 22)

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (commit 23)

@jcouv jcouv self-requested a review July 10, 2021 04:54
switch (typeArgument.TypeKind)
{
case TypeKind.Struct:
return HasPublicParameterlessConstructor((NamedTypeSymbol)typeArgument, synthesizedIfMissing: true);
Copy link
Member

@jcouv jcouv Jul 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, do you know why the class case has a type argument check, but the struct case doesn't need one? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typeArgument is the struct type and cannot be abstract.

if ((object)node.Constructor == null ||
(node.Arguments.Length == 0 && !node.Type.IsStructType()) ||
node.Constructor.IsDefaultValueTypeConstructor())
node.Constructor.IsDefaultValueTypeConstructor(requireZeroInit: false))
Copy link
Member

@jcouv jcouv Jul 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like expression trees scenarios are handled already.
Could you clarify the follow-up work item tracked in test plan? "Expression tree: synthesized parameterless constructor is not called in Expression e = () => new S();" #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call should have used requireZeroInit: true. Fixed and checked off work item in test plan.

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM Thanks (iteration 25)

@cston cston merged commit bbfeab5 into dotnet:features/struct-ctors Jul 12, 2021
@cston cston deleted the struct-ctor branch July 12, 2021 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants