镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Typescript: Realistic type definition errors #35247

Closed

Description

@SephReed

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.

Activity

  1. AnyhowStep commented on Nov 20, 2019

    @AnyhowStep
    Contributor

    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?

  2. SephReed commented on Nov 20, 2019

    @SephReed
    Author

    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.

  3. AnyhowStep commented on Nov 21, 2019

    @AnyhowStep
    Contributor

    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

  4. SephReed commented on Nov 21, 2019

    @SephReed
    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.

  5. RyanCavanaugh commented on Nov 21, 2019

    @RyanCavanaugh
    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.

  6. SephReed commented on Nov 21, 2019

    @SephReed
    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.

  7. weswigham commented on Nov 21, 2019

    @weswigham
    Member

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

    A feature for this, mayhaps?

  8. SephReed commented on Nov 21, 2019

    @SephReed
    Author

    Wesley Wigham (@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.

  9. SephReed commented on Jan 3, 2020

    @SephReed
    Author

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

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

  10. SephReed commented on Feb 8, 2020

    @SephReed
    Author

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

  11. AnyhowStep commented on Feb 9, 2020

    @AnyhowStep
    Contributor

    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.

  12. SephReed commented on Feb 10, 2020

    @SephReed
    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.

  13. vadimshvetsov commented on May 1, 2020

    @vadimshvetsov

    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    UnactionableThere isn't something we can do with this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions