-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Fixed too eager type resolution when looking for symbol aliases #57396
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
Open
Andarist
wants to merge
1
commit into
microsoft:main
Choose a base branch
from
Andarist:fix/circularity-through-error-reporting
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
tests/baselines/reference/declarationEmitUsingTypeAlias2.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
//// [tests/cases/compiler/declarationEmitUsingTypeAlias2.ts] //// | ||
|
||
//// [inner.d.ts] | ||
export declare type Other = { other: string }; | ||
export declare type SomeType = { arg: Other }; | ||
|
||
//// [indirection.d.ts] | ||
import { Other as IndirectOther, SomeType as IndirectSomeType } from './inner'; | ||
export declare type Other = IndirectOther; | ||
export declare type SomeType = IndirectSomeType | ||
|
||
//// [index.d.ts] | ||
import { Other, SomeType as IndirectSomeType } from './indirection'; | ||
export type OtherType = Other; | ||
export type SomeType = IndirectSomeType; | ||
|
||
//// [package.json] | ||
{ | ||
"name": "some-dep", | ||
"exports": { | ||
".": "./dist/index.js" | ||
} | ||
} | ||
|
||
//// [index.ts] | ||
import { SomeType } from "some-dep"; | ||
|
||
export const foo = (thing: SomeType) => { | ||
return thing; | ||
}; | ||
|
||
export const bar = (thing: SomeType) => { | ||
return thing.arg; | ||
}; | ||
|
||
//// [index.js] | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.bar = exports.foo = void 0; | ||
var foo = function (thing) { | ||
return thing; | ||
}; | ||
exports.foo = foo; | ||
var bar = function (thing) { | ||
return thing.arg; | ||
}; | ||
exports.bar = bar; | ||
|
||
|
||
//// [index.d.ts] | ||
import { SomeType } from "some-dep"; | ||
export declare const foo: (thing: SomeType) => import("some-dep").SomeType; | ||
export declare const bar: (thing: SomeType) => import("some-dep").OtherType; |
66 changes: 66 additions & 0 deletions
66
tests/baselines/reference/declarationEmitUsingTypeAlias2.symbols
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
//// [tests/cases/compiler/declarationEmitUsingTypeAlias2.ts] //// | ||
|
||
=== node_modules/some-dep/dist/inner.d.ts === | ||
export declare type Other = { other: string }; | ||
>Other : Symbol(Other, Decl(inner.d.ts, 0, 0)) | ||
>other : Symbol(other, Decl(inner.d.ts, 0, 29)) | ||
|
||
export declare type SomeType = { arg: Other }; | ||
>SomeType : Symbol(SomeType, Decl(inner.d.ts, 0, 46)) | ||
>arg : Symbol(arg, Decl(inner.d.ts, 1, 32)) | ||
>Other : Symbol(Other, Decl(inner.d.ts, 0, 0)) | ||
|
||
=== node_modules/some-dep/dist/indirection.d.ts === | ||
import { Other as IndirectOther, SomeType as IndirectSomeType } from './inner'; | ||
>Other : Symbol(IndirectOther, Decl(inner.d.ts, 0, 0)) | ||
>IndirectOther : Symbol(IndirectOther, Decl(indirection.d.ts, 0, 8)) | ||
>SomeType : Symbol(IndirectSomeType, Decl(inner.d.ts, 0, 46)) | ||
>IndirectSomeType : Symbol(IndirectSomeType, Decl(indirection.d.ts, 0, 32)) | ||
|
||
export declare type Other = IndirectOther; | ||
>Other : Symbol(Other, Decl(indirection.d.ts, 0, 79)) | ||
>IndirectOther : Symbol(IndirectOther, Decl(indirection.d.ts, 0, 8)) | ||
|
||
export declare type SomeType = IndirectSomeType | ||
>SomeType : Symbol(SomeType, Decl(indirection.d.ts, 1, 42)) | ||
>IndirectSomeType : Symbol(IndirectSomeType, Decl(indirection.d.ts, 0, 32)) | ||
|
||
=== node_modules/some-dep/dist/index.d.ts === | ||
import { Other, SomeType as IndirectSomeType } from './indirection'; | ||
>Other : Symbol(Other, Decl(index.d.ts, 0, 8)) | ||
>SomeType : Symbol(IndirectSomeType, Decl(indirection.d.ts, 1, 42)) | ||
>IndirectSomeType : Symbol(IndirectSomeType, Decl(index.d.ts, 0, 15)) | ||
|
||
export type OtherType = Other; | ||
>OtherType : Symbol(OtherType, Decl(index.d.ts, 0, 68)) | ||
>Other : Symbol(Other, Decl(index.d.ts, 0, 8)) | ||
|
||
export type SomeType = IndirectSomeType; | ||
>SomeType : Symbol(SomeType, Decl(index.d.ts, 1, 30)) | ||
>IndirectSomeType : Symbol(IndirectSomeType, Decl(index.d.ts, 0, 15)) | ||
|
||
=== src/index.ts === | ||
import { SomeType } from "some-dep"; | ||
>SomeType : Symbol(SomeType, Decl(index.ts, 0, 8)) | ||
|
||
export const foo = (thing: SomeType) => { | ||
>foo : Symbol(foo, Decl(index.ts, 2, 12)) | ||
>thing : Symbol(thing, Decl(index.ts, 2, 20)) | ||
>SomeType : Symbol(SomeType, Decl(index.ts, 0, 8)) | ||
|
||
return thing; | ||
>thing : Symbol(thing, Decl(index.ts, 2, 20)) | ||
|
||
}; | ||
|
||
export const bar = (thing: SomeType) => { | ||
>bar : Symbol(bar, Decl(index.ts, 6, 12)) | ||
>thing : Symbol(thing, Decl(index.ts, 6, 20)) | ||
>SomeType : Symbol(SomeType, Decl(index.ts, 0, 8)) | ||
|
||
return thing.arg; | ||
>thing.arg : Symbol(arg, Decl(inner.d.ts, 1, 32)) | ||
>thing : Symbol(thing, Decl(index.ts, 6, 20)) | ||
>arg : Symbol(arg, Decl(inner.d.ts, 1, 32)) | ||
|
||
}; |
61 changes: 61 additions & 0 deletions
61
tests/baselines/reference/declarationEmitUsingTypeAlias2.types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
//// [tests/cases/compiler/declarationEmitUsingTypeAlias2.ts] //// | ||
|
||
=== node_modules/some-dep/dist/inner.d.ts === | ||
export declare type Other = { other: string }; | ||
>Other : { other: string; } | ||
>other : string | ||
|
||
export declare type SomeType = { arg: Other }; | ||
>SomeType : { arg: Other; } | ||
>arg : Other | ||
|
||
=== node_modules/some-dep/dist/indirection.d.ts === | ||
import { Other as IndirectOther, SomeType as IndirectSomeType } from './inner'; | ||
>Other : any | ||
>IndirectOther : any | ||
>SomeType : any | ||
>IndirectSomeType : any | ||
|
||
export declare type Other = IndirectOther; | ||
>Other : IndirectOther | ||
|
||
export declare type SomeType = IndirectSomeType | ||
>SomeType : IndirectSomeType | ||
|
||
=== node_modules/some-dep/dist/index.d.ts === | ||
import { Other, SomeType as IndirectSomeType } from './indirection'; | ||
>Other : any | ||
>SomeType : any | ||
>IndirectSomeType : any | ||
|
||
export type OtherType = Other; | ||
>OtherType : import("node_modules/some-dep/dist/inner").Other | ||
|
||
export type SomeType = IndirectSomeType; | ||
>SomeType : import("node_modules/some-dep/dist/inner").SomeType | ||
|
||
=== src/index.ts === | ||
import { SomeType } from "some-dep"; | ||
>SomeType : any | ||
|
||
export const foo = (thing: SomeType) => { | ||
>foo : (thing: SomeType) => import("node_modules/some-dep/dist/inner").SomeType | ||
>(thing: SomeType) => { return thing;} : (thing: SomeType) => import("node_modules/some-dep/dist/inner").SomeType | ||
>thing : import("node_modules/some-dep/dist/inner").SomeType | ||
|
||
return thing; | ||
>thing : import("node_modules/some-dep/dist/inner").SomeType | ||
|
||
}; | ||
|
||
export const bar = (thing: SomeType) => { | ||
>bar : (thing: SomeType) => import("node_modules/some-dep/dist/inner").Other | ||
>(thing: SomeType) => { return thing.arg;} : (thing: SomeType) => import("node_modules/some-dep/dist/inner").Other | ||
>thing : import("node_modules/some-dep/dist/inner").SomeType | ||
|
||
return thing.arg; | ||
>thing.arg : import("node_modules/some-dep/dist/inner").Other | ||
>thing : import("node_modules/some-dep/dist/inner").SomeType | ||
>arg : import("node_modules/some-dep/dist/inner").Other | ||
|
||
}; |
52 changes: 52 additions & 0 deletions
52
tests/baselines/reference/errorReportNotCircular1.errors.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
data.ts(6,24): error TS2345: Argument of type '{}' is not assignable to parameter of type 'Data'. | ||
Property 'quantity' is missing in type '{}' but required in type 'Data'. | ||
data.ts(6,28): error TS1360: Type '{ quantity: Value; }' does not satisfy the expected type '[]'. | ||
|
||
|
||
==== data.ts (2 errors) ==== | ||
// https://github.com/microsoft/TypeScript/issues/57357 | ||
|
||
import type { Data } from "./processor"; | ||
|
||
export function getData() { | ||
return constructData({}) satisfies []; // error | ||
~~ | ||
!!! error TS2345: Argument of type '{}' is not assignable to parameter of type 'Data'. | ||
!!! error TS2345: Property 'quantity' is missing in type '{}' but required in type 'Data'. | ||
!!! related TS2728 processor.ts:6:3: 'quantity' is declared here. | ||
~~~~~~~~~ | ||
!!! error TS1360: Type '{ quantity: Value; }' does not satisfy the expected type '[]'. | ||
} | ||
|
||
function constructData(data: Data) { | ||
const { ...props } = data; | ||
return { | ||
...props, | ||
}; | ||
} | ||
|
||
==== type.ts (0 errors) ==== | ||
export type Value = {}; | ||
|
||
==== processor.ts (0 errors) ==== | ||
import { getData } from "./data"; | ||
|
||
import type { Value } from "./type"; | ||
|
||
export type Data = { | ||
quantity: Value; | ||
}; | ||
|
||
declare function createRequestProcessor<Req>(pipeline: () => Req): Req; | ||
|
||
export const processor = createRequestProcessor(getData); | ||
|
||
==== main.ts (0 errors) ==== | ||
import { processor } from "./processor"; | ||
export default processor; | ||
|
||
==== workerinterface.ts (0 errors) ==== | ||
import type Server from "./main"; | ||
|
||
export type _T = typeof Server; | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
//// [tests/cases/compiler/errorReportNotCircular1.ts] //// | ||
|
||
//// [data.ts] | ||
// https://github.com/microsoft/TypeScript/issues/57357 | ||
|
||
import type { Data } from "./processor"; | ||
|
||
export function getData() { | ||
return constructData({}) satisfies []; // error | ||
} | ||
|
||
function constructData(data: Data) { | ||
const { ...props } = data; | ||
return { | ||
...props, | ||
}; | ||
} | ||
|
||
//// [type.ts] | ||
export type Value = {}; | ||
|
||
//// [processor.ts] | ||
import { getData } from "./data"; | ||
|
||
import type { Value } from "./type"; | ||
|
||
export type Data = { | ||
quantity: Value; | ||
}; | ||
|
||
declare function createRequestProcessor<Req>(pipeline: () => Req): Req; | ||
|
||
export const processor = createRequestProcessor(getData); | ||
|
||
//// [main.ts] | ||
import { processor } from "./processor"; | ||
export default processor; | ||
|
||
//// [workerinterface.ts] | ||
import type Server from "./main"; | ||
|
||
export type _T = typeof Server; | ||
|
||
|
||
//// [type.js] | ||
export {}; | ||
//// [processor.js] | ||
import { getData } from "./data"; | ||
export const processor = createRequestProcessor(getData); | ||
//// [data.js] | ||
// https://github.com/microsoft/TypeScript/issues/57357 | ||
export function getData() { | ||
return constructData({}); // error | ||
} | ||
function constructData(data) { | ||
const { ...props } = data; | ||
return { | ||
...props, | ||
}; | ||
} | ||
//// [main.js] | ||
import { processor } from "./processor"; | ||
export default processor; | ||
//// [workerinterface.js] | ||
export {}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 not a huge fan of this - this looks like it's attempting to reverse some of the logic done within
getTypeFromTypeAliasReference
and the like to assign alias symbols to a type in the first place, and I can't be confident it covers all cases, since that operates on types, while this is basically alternating between symbols and AST nodes while avoiding the information trafficked in the (declared) type. I'd be much happier if the logic here was actually shared with type alias assignments, eg, it actually usedgetAliasSymbolForTypeNode
to lookup the symbol, and then shared logic with type creation to determine if that symbol actually applied to the resulting type. (Eg, this looks kinda like theisTypeReferenceType(node)
branch ingetTypeFromTypeAliasReference
, with some heuristic only-types-without-type-parameters-would-hold-old-aliases thrown in, but because the logic isn't actually shared, I can't be sure they match)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 kinda tries to follow
checkTypeReferenceOrImport
. I understand your hesitancy here but I'm not quite sure how I'm supposed to unify this with this function or the ones that were mentioned. The problem is that all of them primarily operate on the declared types so they resolve this information eagerly (what the previous code was doing).The problem with the eager resolution is that type printing in error messages goes through similar code paths as the declaration emit. So an error raised on some relation ended up resolving types that were previously not resolved and that led to the raised circularity.
This new code is also recursive and the mentioned functions are not.
Consider this:
When requesting
getAliasSymbolForTypeNode
on2
we just end up on1
. This holds true:So it doesn't give us any new information because it doesn't even go to the
import
location.In addition to that,
resolveTypeReferenceName
(that I could use instead ofgetSymbolAtLocation
) goes just to the import statement. And evenresolveAlias
goes just to the intermediate declaration site in./indirection.ts
and not to./inner.ts
.So all in all, I don't see how to reuse the existing machinery, in a clean way, because that machinery is just shallow and still relies on declared types heavily - and that has to be avoided here. So I think that any attempt at unifying this would not be that clean/straightforward.
Alternatively, I was thinking about using the existing solution (what is currently on
main
) but to combine it with either one of those 2:PS. I have quite huge gaps in my understanding of how symbol aliases etc work today. It's not something that I have touched a lot.
yes, kinda - that branch is only entered when type parameters are involved though and this code specifically wants to resolve those symbols when there are no type parameters. I feel like this is another indication that this is actually not that same-y
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.
Personally, I think I'd much prefer this - the need for a setup phase like that is why we use
checkNodeDeferred
for many node kinds - it's a common pattern for us.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.
It's different from
checkNodeDeferred
- we don't want to defer checking, just error reporting. I thought that I could useaddLazyDiagnostic
for this but it turns out that it's still often called quite eagerly bycheckSourceFileWithEagerDiagnostics
. So - unless there is already something like this - I'd have to introduce a new (small) mechanism (maybe just an extension to eithercheckNodeDeferred
oraddLazyDiagnostic
). I'll look into this tomorrow.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've looked into it more. If you don't like the current approach then deferring those errors would be best.
However, it's not that straightforward. Reporting relation errors is quite convoluted and spread out across multiple places.
reportRelationError
eagerly converts types to strings usinggetTypeNamesForErrorDisplay
(there are some extra eagertypeToString
calls there too).It wouldn't be hard to adjust this and make it lazier if only this would directly be added to the diagnostic collection here. That's not the case though - this just creates (overrides?)
errorInfo
, that is later conditionally chained withcontainingMessageChain
incheckTypeRelatedTo
,relatedInformation
might get appended to it, and only after thatDiagnosticWithLocation
gets created, and added to the diagnostic collection.All of this assumes already a certain structure - one that is string-oriented. It's not impossible to adjust this. I feel that it would be a disruptive change though. I can commit to working on it if you feel that it's the best solution.
This PR fixes a regression and it would be great to backport a fix to the upcoming 5.4. Since making this type-to-string conversion deferred isn't straightforward I feel like it's too risky for it to be that (at this moment). After thinking about it more, I don't think the currently proposed solution is that bad. It does its job in an isolated manner and the rules around this lookup are pretty straightforward.
If you feel strongly that this is just not the best solution for the problem (which it might not be, I'm not the judge of that :P) then I propose adding a new internal
TypeFormatFlags
/NodeBuilderFlags
/SymbolFormatFlags
based on which the extra lookup introduced by #56087 would be avoided when printing types for relation errors. This would be less than ideal than the currently proposed solution since, well, a better print wouldn't be used by relation error messages - that's not a regression though (with 5.3 as the baseline). With this as the temporary fix in place, the regression would get addressed and I could work on making the relation error lazier for 5.5