Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expression.Default and Expression.New with structs with parameterless constructors #57034

Merged
merged 8 commits into from
Aug 13, 2021

Conversation

cston
Copy link
Member

@cston cston commented Aug 7, 2021

Fixes #45397.

First commit based on GrabYourPitchforks@1a22b6a.

Relates to parameterless struct constructors: dotnet/roslyn#51698 (test plan)

@ghost
Copy link

ghost commented Aug 7, 2021

Tagging subscribers to this area: @cston
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #45397.

First commit based on GrabYourPitchforks@1a22b6a.

Author: cston
Assignees: -
Labels:

area-System.Linq.Expressions

Milestone: -

@cston cston force-pushed the 45397 branch 2 times, most recently from e870d78 to c661b31 Compare August 9, 2021 22:13
@@ -424,7 +424,7 @@ public override int Run(InterpretedFrame frame)

try
{
value = Activator.CreateInstance(_type);
value = RuntimeHelpers.GetUninitializedObject(_type);
Copy link
Member Author

Choose a reason for hiding this comment

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

@GrabYourPitchforks, does RuntimeHelpers.GetUnitializedObject(_type) support Nullable<T> or should this method use Activator.CreateInstance(_type) or perhaps just null if _type.IsNullableType()?

Copy link
Member

Choose a reason for hiding this comment

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

GetUninitializedObject allows typeof(Nullable<T>) to be passed in. It extracts the T and returns a boxed default(T).

// If it is a nullable, return the underlying type instead.
if (pMT->IsNullable())
pMT = pMT->GetInstantiation()[0].GetMethodTable();

Copy link
Member Author

@cston cston Aug 11, 2021

Choose a reason for hiding this comment

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

GetUninitializedObject allows typeof(Nullable<T>) to be passed in. It extracts the T and returns a boxed default(T).

Thanks. For MutableBox and MutableValue, it seems the value should be (T?)null rather than (T?)default(T).

@cston cston marked this pull request as ready for review August 10, 2021 00:19
Copy link
Member

@eerhardt eerhardt 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 for fixing this.

Just a request to update the suppress message justifications.

@@ -26,7 +27,7 @@ internal DefaultValueInstruction(Type type)
Justification = "_type is a ValueType. You can always create an instance of a ValueType.")]
Copy link
Member

@eerhardt eerhardt Aug 10, 2021

Choose a reason for hiding this comment

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

Can we update these justifications to match the new code? (here and below)

Suggested change
Justification = "_type is a ValueType. You can always create an instance of a ValueType.")]
Justification = "_type is a ValueType. You can always get an uninitialized ValueType.")]

@cston cston requested a review from a team August 11, 2021 23:13
@cston
Copy link
Member Author

cston commented Aug 12, 2021

@dotnet/roslyn-compiler, please review.

public override int Run(InterpretedFrame frame)
{
if (frame.Peek() == null)
{
frame.Pop();
frame.Push(Activator.CreateInstance(_defaultValueType));
frame.Push(RuntimeHelpers.GetUninitializedObject(_defaultValueType));
Copy link
Member

@jaredpar jaredpar Aug 12, 2021

Choose a reason for hiding this comment

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

There is no assertion in the ctor here that _defaultValueType is not nullable as there is in the ctor for DefaultValueInstruction. Would expect an assert in both places. #Resolved

Comment on lines +429 to +431
value = _type.IsNullableType() ?
Activator.CreateInstance(_type) :
RuntimeHelpers.GetUninitializedObject(_type);
Copy link
Member

@jaredpar jaredpar Aug 12, 2021

Choose a reason for hiding this comment

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

This pattern is repeated, did you consider adding a helper?

Copy link
Member Author

Choose a reason for hiding this comment

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

Will leave as is to avoid moving suppression messages, etc.

@@ -439,6 +439,13 @@ public override int Run(InterpretedFrame frame)

public override string InstructionName => "InitMutableBox";
}

private static object? GetUninitializedObjectOrNull(Type type)
Copy link
Member

@eerhardt eerhardt Aug 12, 2021

Choose a reason for hiding this comment

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

You are going to have to move the suppress messages now... #Resolved

@@ -23,10 +24,10 @@ internal DefaultValueInstruction(Type type)
public override string InstructionName => "DefaultValue";

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077:UnrecognizedReflectionPattern",
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we still get a warning after this change if the attribute is removed?

Copy link
Member

Choose a reason for hiding this comment

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

You should, yes, because RuntimeHelpers.GetUninitializedObject is annotated as requiring Constructors on the Type:

public static object GetUninitializedObject(
// This API doesn't call any constructors, but the type needs to be seen as constructed.
// A type is seen as constructed if a constructor is kept.
// This obviously won't cover a type with no constructor. Reference types with no
// constructor are an academic problem. Valuetypes with no constructors are a problem,
// but IL Linker currently treats them as always implicitly boxed.
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
Type type)

public override int Run(InterpretedFrame frame)
{
object? value;

try
{
value = Activator.CreateInstance(_type);
value = _type.IsNullableType() ?
Copy link
Contributor

@RikkiGibson RikkiGibson Aug 13, 2021

Choose a reason for hiding this comment

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

It wasn't clear to me why we want to use Activator.CreateInstance when the type is a System.Nullable<T>. Does something different/unexpected happen if we use RuntimeHelpers.GetUninitializedObject(_type) here unconditionally? #Resolved

Copy link
Member Author

@cston cston Aug 13, 2021

Choose a reason for hiding this comment

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

Does something different/unexpected happen if we use RuntimeHelpers.GetUninitializedObject(_type) here unconditionally?

Yes, GetUninitializedObject(_type) will return (ValueType?)default(ValueType) - that is, a boxed instance of ValueType - rather than null when _type == typeof(ValueType?).

{
// f = () =>
// {
// ValueTypeWithParameterlessConstructorThatThrows p;
Copy link
Contributor

@RikkiGibson RikkiGibson Aug 13, 2021

Choose a reason for hiding this comment

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

I feel like I might not understand the semantics of this interpreter. Do we just implicitly initialize locals to default? Do you think it would improve clarity to throw in an = default here? #ByDesign

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, we implicitly initialize the value to default in MutableValue. I was not including = default to ensure we caught regressions where the behavior of MutableValue changes. Since we're using a value type that throws an exception in the parameterless constructor, that might also catch the same regressions though.


[Theory]
[ClassData(typeof(CompilationTypes))]
public static void GetValueOrDefault(bool useInterpreter)
Copy link
Contributor

@RikkiGibson RikkiGibson Aug 13, 2021

Choose a reason for hiding this comment

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

I didn't understand whether there was significance to this being static. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

It looks like we have a mix of static and instance tests. The only reason this one is static is it was originally added in a location where others were static. Removed the static here for consistency.

@cston cston merged commit 8ca505f into dotnet:main Aug 13, 2021
@cston cston deleted the 45397 branch August 13, 2021 04:23
@ghost ghost locked as resolved and limited conversation to collaborators Sep 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
5 participants