-
Notifications
You must be signed in to change notification settings - Fork 7.8k
DotNetTypeAdapter - treat init-only properties as non-settable #16520
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
DotNetTypeAdapter - treat init-only properties as non-settable #16520
Conversation
|
@SeeminglyScience @JustinGrote Please review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! I echo @iSazonov's comment about maybe omitting the initOnly field. Did the hashtable conversion path (aka object init) code path not need any changes?
Co-authored-by: Patrick Meinecke <SeeminglyScience@users.noreply.github.com>
Co-authored-by: Patrick Meinecke <SeeminglyScience@users.noreply.github.com>
I'm reluctant since it makes the adapter code more readable to have the distinction IMO, but I'm happy to remove it if there are any arguments better than "technically it's not strictly needed" available. WRT hashtable-initialization: Nope - this is the beauty of using the type adapter - from a hashtable cast, the compiler will generate an expression tree that 1) invokes the default constructor on the target type and then 2) sets the properties and fields subsequently. Since this happens directly against the runtime and not via ETS, our new settability-restriction doesn't apply |
Omit test for specific (localized) error message text, not reliable
|
This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days. |
|
What are we waiting for here? Anything I can do to move it forward? |
|
@daxian-dbw Can you please review? |
|
Looks like the last commit pushed the "total complexity" of Just to set expectations: I am NOT going to refactor 6000 lines of half-a-decade-old code as part of this PR ^_^ |
|
This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days. |
|
@daxian-dbw *nudge nudge* :) |
|
@IISResetMe Sorry about the delays. I will try to get to this PR next week. |
|
@IISResetMe Sorry that I won't be able to get to this PR this week. All my pre-allocated PR review time will most likely be spent on #12412 this week (I started the review last week and it took more time than I thought). Hopefully, I will get to this PR the next week. |
Ah! Who is the person who could double that time besides God? 😄 |
|
Time to commence the @daxian-dbw cloning program :) Thanks for the heads up, I fully understand |
|
This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days. |
| @@ -2788,6 +2788,14 @@ internal PropertyCacheEntry(PropertyInfo property) | |||
| if (propertySetter != null && (propertySetter.IsPublic || propertySetter.IsFamily)) | |||
| { | |||
| this.isStatic = propertySetter.IsStatic; | |||
| foreach (Type requiredCustomModifier in propertySetter.ReturnParameter.GetRequiredCustomModifiers()) | |||
| { | |||
| if (!requiredCustomModifier.IsNested && requiredCustomModifier.FullName == "System.Runtime.CompilerServices.IsExternalInit") | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add comments here to indicate that this is for handling the init concept introduced in C# 9.
| @@ -2788,6 +2788,14 @@ internal PropertyCacheEntry(PropertyInfo property) | |||
| if (propertySetter != null && (propertySetter.IsPublic || propertySetter.IsFamily)) | |||
| { | |||
| this.isStatic = propertySetter.IsStatic; | |||
| foreach (Type requiredCustomModifier in propertySetter.ReturnParameter.GetRequiredCustomModifiers()) | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@IISResetMe Sorry for taking so long. The changes in this PR do not prevent PSProperty from being changed through PSProperty.Value = xxx for an init property. Example (with changes in this PR):
> Add-Type -TypeDefinition @'
public class InitOnlyTestClass
{
// default constructor to allow cast-initialization
public InitOnlyTestClass()
{
}
public long Value { get; init; }
}
'@
> $s = [InitOnlyTestClass]::new()
> $p = $s.psobject.Properties['Value']
> $p.IsSettable
False
> $p.Value = 1000
> $p
MemberType : Property
Value : 1000
IsSettable : False
IsGettable : True
TypeNameOfValue : System.Int64
Name : Value
IsInstance : True
The hashtable-to-object conversion actually goes through ConvertViaNoArgumentConstructor.Convert, and depends on setting PSProperty.Value.
This is the tricky part, for an init property, PSProperty.Value should throw when setting value to it, but the [type]@{ property1 = value1; property2 = value2 } depends on setting PSProperty.Value to work. So, here we got a contradiction :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm almost inclined to say that's a restriction we could document. Going through PSObject to target the underlying metadata and modify the PSProperty directly is almost akin to using Reflection to modify the value in C# -- and in Reflection, too, I don't think init-only holds any water.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally don't think we should introduce a language feature that is inconsistent. If a user is doing reflection directly, then it's out of our hands. But if the user is doing it in the PowerShell way, then consistent behavior should be expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daxian-dbw I agree behavior should be consistent - but I'm inclined to argue that inconsistent is consistent with the behavior we're trying to mimic here.
Init-only properties are an illusion, in that it requires the consenting participation of all compilers. The C#, VB and F# implementations all introduce compile-time guardrails, no actual runtime-constraints or CTS-specific flags to indicate any behaviorial difference between properties defined with init; and auto properties. Because there is no such flags. The core design of CTS anticipates no such distinction :)
We can mimic the runtime-inconsistent behavior that the dominant .NET languages have converged on (which is what this PR attempts), or we can decide to adapt differently (because PowerShell is different and its runtime has a different lifecycle model).
Perhaps I should close this PR and leave that question up to the language WG? 😄
Co-authored-by: Dongbo Wang <dongbow@microsoft.com>
|
This PR has Quantification details
Why proper sizing of changes matters
Optimal pull request sizes drive a better predictable PR flow as they strike a
What can I do to optimize my changes
How to interpret the change counts in git diff output
Was this comment helpful? 👍 :ok_hand: :thumbsdown: (Email) |
|
Closed based on @daxian-dbw's comments about consistency (and lack of consensus around what that means in the context of PowerShell for this specific feature) |
PR Summary
This PR modifies the .NET type adapter to respect the
IsExternalInitmodifier on property setters.IsExternalInitis the mechanism through which the C# 9.0 specification implements support for init-only setters - properties that can only be set during instantiation, after which they're considered read-only by the compiler.By inspecting the property setter's return parameter we can similarly discover the
IsExternalInitmodifier on runtime types, and change the behavior of ETS to reject attempts to set init-only properties by havingAdapter.PropertyIsSettable()reflect whether the modifier was found.PR Context
As reported in #13819, PowerShell has no current facility for detecting init-only
properties, and what the C# compiler would now treat as read-only properties are suddenly writable/settable at runtime:
After we apply the changes in this PR, PowerShell's behavior reflects that of C# (and VB.NET):
PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:or[ WIP ]to the beginning of the title (theWIPbot will keep its status check atPendingwhile the prefix is present) and remove the prefix when the PR is ready.(which runs in a different PS Host).