-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Fix unassignable properties by adding undefined with exactOptionalPropertyTypes #45032
Conversation
Doesn't cover or test any complicated variations.
Destructuring does not. But - skipping node_modules and lib.* does. - call expressions does - property access, including with private identifiers, does
As long as it's not nested
@iisaduan you might be interested in using this in your codefix tool when it's ready. |
@typescript-bot pack this |
Heya @andrewbranch, I've started to run the tarball bundle task on this PR at d058191. You can monitor the build here. |
Hey @andrewbranch, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
I was surprised that neither of these cases are covered: // @exactOptionalPropertyTypes: true
interface User {
name: string;
email?: string;
}
const user: User = {
name: "Andrew",
email: undefined,
};
user.email = undefined; |
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.
There should be 4 comments below unless github lost two.
obj.b = undefined; | ||
} | ||
|
||
function f2(obj: { a?: string, b?: string | undefined }) { | ||
obj = obj; | ||
obj.a = obj.a; // Error | ||
~~~~~ | ||
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'. | ||
!!! error TS2322: Type 'undefined' is not assignable to type 'string'. | ||
!!! error TS2412: Type 'string | undefined' is not assignable to type 'string' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the type of the target. |
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 error is not obvious, but self-assignments of optional properties require adding undefined
@@ -100,8 +111,8 @@ tests/cases/compiler/strictOptionalProperties1.ts(119,5): error TS2411: Property | |||
function f4a(t1: [number, string?], t2: [number, string?, string?]) { | |||
t1 = t2; // Wasn't an error, but should be | |||
~~ | |||
!!! error TS2322: Type '[number, string?, string?]' is not assignable to type '[number, string?]'. | |||
!!! error TS2322: Target allows only 2 element(s) but source may have more. | |||
!!! error TS2375: Type '[number, string?, string?]' is not assignable to type '[number, string?]' with exactOptionalPropertyTypes: true. Consider adding 'undefined' to the types of the target's properties. |
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 error needs two fixes, and now we suggest one in the elaboration and the other as a codefix.
This is confusing because they appear to disagree, but I don't know that it's worth fixing.
src/compiler/checker.ts
Outdated
@@ -16753,24 +16755,29 @@ namespace ts { | |||
let sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType); | |||
if (!sourcePropType) continue; | |||
const propName = getPropertyNameFromIndex(nameType, /*accessNode*/ undefined); | |||
const targetIsOptional = !!(propName && (getPropertyOfType(target, propName) || unknownSymbol).flags & SymbolFlags.Optional); |
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 moved this verbatim inside the new else
src/compiler/checker.ts
Outdated
reportedError = true; | ||
} | ||
else { | ||
reportedError = true; |
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.
driveby fix; reportedError was always set to true in this block, regardless of whether elaborated
is true, and it's not checked anywhere in this block, so it's fine to set it at the top.
This is ready to review again; it was a surprising amount of work to add the two other cases that @andrewbranch pointed out. |
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.
Not a blocker, but these new diagnostics seem like perfect candidates for related diagnostics of '{0}' was declared here.
Presumably “here” would be the result of getSourceTarget
, and your codefix could actually pull the related diagnostic location out instead of calculating it there.
Co-authored-by: Andrew Branch <andrewbranch@users.noreply.github.com>
I'll take a look at how complicated that is. The first draft has some add-related-span code that runs afterward already. The two newer errors are separate from all the drill-down error code, though.
Codefixes only get an error code and a span, not the actual diagnostic. I could change that but I don't have the time right now. |
I went poking through other codefix's code and found |
@typescript-bot pack this |
Heya @andrewbranch, I've started to run the tarball bundle task on this PR at 3ffe9db. You can monitor the build here. |
Hey @andrewbranch, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
@sandersn it now crashes here: /~https://github.com/microsoft/playwright/blob/869f8d541b60fae388637e83243a642b0f225e4b/src/server/network.ts#L352
private _finishedPromiseCallback: (arg: { error?: string }) => void = () => {}; |
I wrapped each individual request for codefixes in a try/catch and there was one other place in playwright that failed with the same crash. Otherwise, what became apparent is there are still a lot of assignability errors that this codefix doesn’t cover, because we started out with 253 errors (8 were due to me failing to run some codegen as part of the build, I think), and after applying this codefix across all files repeatedly until it was no longer offered (except for the two places that crashed), we actually went up to 404 errors. Essentially, the assignability errors just move (and apparently multiply) into places that we don’t identify as fixable. Possible conclusions:
If you want to see what unfixable errors remain after running the tool, I’ve committed them to a fork (one commit per run, and each run actually does two passes of codefixes when it discovers overlapping changes): /~https://github.com/andrewbranch/playwright/tree/exactOptionalPropertyTypes |
Another possible conclusion: We need more analysis of the new errors to figure out how the codefix causes the new errors. I thought that adding In the meantime, we need to decide between shipping a half-baked codefix or not. We've done this before! But for a new pedantic feature, I don't think we'll do much better than codemods written by the experts who adopt new features. So I'm leaning toward not shipping it. @DanielRosenwasser do you have an opinion about this? |
I believe the deal is that if you naively add // @exactOptionalPropertyTypes: true
interface A1 {
a?: string | number;
}
interface A2 {
a?: string | boolean;
}
interface B extends A1, A2 {
a?: string;
}
declare let b: B;
b.a = undefined;
//^^ Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target. Fixing the property assignment adds an “incorrectly extends” error for each of |
I took a look at your branch and got 398 errors (after realising I incorrectly re-generated the types.d.ts after cloning the repo). 168 of those used the new errors, but I guess didn't offer a codefix. I'm looking at the errors right now. I'll edit this comment as I survey them: So far:
export type CallMetadata = {
params: any;
apiName?: string | undefined;
stack?: {}[] | undefined;
};
export type Metadata = {
stack?: {}[],
apiName?: string,
};
export interface BrowserChannel {
newContext(metadata?: Metadata): void
}
class ConnectedBrowserDispatcher implements BrowserChannel {
newContext(metadata: CallMetadata): void {}
} Also, the error is very misleading, given the context. declare var md: Metadata | undefined
declare var cmd: CallMetadata
md = cmd Now the new error fires and the elaboration tells me that
|
I think this is the wrong way to fix exactOptionalPropertyTypes errors. Applying the fix produces errors that (1) occur a long way from the true cause (2) penalises slightly-bad code like having structurally related types with no extends clause, or combining options types with a bunch of type arithmetic. I think the better fix is to unconditionally add The new error messages are often correct, so it might be worthwhile to leave them. However, I'm not sure their advice is clear enough without a codefix to back them up. |
return emptyArray; | ||
} | ||
const targetPropertyType = getTargetPropertyType(checker, targetNode); | ||
if (targetPropertyType && checker.isExactOptionalPropertyMismatch(source, targetPropertyType)) { |
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.
maybe find parent type and fix all, like the other cases
@typescript-bot pack this |
Heya @andrewbranch, I've started to run the tarball bundle task on this PR at 3e70798. You can monitor the build here. |
Hey @andrewbranch, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
whether or not somebody tried to assign undefined to them in the erroneous assignment
I think this is ready now. I simplified the fix to add And I got rid of the fix-all, since it's not a good idea. Thanks @amcasey for suggesting this. |
@typescript-bot pack this one last time please 🙃 |
Heya @andrewbranch, I've started to run the tarball bundle task on this PR at d535509. You can monitor the build here. |
Hey @andrewbranch, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
…pertyTypes (microsoft#45032) * Simple first version Doesn't cover or test any complicated variations. * Lots of cases work Destructuring does not. But - skipping node_modules and lib.* does. - call expressions does - property access, including with private identifiers, does * Support variable declarations, property assignments, destructuring As long as it's not nested * More cleanup * skip all d.ts, not just node_modules/lib * Offer a codefix for a lot more cases * remove incorrect tuple check * Use getSymbolId instead of converting to string Co-authored-by: Andrew Branch <andrewbranch@users.noreply.github.com> * add test + switch to tracking number symbol ids * Address PR comments * Exclude tuples from suggestion * Better way to get error node Plus add a check that errorNode is an argument to the call, not the call's expression. * fix semicolon lint * fix another crash * Simplify: add undefined to all optional propertie whether or not somebody tried to assign undefined to them in the erroneous assignment * remove fix-all Co-authored-by: Andrew Branch <andrewbranch@users.noreply.github.com>
Provide a specific error and codefix for missing
undefined
in target types withexactOptionalPropertyTypes: true
.lib.
and containingnode_modules
Fixes #44403