-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Parameter type inference/capture depends on the order of declaration of fields in an object. #56297
Comments
Thinking about it, this is probably a result of where the parser first finds a reference to Perhaps the type inference engine should have some algorithm to evaluate all the references to a type parameter, and use the "narrowest" definition by default, rather than just the first? Although I'm a reasonably experienced TS user, my knowledge of the compiler is pretty rudimentary. If someone more knowledgeable knows where this is implemented, please point me at it, and I'll have a go at a PR. |
The other possibility is more syntax to highlight where a type parameter should be captured (or ignored) if it appears more than once, so that I can give the compiler a hint, e.g:
|
This is a current limitation of the so-called intra-expression inference (introduced in TS 4.7). The producer of a type has to come up before the consumer. The current "catch-all" issue for further improvements of this algorithm can be found here |
Thanks for the swift feedback! I didn't find that class of issues when I was searching, but I agree it's a similar case. I'm not sure why the fix (as described here) doesn't cover my case, but it doesn't. I have a different example where the overriding issue - being able to specify the capture site - would again avoid the problem (it's completely different: a recursive type that parses ElasticSearch aggregation specifications to generate the appropriate response types). Perhaps that's a more generic (no pun intended), if somewhat manual, solution to this class of problem? |
It's how it's implemented. The output of any function can be used as input to any other function. TypeScript doesn't form any form of a dependency between them and it cannot loop through the set of involved expressions indefinitely until no new information can be received (that would likely be a perf killer). So the algorithm is a one-way street - no backsies. It has to settle the parameter type of a function at some point - so at that point it uses the information available to it at that point. And yes, it iterates through your expression object expression in the source order. So when it sees the It doesn't work if you put your Remember that you could get some crazy cases like: declare function test<T1, T2, T3, T4, T5>(obj: {
a: (arg: T5) => T4;
b: (arg: T4) => T1;
c: (arg: T1) => T5;
d: () => T1;
e: (arg: T2) => T3;
f: (arg: T4) => T2;
}): void; Given that TS can't "sort" those in some kind of a topological order... this is impossible to fix without incurring a big perf cost on the algorithm.
I'd need some examples to comment on this |
Interesting. I'll leave the recursive example for another time until I've cut it down to a reasonable size (or better understood the cause). In the case we're considering, it works as expected if the definition is changed to It's complicated by the fact that in my production code, it's not a parameter to I think don't understand why it doesn't work as declare function extended<P extends Record<string,unknown>>(def: {
methods?: P;
init?: (a:P) => any;
}): unknown; ...meaning that there's no ambiguity in the context of If I change the member of this literal to a non-function, it works, but adding back any context-dependent function breaks all the references within the literal, not just the function here. Even if the function within const N = extended({
init(a) {
return a.baz // a.baz is `unknown`
},
methods: {
baz: 1,
bar() { } // Remove this declaration, and a.baz above works as expected
}
}); |
...but I totally accept this is a complicated issue, and that the resolution of a this kind of type-graph (where all nodes on the graph appear in different parts of the AST each of which must satisfy different constraints). The common theme when I have this kind of issue is that an analyser will struggle to work out where the graph (esp if cyclic) "starts". This is often implicit in JS/TS, since it relies on the developer using convention to supply the information, hence my suggestion that the developer be able to specify where the type is to be initialised from. |
This issue has been marked as "Duplicate" and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
🔎 Search Terms
parameter type inference capture
order of fields
order of declaration
🕗 Version & Regression Information
In 4.6.4 is was consistently broken in that it just didn't work irrespective of the order the fields are declared. From 4.7.4 onwards it works if
methods
are declared beforeinit
, but not ifinit
is declared beforemethods
.It remains with this inconsistent behaviour in 5.3.0-beta
⏯ Playground Link
https://www.typescriptlang.org/play?ts=5.2.2#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXxAA8MRVRgAeABQD4AKURALngG8AoeeAWxAwAscwAM4B+FlQDcneFlRYM4+HShMqASngBeGvCioAntIC+6lmgDWqHAHdU09mDzCM8ALLaCxUuTocuvAJCwiz+XPAARrB0mqzwcBjIMPgAjPDGMsYANDJyCiqxMlwJSfhQAHRRMDFcAPS1erLC8AAGcVUxLCmS6S2Z7KYOTqgu8ABynkQkZCDAfrnyGAVsRfF8pXqV0Zr1jVjNLZbWdn1c2TKBgiKhqx2xa4nJ8GkZZwPq0kA
💻 Code
🙁 Actual behavior
const M
works as expected.const N
has errors dereferencinga
in all locations. Hovering reveals that inN
,a
isunknown
🙂 Expected behavior
In
N.init()
,a
should be:...as it is in
M
, notunknown
Additional information about the issue
This is a heavily simplified snippet from a codebase that defines a form of class/object hierarchy.
I've never come across an issue where the order that fields are declared in an object affect how types are inferred or captured.
Any ideas where I might start looking into a fix, or a workaround (other than swapping the order in all my declarations!).
The text was updated successfully, but these errors were encountered: