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

Typescript: Realistic type definition errors #35247

Closed
SephReed opened this issue Nov 20, 2019 · 13 comments
Closed

Typescript: Realistic type definition errors #35247

SephReed opened this issue Nov 20, 2019 · 13 comments
Labels
Unactionable There isn't something we can do with this issue

Comments

@SephReed
Copy link

Type checking, and type definitions errors could me made significantly easier to debug by boiling down the type definitions to something realistic.

  1. This first example shows the kind of errors I run into in actual development. It's totally unreadable.

Screen Shot 2019-11-20 at 3 58 27 PM

  1. This second example shows the same effect

Screen Shot 2019-11-20 at 4 01 34 PM

Screen Shot 2019-11-20 at 4 02 52 PM

For this second example, it would be a lot more useful if instead it just showed what the type realistically is, and what it was missing. Ie:

Property "badProp" does not exist on type `{
  requestId: string;
  type: string;
  id?: string;
  createdAt?: Date;
  data?: string;
}`

This could be a configuration thing, but I can't imagine any reason why a person would want cryptic error messages by default.

@mjbvz mjbvz transferred this issue from microsoft/vscode Nov 20, 2019
@AnyhowStep
Copy link
Contributor

AnyhowStep commented Nov 20, 2019

Kind of a difficult one.

It's not always true that the fully expanded type is more readable than the type alias.


I personally manually control the emit of my types (via type hackery) to try and get the best error message I can. (Rather than relying on TS' default emit algorithm)


Maybe something like this?

"badProp" does not exist on type `Partial<IEventTableNeeds> & IEventCreateNeeds`.
  "badProp" does not exist on type with keys requestId, type, id, createdAt, data

Maybe also do a Levenshtein distance on valid strings and suggest the closest match?

@SephReed
Copy link
Author

SephReed commented Nov 20, 2019

I personally manually control the emit of my types (via type hackery)

How do I get started on that?

And I'm sure this is one of those threads that once you start pulling it never stops... but, never-the-less, it couldn't be any worse than it is for some of these errors.

@AnyhowStep
Copy link
Contributor

AnyhowStep commented Nov 21, 2019

To force TS to expand types,
#34556

To force TS to not expand types (situational, doesn't work for all cases),
#34777 (comment)

If you go down this rabbit hole, you may be cursed!


The Identity<> trick is a measure that forces the type to be expanded (almost?) always.

There are other tricks to force a type to be expanded if you can't rewrite a type to use the Identity<> trick.

Try looking at ts-toolbelt. I forget the name of the type.


One such trick is,

type Identity<T> = T;
type Merge<T> = Identity<{ [k in keyof T] : T[k] }>;

However, if T is a union type, this will break.

Hence, my feature request here, #32909

A workaround is,

type DistributeMerge<T> = T extends any ? Merge<T> : never;

This merge trick can force the expansion of the top level object

@SephReed
Copy link
Author

This is the most helpful response I think I've ever gotten to a GH issue. Thank you very much.... I'm going down the rabbit hole.

@RyanCavanaugh RyanCavanaugh added the Unactionable There isn't something we can do with this issue label Nov 21, 2019
@RyanCavanaugh
Copy link
Member

A way to reproduce the error you saw would be very useful. We can sometimes improve error messages based on the use case.

Anyway it's not at all clear what the right rule is here; if one way were just always better than the other, that's the one we'd be doing by now (one would hope).

Let's say you had something like

interface Options {
  height: number;
  length: number;
  depth: number;
  width: number;
  color: string; 
  name: string;
  orientation: string;
  location: string; 
  age: number;
  // 30 other properties
}

and you use Options a lot throughout your code.

Somewhere, a function says

function fn(opts: Partial<Omit<Options, "orientation">>) {

}

and you call it incorrectly.

What would you want to see? Partial<Omit<Options, "orientation">> which says "Options, but it's all optional, and no orientation" or

{
  height?: number;
  length?: number;
  depth?: number;
  width?: number;
  color?: string; 
  name?: string;
  location?: string; 
  age?: number;
  // 30 other properties
}

where you will have to manually diff the property list to figure out which one got removed? That would be clearly worse in a lot of cases.

You can't just rely on "Well if the alias appears in the code, then use that" rule either because these types can appear in higher-order forms, e.g.

function fn<T, U extends keyof T>(obj: T, key: U, others: Partial<Omit<T, U>>) {

where you'd still want the unexpanded alias form.

@SephReed
Copy link
Author

I suppose the ideal would be some form of switching between the two. I don't expect that to have any easy way, but (if it was possible) it might be like:

  • Get error: Partial<Omit<Options, "orientation">>
  • click "show flattened"
  • have it turn to the long bare version
  • then click "show symbolic" to go back.

Anyway it's not at all clear what the right rule is here;

Perhaps there is a way to pass this logic off to user land? There are some things I'd like to try.

Speculatively, I think it might be effective to show flat versions of anything with a symbolic tree of over 3-4 nodes. Other times, it could perhaps fit both the symbolic and flat version one after another.

I've been getting really into advanced, conditional, and utility types for my projects.

@weswigham
Copy link
Member

I suppose the ideal would be some form of switching between the two.

A feature for this, mayhaps?

@SephReed
Copy link
Author

@weswigham That would definitely be a good way to go about it. Really, anything works for me, as long as I can see what the final list of properties and types for them is.

@SephReed
Copy link
Author

SephReed commented Jan 3, 2020

I'm going to post examples here as they come up.

Screen Shot 2020-01-03 at 11 18 19 AM

@SephReed
Copy link
Author

SephReed commented Feb 8, 2020

Screen Shot 2020-02-08 at 2 56 12 PM

@AnyhowStep
Copy link
Contributor

AnyhowStep commented Feb 9, 2020

Your last two examples just look like type emit problems.

The thing is, I feel like most people don't know how to get the current type system/emitter to produce "good" type emit. Especially for complex generic types.

There are tricks for these things. But sometimes, the types are just too large and some amount of difficult-to-read output is unavoidable.

So, errors can become unreadable, despite the team's best efforts, if users create types with unreadable emit in the first place.

@SephReed
Copy link
Author

I've found a solution for my scenario, and it is to create facade types that are simple and assert their equivalency to my more complex (and realistic) types elsewhere.

I'm not sure what the limitation of facade types is, but so far it's doing the trick.

@vadimshvetsov
Copy link

vadimshvetsov commented May 1, 2020

Just started to find a way to fold a type in error message. I'm new in typescript and sometimes I fight with it so hard to be safe typed. But when I see message like this I start to consider renaming this file back to .js.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Unactionable There isn't something we can do with this issue
Projects
None yet
Development

No branches or pull requests

5 participants