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

Drop unnecessary type arguments in the isolated declarations quick fix #59665

Merged
merged 13 commits into from
Sep 16, 2024

Conversation

blickly
Copy link
Contributor

@blickly blickly commented Aug 17, 2024

Drops unnecessary type arguments in the isolated declarations quick fix by repeatedly trying to resolve a resulting reference type with a prefix of the full type arguments until finding the minimal number of arguments that work.

Fixes #58449

(I'm sure that the code at present isn't using the proper APIs, but I'd appreciate feedback)

This is the TypeNode representation of a ReferenceType that includes
the minimal number of typeArguments that are still semantically equivalent
to the full type.

Also use this functionality in the isolatedDeclaration autofixer to
fix microsoft#58449
These are the cases where our heuristic makes some compromise
that isn't what we would have preferred, but add the unit tests
as a way of documenting the current behavior.
@typescript-bot typescript-bot added the For Backlog Bug PRs that fix a backlog bug label Aug 17, 2024
src/compiler/types.ts Outdated Show resolved Hide resolved
src/compiler/types.ts Outdated Show resolved Hide resolved
@blickly blickly force-pushed the dont-suggest-default-generics branch from f4eea52 to de61d6d Compare August 19, 2024 16:35
blickly and others added 4 commits August 19, 2024 09:41
Co-authored-by: Jake Bailey <5341706+jakebailey@users.noreply.github.com>
Also, mark typeToMinimizedReferenceType as `@internal`.
@blickly
Copy link
Contributor Author

blickly commented Aug 20, 2024

OK, I switched to the more idiomatic way of creating a new AST node, rather than trying to mutate anything. PTAL

const genericType = type as GenericType;
if (genericType.typeArguments) {
const cutoff = endOfRequiredTypeParameters(checker, genericType);
if (cutoff !== undefined && typeNode.typeArguments) {
Copy link
Member

Choose a reason for hiding this comment

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

cutoff can't be undefined; should this instead be a check that skips this block if the length is different than the existing node?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Done.

description: "Add annotation of type 'MyIterator<number>'",
index: 0,
newFileContent:
`interface MyIterator<T, TReturn = any, TNext = undefined> {}
Copy link
Member

Choose a reason for hiding this comment

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

Do we have tests for generators elsewhere? I seem to recall us auto-adding type long type annotations for those...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I can add a case with Generator.

For reasons I don't fully understand, Generator<T> and Iterator<T> get generated with the full 3 type arguments, whereas Iterable<T> and IterableIterator<T> are special cased inside the TypeChecker to omit the extra arguments when unnecessary.

The special casing inside typeReferenceToTypeNode:

                            // Maybe we should do this for more types, but for now we only elide type arguments that are
                            // identical to their associated type parameters' defaults for `Iterable`, `IterableIterator`,
                            // `AsyncIterable`, and `AsyncIterableIterator` to provide backwards-compatible .d.ts emit due
                            // to each now having three type parameters instead of only one.
                            if (
                                isReferenceToType(type, getGlobalIterableType(/*reportErrors*/ false)) ||
                                isReferenceToType(type, getGlobalIterableIteratorType(/*reportErrors*/ false)) ||
                                isReferenceToType(type, getGlobalAsyncIterableType(/*reportErrors*/ false)) ||
                                isReferenceToType(type, getGlobalAsyncIterableIteratorType(/*reportErrors*/ false))
                            ) {
                                if (
                                    !type.node || !isTypeReferenceNode(type.node) || !type.node.typeArguments ||
                                    type.node.typeArguments.length < typeParameterCount
                                ) {
                                    while (typeParameterCount > 0) {
                                        const typeArgument = typeArguments[typeParameterCount - 1];
                                        const typeParameter = type.target.typeParameters[typeParameterCount - 1];
                                        const defaultType = getDefaultFromTypeParameter(typeParameter);
                                        if (!defaultType || !isTypeIdenticalTo(typeArgument, defaultType)) {
                                            break;
                                        }
                                        typeParameterCount--;
                                    }
                                }
                            }

Copy link
Member

Choose a reason for hiding this comment

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

That was a part of #58243.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. Thanks for the context.

Also include test cases with the global `Generator` and `Iterator` which,
unlike `Iterable` and `IterableIterator`, are not special cased in the
type system and were previously always generated with 3 type arguments.
description: "Add return type 'Generator<number>'",
index: 0,
newFileContent:
`export function foo(x: Generator<number>): Generator<number> {
Copy link
Member

Choose a reason for hiding this comment

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

To be clear I was meaning to write function *foo() { } here and then see what happens.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, OK, added.

It's not technically related to the rest of this PR since it gets inferred a precise return that needs all 3 type arguments, but it's a good case to include in the test suite anyway.

Copy link
Member

@jakebailey jakebailey left a comment

Choose a reason for hiding this comment

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

LGTM I think, but would be good to get a couple of other eyes on it.

@blickly
Copy link
Contributor Author

blickly commented Aug 28, 2024

Any advice on who would make a good secondary reviewer?

blickly and others added 3 commits August 29, 2024 14:49
…gerenrics-oversimplification.ts


Fix typo

Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
Simplify loop

Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
@blickly blickly force-pushed the dont-suggest-default-generics branch from ee3d89e to cd0cd4e Compare August 29, 2024 22:06
@jakebailey
Copy link
Member

Tested locally with main and it merges and passes cleanly.

@jakebailey jakebailey merged commit 8230bc6 into microsoft:main Sep 16, 2024
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Isolated declarations quick fix explicitly prints out default type arguments
4 participants