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

Error messages should place most relevant information first. #29759

Open
CyrusNajmabadi opened this issue Feb 5, 2019 · 25 comments
Open

Error messages should place most relevant information first. #29759

CyrusNajmabadi opened this issue Feb 5, 2019 · 25 comments
Assignees
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@CyrusNajmabadi
Copy link
Contributor

CyrusNajmabadi commented Feb 5, 2019

Here's an actual example of an error message that can go wrong in our library when mispelling a property when creating an object literal:

[ts]
Type '{ containers: { nginx: { image: string; memory: number; portMappings: NetworkListener[]; enviroment: number; }; }; }' is not assignable to type 'FargateTaskDefinitionArgs'.
  Types of property 'containers' are incompatible.
    Type '{ nginx: { image: string; memory: number; portMappings: NetworkListener[]; enviroment: number; }; }' is not assignable to type 'Record<string, Container>'.
      Property 'nginx' is incompatible with index signature.
        Type '{ image: string; memory: number; portMappings: NetworkListener[]; enviroment: number; }' is not assignable to type 'Container'.
          Object literal may only specify known properties, but 'enviroment' does not exist in
          type 'Container'. Did you mean to write 'environment'? [2322]

- fargateService.d.ts(169, 5): The expected type comes from property 'taskDefinitionArgs'
  which is declared here on type 'FargateServiceArgs'

The funny thing here is that the final parts of the error message are superb. Namely:

- Object literal may only specify known properties, but 'enviroment' does not exist in
  type 'Container'. Did you mean to write 'environment'? [2322]

- fargateService.d.ts(169, 5): The expected type comes from property 'taskDefinitionArgs'
  which is declared here on type 'FargateServiceArgs'

However, the actual output is quite cluttered and hard to glean the information from. This is esp. true in an ide setting where one sees something like this:

image

This view is particularly problematic due to the wrapping and wall-of-goop nature of this type of error.

My suggestion would be to invert the information being provided by errors. Give the most specific and directly impactful information first (i.e. Object literal may only specify known properties, but 'enviroment' does not exist in type 'Container'. Did you mean to write 'environment'?), and then follow that up with all the extra details that can be used to dive deeper into things when that isn't clear enough.

@CyrusNajmabadi
Copy link
Contributor Author

Tagging @DanielRosenwasser for thoughts here. I'm curious if there is a specific reason why we do things this way? It seems like a bunch of effort was put into extracting out some really good information to tell the user. But that good information is buried after the really crufty info which, while occasionally useful, seems strange to place front and center.

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Feb 5, 2019

Sometimes inside-out is the best error, sometimes outside-in is the best error. It depends on whether your error is because the type "isn't even close" (i.e. you have the completely wrong kind of object), or if there's something "subtly wrong" (i.e. you are off by one property spelling or type).

Consider this code:

// Library code
type Flippy<T> = T extends string ? number : object;
class Alpha<T> {
    getBar() {
        return {
            doSomething() {
                return {
                    another: null as Flippy<T>
                }
            }
        }
    }
}

class Beta<T> {
    getBar() {
        return {
            doSomething() {
                return {
                    another: null as Flippy<T>
                }
            }
        }
    }
}

// User code
const a: Alpha<string> = new Beta<boolean>();

The error message here is:

sample.ts:26:7 - error TS2322: Type 'Beta<boolean>' is not assignable to type 'Alpha<string>'.
  Types of property 'getBar' are incompatible.
    Type '() => { doSomething(): { another: object; }; }' is not assignable to type '() => { doSomething(): { another: number; }; }'.
      Type '{ doSomething(): { another: object; }; }' is not assignable to type '{ doSomething(): { another: number; }; }'.
        Types of property 'doSomething' are incompatible.
          Type '() => { another: object; }' is not assignable to type '() => { another: number; }'.
            Type '{ another: object; }' is not assignable to type '{ another: number; }'.
              Types of property 'another' are incompatible.
                Type 'object' is not assignable to type 'number'.

