-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Declaration emit should not inline type definitions #37151
Comments
What would you have TypeScript emit in the case of the following? export const exportNumNum = num + num;
export const exportExtendedObj = {
...obj,
someOtherProp: 200,
}; |
Thanks for looking into this, @DanielRosenwasser . As your examples illustrate, there is a long tail of correctness issues in this area. If examples like the one in the issue description are the common case, maybe it would be possible to solve them independently of the uncommon cases. We have some ideas for how to deal with the less common cases and can open a separate issue for them if you think it's a good idea. Perhaps related: #29043. |
There's a fairly fundamental tension here about what declaration emit means: Should declaration files represent the types as they existed when you compiled your program, or should they represent the types that a consuming library would have seen had your original program been compiled "in the context of" the consumer's setup? This gets really mind-bending if you think about conditional types or overloads, e.g. declare var x: SomeType;
export const c = func(x); Is the intent here that I think one of these behaviors is much easier to reason about than the other, as you can probably tell from my descriptions of them. Anyway the example in the OP is also in tension with people who want their end-result |
@RyanCavanaugh thanks for your response, I find your description of the two mental models really helpful: the "types are conceptually inlined" model vs. the "types always flow through" model. I'm not sure the inlining is easier to reason about, given that "types flow through" is how export { foo } from "./foo"; // re-exposes whatever is in ./foo It also seems like the inlining model doesn't work for nominal types: it would lead to spurious type incompatibility errors. That's probably why TS currently does not inline nominal types. So if the choices are:
I'd choose the latter. Please let me know if this is a misleading way of stating the choices! |
I'm curious about this issue: is there a way to help progress this? I could open a separate issue for @RyanCavanaugh 's more fundamental topic re the point of a declaration file. |
Following up on this issue: it's causing us some pain, so it would be good to have some guidance. |
@mheiber have you tried with |
If you're thinking of #37444, that's still out for review. |
Thanks Ryan and Wes! I'll check out Wes' PR. |
Thanks for the recommendation. I tried #37444 and am not seeing a difference in the output for the example above. |
Yeah, didn't think it would - you're looking for everything to be represented with Naturally, you can always just write the type annotations yourself if preserving that last bit of origin information is important (eg, because you expect augmentations somehow). |
@weswigham thanks for explaining. After looking at your related PR, I think I can see how the information is not currently tracked. Regarding design (rather than implementation), the fundamental issue seems to me to be that declaration files are neither:
I gave some reasons above why I see advantages to the dynamic model (#37151 (comment)). One of the most compelling reasons, in my opinion, is that it's the only way I can see that will work well with nominal types. Am I understanding the design issue correctly? |
A superpowered export declare const exportNumNum: typeof num + num; Would also address:
Would |
I'm here because inlining everything caused my |
@cyberixae One mitigation to reduce inlining is to use |
@mheiber Have you find a solve for this problem? |
@aleksey-ilin I'm not writing TS full-time anymore, @robpalme is more up to date. But my understanding is that this is a fundamental issue with TS not picking a consistent model for type inlining. |
@aleksey-ilin the main solution I have found to solve huge declarations is to identify the root type that is inlined and then, assuming it is a statically known object type, create an export interface WrappedProblemType extends ProblemType {} I have been experimenting with changing declaration emit so that shenanigans like this are not necessary. It kinda works and I'll share that soon. Separately, union and intersection types also get inlined. |
The fact that the TypeScript compiler does this has caused many issues for me, including:
|
Thank you for the examples. There's more work on the way related to Isolated Declarations that may mitigate 1 and 2. 3 just sounds like a bug. If you have a small repro, please file a standalone issue. |
Just ran into this little issue too.
I've got a package I'm trying to use types from. For the sake of this repro it's "./bar"; foo.ts import { Bar } from "./bar";
export const foo = <T extends Bar = Bar>(bar: T) => ""; bar.ts export const bar = ["a", "b", "c"] as const;
export type Bar = (typeof bar)[number]; emits: import { Bar } from "./bar";
export declare const foo: <T extends Bar = "a" | "b" | "c">(param: T) => string; Which is kinda both worlds? We've got the extends Bar but then the inlined definition? Which is particularly tricky when the type of Bar gets updated in a separate package update. If I change |
TypeScript Version: 3.8.3, 3.8.1, probably others
Search Terms:
declaration inlining, dts inlining, declaration inline, inline literal, declaration literal
Code
tsc index.ts --declaration
Expected behavior:
Declaration emit for
parent.ts
should not inline types.Actual behavior:
Today, declaration emit for
parent.ts
inlines the types and eliminates the import of thechild.d.ts
type definition.This is a correctness issue, because consumers of
parent.d.ts
will not get the correct types if the types inchild.d.ts
change.In practice, this is most likely to happen when
parent
andchild
are in separate packages, because they are published independently, i.e. an application usesparent-package
which uses types fromchild-package
. This is exacerbated by the current practice on npm ofparent-package
depending on an unpinned version, usingpackage.json
dependency syntax"child-package": "*"
.The text was updated successfully, but these errors were encountered: