-
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
Attempt to more-frequently use local aliases when printing out types #34778
Comments
I want to call out another case explicitly since it behaves differently right now and that's when a type alias is used in an argument of a lambda: For example, import { DeepReadonly } from 'utility-types'
export type Foo = DeepReadonly<{ foo: string }>
export const useFoo = ({ foo }: { foo: Foo }) => {} Currently emits: import { DeepReadonly } from 'utility-types';
export declare type Foo = DeepReadonly<{
foo: string;
}>;
export declare const useFoo: ({ foo }: {
foo: {
readonly foo: string;
};
}) => void; Ideally it would emit: import { DeepReadonly } from 'utility-types';
export declare type Foo = DeepReadonly<{
foo: string;
}>;
export declare const useFoo: ({ foo }: Foo) => void; |
As long as the There are cases where But if The Pick and Omit suffer from this problem, too.
If I guess I should find/file a separate issue for this, and locate all the relevant issues. (Like a meta issue regarding type display?) But it seems like TS doesn't really give developers a way to explicitly control how they want a type to "expand" in emit/hover, at the moment. To TS, as long as the types behave well, then all is good; it doesn't matter if the type looks simple or complicated. Except, to developers, if the type looks complicated/long/verbose, then it's extra cognitive overhead. That, and hover and error messages tend to truncate long types. I'm not sure how to phrase this properly. I think it's fine to have heuristics for TS to automatically determine how to "present" a type. Given a type, there could be a million different ways to express it syntactically. However, there needs to be a way for a programmer to step in and say, "Your heuristics are bad.", "Expand this type.", "Don't expand this type.", "Use Because, sometimes, the 100+ line type is more readable than the 5 line type. Mostly, it's the other way around. Sometimes, aliases are easier to understand. Other times, the full expanded type is easier. There's no way an algorithm can display a type such that it'll always be "easiest" to read. It's rather subjective, I've found. Also relevant, |
I bring this up over here because I just saw that this is the proposed output, // ./c.ts
import { Foo } from "./a.js"
function f(x: Foo) {
return x;
} This output would actually be bad for a lot of my use cases involving generic types. Right now, I'm using the Also, @aaronjensen said this is undesirable, import { DeepReadonly } from 'utility-types';
export declare type Foo = DeepReadonly<{
foo: string;
}>;
export declare const useFoo: ({ foo }: {
foo: {
readonly foo: string;
};
}) => void; However, there are cases where it is desirable. So, forcing either current display, or proposed display isn't a great idea, unless it comes with official ways for devs to control the exact type display. This can make the difference between a usable API and an unusable API (or, at least indecipherable when looking at the type display on hover) |
@AnyhowStep That's interesting, I wasn't thinking there would be times where the existing behavior would be desirable. I wonder though, in your examples and when you talk of an ability to expand types, are you imagining it fully expanding the entire type? If so, it seems like you're potentially talking about api authors being able to provide utility types that fully expand (like your The problem I see with that, is if the consumer of that type doesn't want the type expanded. I certainly wouldn't, if I did something like So, I don't think the choice could be made at the level of the type alias definition. That makes me wonder if the missing piece is the ability to expand types in dev tools (maybe this isn't missing, I'm not sure). If I could cursor over a type and then expand it one step at a time, I could learn about the type as needed. This would be similar to a macro expand in lisp languages. Do you have any examples where a fully aliased type is "at least indecipherable when looking at the type display on hover"? That seems to only apply to expanded types in my mind. |
Not necessarily so. And it isn't always expansion I'm interested in. Sometimes, just using Sometimes, keeping the alias is better than expanding. Sometimes, forcing the alias is better than expanding. It's all super subjective because humans are involved.
Also not necessarily so. They're not always utility types. By "utility type", I mean very generic type aliases that can be applied to a variety of situations. A lot of the time, they're one-off types for a particular function/method.
This is definitely missing. And would make the problem a little less painful. I believe there's an issue somewhere asking for this... Even so, this wouldn't solve the problem 100% This will let you work around the problem in your tooltip/hover. But not during emit, where it will probably go back to the "default" type display, which may be 100+ lines long, and ugly, and impossible to read, and cause your emit time to be 40-80+s
Imagine Pick<{
//Snip 100+ lines
}, "hi"> Whereas the fully expanded type would be something like, { hi : U } There are many other less contrived examples in my projects but this is the easiest to explain. Also consider, Omit<{
//Snip 100+ lines
}, "hi"|"bye"> Which becomes, Pick<{
//Snip 100+ lines
}, "key0"|"key1"|"key2"|...|100+ more|...|"keyN"> So, now your type is 200+ lines long. Whereas this would be better, {
key0 : U0,
key1 : U1,
key2 : U2,
//snip 100+ lines
keyN : UN,
} Only 100+ lines, vs 200+. In this very specialized case, a developer may say "Hmm, I want to force this 100+ line type to be a single line type alias". (In this case, no generics are involved, so it's a hard coded type and we can just alias it. But if it's a generic, life gets harder and we just have to keep the 100+ line type because it's better than the 200+ line alternative) And it would be useful if one can write, type Blah = //that 100+ line type And force TS to never expand the type. At the end of the day, it's super subjective. Sometimes, the 100+ line type is readable. Sometimes it isn't. I like it when I can control what the emit and hover display by default, so that the type is as easy to understand as possible, with as little work as possible for downstream users. Here's an example from one of my use cases, type SomeDelegate<TableT extends ITable> =
(columns : SomeMappedType<TableT>) => OtherComplexType<TableT>
;
function foo<TableT extends ITable> (table : TableT, d : SomeDelegate<TableT>) : //snip
However, it will always be more readable, when fully expanded. Considerably fewer lines. When users (and even myself) use the function, they write something like, foo(myTable, (columns) => // snip Now, when they hover over However, because it isn't naturally expanded all the time, they end up seeing, SomeMappedType<{ /* properties of myTable that we don't care about */ }> Definitely not a good user experience. In this case, a fully expanded type is more readable. And so I use the |
Does Anyway, I hear you that there are times when more control is desired. This issue is in response to a serious performance impact of the current implementation. My guess is that aliasing more types by default will be a better experience for most, though I can see how a way to opt out could be helpful. I'll be curious to see what the TS team ultimately comes up with. Thank you for the examples and the discussion! |
Not at the moment. A simple table looks like this, const myTable = tsql.table("myTable")
.addColumns({
testId : tm.mysql.bigIntUnsigned(),
otherVal : tm.mysql.bigIntUnsigned(),
})
.setPrimaryKey(columns => [columns.testId]); Then, you just use Having Also, I'm pretty sure TS will expand the type, even if I aliased it, anyway. const x = {
a: "",
b: "",
c: "",
d: "",
e: "",
};
type X = typeof x;
declare function foo<T>(t: T): T;
/*
const y0: {
a: string;
b: string;
c: string;
d: string;
e: string;
}
*/
const y0 = foo(x);
/*
const y1: {
a: string;
b: string;
c: string;
d: string;
e: string;
}
*/
const y1 = foo<typeof x>(x);
/*
const y2: {
a: string;
b: string;
c: string;
d: string;
e: string;
}
Would be nice to have,
const y2 : X;
or something. I don't know.
Even if we did have that, it would require me to explicitly
pass in `X` as a type parameter, thus losing type inference
and degrading usability.
*/
const y2 = foo<X>(x); |
Got it, thanks. I see similar things with
If I understand you correctly, that's part of what this issue is about—preventing the expansion in your last case. |
@DanielRosenwasser Does this cover the use cases I outlined in #32287 |
It is. However, I'm just throwing it out there that they shouldn't be so aggressive with fixing this issue, that they break my If using the alias The best thing that could happen would be an official way to tell the compiler,
|
I agree with you:
What do you think about this solution-space:
|
The problem with that solution is that it does not help for .d.ts emit and error message elaboration. Those 2 cases are the main motivators, for me. See, When .d.ts emit goes wrong and it emits too much, the emitted files become unreadable, or cause emit times to blow up When it emits too little, it can also impact readability negatively, the compiler needs to put in more work to resolve the type for downstream consumers, increasing check times, causing earlier max depth errors, etc Error elaborations are also affected by emit being too small, or too large |
I'm not understanding the original post so I created a bug workbench with the same code. Here's a screenshot, including the error: What's the expected result? //cc @DanielRosenwasser |
When a user imports a type alias like
We should try to print out
Foo
in error messages like inand in declaration emit like in
The text was updated successfully, but these errors were encountered: