-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Disallow property/accessor overrides #33401
Conversation
Unless the base property or accessor is abstract
I'm curious how much real-world code actually does this... @typescript-bot test this |
The user suite test run you requested has finished and failed. I've opened a PR with the baseline diff from master. |
Discovered in the design review:
|
For (2), @rbuckton suggested a test that extends |
Summary: Only bogus failures in user tests. Two projects failed in DT, but since .d.ts files don't currently have accessors, we'll only see accessor-overrides-property errors from there. Admittedly, these are probably the most common. It looks like RWC failed to run; I'll try again. |
@typescript-bot test this |
continue; | ||
} | ||
} | ||
else if (isPrototypeProperty(base)) { |
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.
this change makes it obvious that this line is always false and its contents are dead code. I'll remove it.
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.
Errr, instead I tried reviving it. We'll see how much code has unknowingly been missing this error.
RWC: 27 breaks across 7 projects. So, lots of projects each had a few breaks, which is not great for a problem that often has no easy fix. I'll look at the breaks next to see if I'm wrong about "no easy fix". I hope so! |
1. Don't error when overriding properties from interfaces. 2. Fix error when overriding methods with other things. This had no tests so I assume that the code was always dead and never worked.
@typescript-bot test this |
From RWC:
A couple of failures are fixed in the latest commit, which allows anything to override properties declared in interfaces. The worrisome problems are (1), (4) and (5). It sounds like (5) is working around [[Set]] semantics so would be hard to fix. (1) has to do with decorators in Angular. I'm sure we can come up with a workaround for (4), but it will be awfully complicated to put in a blog post. |
Note: I tested my fix for "properties/accessors aren't allowed to override methods", and it turns out it's only been broken from 3.0-3.6. That means there were no new RWC failures, as our RWC code predates 3.0. |
@typescript-bot pack this |
Hey @sandersn, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running |
Here is the failure from Angular: @Component({selector: 'tree', properties: ['data']})
@View({
directives: [StaticTreeComponent1],
template:
`<span> {{data.value}} <tree [data]='data.right'></tree><tree [data]='data.left'></tree></span>`
})
class StaticTreeComponent2 extends StaticTreeComponentBase {
data: TreeNode;
}
class StaticTreeComponent2 extends StaticTreeComponentBase {
declare data: TreeNode;
} |
@rkirov @mprobst @evmar {
"devDependencies": {
"typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/43964/artifacts?artifactName=tgz&fileId=3AF21D4D4FC0F9A349D943EEA8D0975B1F7FB2E974C87B30467B3F4A1F0AFD2B02&fileName=/typescript-3.7.0-insiders.20190913.tgz"
}
} If you have any feedback, please let us know. I expect we'll merge this in a week or less to let everybody on |
Note: The interface check is currently wrong; it needs to check the containing type of the base property, not the immediate base type. |
@sandersn thanks for the heads up! I ran this against our internal code base. As expected, this breaks some code, though not terribly many; something like 0.1%. This is manageable for us (famous last words...), assuming there's a reasonably mechanical way to fix. From sampling the results:
These are the most common issues. I'll need to see if I can exclude those to (possibly) find more specific issues. |
@mprobst Yes, sandersn/add-property-define-flag. I haven't created a PR or packed it for consumption yet. I'll update after I have. |
Also, I outlined a mechanical way to fix both of the errors introduced in this PR to the description. However, the second -- moving accessors into the constructor with a defineProperty call -- doesn't perfectly preserve semantics. It would work fine for all the examples I saw, none of which were aware of the weird edge cases of Set semantics. |
Thanks for that branch. I ran this across our internal code base, overall breakages seem to be relatively rare. Maybe somewhat surprisingly, all of them are subtypes just narrowing the type of a supertype's property (and, often, declaring a I've sampled the cases I found, all that I inspected seem legit by the semantics explained here (i.e. I found no compiler bugs). Regarding the possible fixes, "Property overrides accessor" checks out. The "Accessor overrides property" fix will not work for us, because it moves the property name Let me know if you'd like me to look out for (other) specific patterns of expected breakage or would like feedback on something in particular. |
@mprobst While Object.defineProperty has the literal to string issue, Object.defineProperties does not require that change and that is what Closure Compiler recommends for ADVANCED mode code that otherwise requires calls to Object.defineProperty. |
@mprobst Thanks for your analysis. That agrees exactly with what I saw from our test repos, except that a few people thought (incorrectly) that is was safe to use a get-only accessor in a subclass to make a property readonly. But this is probably rare in modern code that has the readonly modifier. |
This is now included in #33509. Thanks all for testing. |
This PR is part of the migration from [[Set]] semantics to [[Define]] semantics. #27644 tracks the complete migration. This is the second step.
Summary
Briefly, properties can now only override properties, and accessors can only override accessors. The tests come from the user-submitted horrorshow parade in #27644. Thanks to all who contributed there.
Exceptions
Motivation
Accessors that override properties have always been error-prone with [[Set]] semantics, so these new errors are useful with [[Set]] or [[Define]] semantics. For example, base-class properties call derived accessors, which may be unexpected. The code below prints
2
, the derived value, but it also printsset 1
. That's because the basep = 1
calls derived accessorset p
.In fact, if the derived class attempts to make
p
readonly by leaving offset p
, the code crashes with"Cannot set property p of #<D> which has only a getter."
This should always have been an error. However, because we haven’t given errors before, and because fixing the errors is likely to be difficult, we probably would not have added them otherwise, or added them in so many cases. Here are some examples of code that will be tricky to change:CleverBase
is a framework class that intends to cache or transform the value provided bySimple
.Simple
is written by a framework user who just knows that they need toextend CleverBase
and provide some configuration properties. This pattern no longer works with [[Define]] semantics. I believe the Angular 2 failure I found below is similar to this case.This code is the same as the first example — accessors override a property — but the intent is different: to ignore the base's property. With [[Set]] semantics, it's possible to work around the initial set from the base, but with [[Define]] semantics, the base property will override the derived accessors. Sometimes a derived accessor can be replaced with a property, but often the accessor needs to run additional code to work around base class limitations. In this case, the fix is not simple. I saw this in a couple of Microsoft apps, and in one place it had a comment "has to be a getter so overriding the base class works correctly".
To test this PR
How to fix the errors
Property overrides accessor
SimpleUser will have an error on the property declaration
p
. The fix is to move it into the constructor as an assignment:Since
CleverBase
declaresp
, there's no need forSimpleUser
to do so.Accessor overrides property
SmartDerived will have an error on the get/set declarations for
p
. The fix is to move them into the constructor as anObject.defineProperty
:This doesn't have exactly the same semantics; the base never calls the derived setter for
p
. Most people won't handle this correctly anyway, so I suspect it's not a problem. However, if the original setter does have skip-initial-set code to work around the current weird Typescript semantics, that code will need to be removed.Next steps
declare property: type
syntax to work around this. This is at Disallow uninitialised property overrides #33423