The last 7 lines of that error message are not really relevant (it's not really the error the developer is making, just an explanation of why the assignment is an error), and arguably only the first line is needed for the user to know what's gone wrong.

Displaying the inner message first here looks like TS has lost its mind:

const a: Alpha<string> = new Beta<boolean>();
// Type 'object' is not assignable to type 'number'.

@DanielRosenwasser
Copy link
Member

Well, for a variable, it would be very confusing if you dove deep into the structure of the type and never explained how the mismatch occurred.

declare var x: { a: { b: { c: { d: number } } } };
declare var y: { a: { b: { c: { d: string } } } };

x = y;

It'd be weird to get an error like

Type 'string' is not assignable to type 'number'.

even if it looks less scary than

Type '{ a: { b: { c: { d: string; }; }; }; }' is not assignable to type '{ a: { b: { c: { d: number; }; }; }; }'.
  Types of property 'a' are incompatible.
    Type '{ b: { c: { d: string; }; }; }' is not assignable to type '{ b: { c: { d: number; }; }; }'.
      Types of property 'b' are incompatible.
        Type '{ c: { d: string; }; }' is not assignable to type '{ c: { d: number; }; }'.
          Types of property 'c' are incompatible.
            Type '{ d: string; }' is not assignable to type '{ d: number; }'.
              Types of property 'd' are incompatible.
                Type 'string' is not assignable to type 'number'.

But when we already try to do this when we have the syntax available, we're able to error exactly on the appropriate declaration.

declare var x: { a: { b: { c: { d: number } } } };
x = { a: { b: { c: { d: "" } } } };
//                   ~ Error: Type 'string' is not assignable to type 'number'.

It's only on excess property checks (like here) that we don't currently, and that's because we don't try to do it as part of the syntactic walk that @weswigham implemented to elaborate on the most specific error location.

@DanielRosenwasser
Copy link
Member

I suppose we could just set a bit to say "don't try to elaborate any further" when excess property checks are issued.

@CyrusNajmabadi
Copy link
Contributor Author

Note: i'm not proposing stripping out the "path" part of the error. Just that it feels as if the innermost message is sometimes far more appropriate on the outside level. I mean, just look at that useless vscode message :)

@RyanCavanaugh
Copy link
Member

How'd you get in a state where the excess property check wasn't the top-level error? Having a hard time reproducing the OP

@CyrusNajmabadi
Copy link
Contributor Author

The error message here is:

sample.ts:26:7 - error TS2322: Type 'Beta<boolean>' is not assignable to type 'Alpha<string>'.

Note: i would say that hte error here is good because the "type not assignable" message is non-complex. And, by 'non-complex' i mean: there is a good named type that can be presented to the user. In this case, saying "Foo is not assignable to Bar" is nice and clear. And the path then leads to an explanation why. When the message is:

'{ containers: { nginx: { image: string; memory: number; portMappings: NetworkListener[]; enviroment: number; }; }; }' is not assignable to type 'FargateTaskDefinitionArgs'

(or even worse at times).

It seems like this may be about having a weighed strategy depending on what sort of message you'd print.

@DanielRosenwasser
Copy link
Member

How'd you get in a state where the excess property check wasn't the top-level error? Having a hard time reproducing the OP

let x: { a: string, b: number } = {
    a: "",
    b: 10,
    c: 20
}

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Feb 5, 2019

To be clear, the span is exact (we literally override the original span) but the exact elaboration comes last.

@CyrusNajmabadi
Copy link
Contributor Author

CyrusNajmabadi commented Feb 5, 2019

It'd be weird to get an error like

Type 'string' is not assignable to type 'number'.

It could be:

Deeply nested assignment of 'd: string' is not assignable to `d: number`
// now print hte long path
       Type 'string' is not assignable to type 'number'.

That's not scary to me :). It explains what's going on to me in the first line. Part of the assignment deep inside isn't compatible. And if that information isn't clear enough, i can walk down the path to find out which part if is complaining about.

@RyanCavanaugh
Copy link
Member

But it's not really nested like the OP shows

let x: { q: { m: { t: { a: string, b: number } } } } = {
    q: {
        m: {
            t: {
                a: "",
                b: 10,
                c: 20
            }
        }
    }
};
sample.ts:4:5 - error TS2322: Type '{ a: string; b: number; c: number; }' is not assignable to type '{ a: string; b: number; }'.
  Object literal may only specify known properties, and 'c' does not exist in type '{ a: string; b: number; }'.

4     c: 20
      ~~~~~

  sample.ts:1:20
    1 let x: { q: { m: { t:  { a: string, b: number } } } }= { q: { m: { t: {
                         ~
    The expected type comes from property 't' which is declared here on type '{ t: { a: string; b: number; }; }'

@CyrusNajmabadi
Copy link
Contributor Author

@RyanCavanaugh Are you asking for a repro for the OP i posted?

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Feb 5, 2019

I just want a multi-level error message originating in an excess property check. That seems like a specific scenario to address by itself.

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Feb 5, 2019

Also Cyrus what version are you on? We might've just gotten better at this.

@CyrusNajmabadi
Copy link
Contributor Author

package.json says "typescript": "^3.0.0". But i'm using: vscode Version: 1.30.2. I'm not sure what that means at the end of the day.

@RyanCavanaugh
Copy link
Member

In the lower-right corner you should see a version number:
image

@RyanCavanaugh
Copy link
Member

... I should just walk over to Cyrus's office?

@CyrusNajmabadi
Copy link
Contributor Author

Hurrdurr. Me smart. Me also on 3.2.2

Where are you now? Our current office is downtown :D

@RyanCavanaugh
Copy link
Member

Didn't realize you moved! I'm still over by the cat bookstore.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Feb 5, 2019
@CyrusNajmabadi
Copy link
Contributor Author

CyrusNajmabadi commented Feb 5, 2019

So this isn't a full repro. But this is basically what we have;

export class FargateService {
    constructor(name: string, args: FargateServiceArgs) {
   }
}

export interface FargateServiceArgs {
    taskDefinitionArgs?: FargateTaskDefinitionArgs;
    // cutting out a lot
}

export interface FargateTaskDefinitionArgs {
    container?: Container;
    containers?: Record<string, Container>;
    // cutting out a lot
}


export interface Container {
    // Properties from aws.ecs.ContainerDefinition
    image?: pulumi.Input<string>;
    memory?: pulumi.Input<number>;
    environment?: pulumi.Input<KeyValuePair[]>;
    // cutting out a lot
}

// And then we're constructing like so
const nginx = new awsx.ecs.FargateService("nginx", {
    taskDefinitionArgs: {
        containers: {
            nginx: {
                image: "nginx",
                memory: 128,
                enviroment: 128,
            },
        },
    },
});

// Unfortunate, Input is a pretty complex type on it's own... but maybe this is enough to get by?

@DanielRosenwasser
Copy link
Member

Check out #29760.

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Sep 25, 2019

Would love to know whether #33361 (PR at #33473) mostly fixes the issue @CyrusNajmabadi @lukehoban.

@Luxcium
Copy link

Luxcium commented Jul 23, 2023

My question is why the error messages in TypeScript can not be inverted... so that if value is not compatible with value is not clear enough I can read down the list instead of being scared to death...

Screenshot_20230723_110319

Credit to @joschan21 for giving me this idea...

to take the error from the OP it would be something like this:

Type 'string' is not assignable to type 'number'.

  Types of property 'd' are incompatible.
    Type '{ d: string; }' is not assignable to type '{ d: number; }'.
      Types of property 'c' are incompatible.
        Type '{ c: { d: string; }; }' is not assignable to type '{ c: { d: number; }; }'.
          Types of property 'b' are incompatible.
            Type '{ b: { c: { d: string; }; }; }' is not assignable to type '{ b: { c: { d: number; }; }; }'.
              Types of property 'a' are incompatible.
                Type '{ a: { b: { c: { d: string; }; }; }; }' is not assignable to type '{ a: { b: { c: { d: number; }; }; }; }'.

@RyanCavanaugh
Copy link
Member

As noted above, if we inverted errors, then the other ~half of the time, you'd have the same problem as you have now. The "correct" end of the error depends on the nature of the problem, which the computer doesn't really have a way of knowing since it doesn't know what your intent was.

@Luxcium
Copy link

Luxcium commented Jul 25, 2023

As noted above, if we inverted errors, then the other ~half of the time, you'd have the same problem as you have now.

Can it be a flag (in the IDE settings) I am using the ubiquitous VSCode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants