Skip to content

Avoid listing missing properties on types with only call/construct signatures #35735

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

Closed
DanielRosenwasser opened this issue Dec 17, 2019 · 13 comments · Fixed by #40973
Closed
Labels
Domain: Error Messages The issue relates to error messaging Experience Enhancement Noncontroversial enhancements Good First Issue Well scoped, documented and has the green light Help Wanted You can do this PursuitFellowship Help wanted from Pursuit fellowship; others please avoid until Dec 19 Suggestion An idea for TypeScript

Comments

@DanielRosenwasser
Copy link
Member

Found with @RyanCavanaugh

interface Dog {
    barkable: true
}

declare function getRover(): Dog;

export let x: Dog = getRover;

Expected

Type '() => Dog' is not assignable to 'Dog'.(2741)
  input.ts(7, 21): Did you mean to call this expression?

Actual

Property 'barkable' is missing in type '() => Dog' but required in type 'Dog'.(2741)
  input.ts(2, 5): 'barkable' is declared here.
  input.ts(7, 21): Did you mean to call this expression?
@DanielRosenwasser DanielRosenwasser added Help Wanted You can do this Good First Issue Well scoped, documented and has the green light Domain: Error Messages The issue relates to error messaging Experience Enhancement Noncontroversial enhancements labels Dec 17, 2019
@weswigham
Copy link
Member

weswigham commented Dec 17, 2019

Hm, OK, but suppose

interface Dog {
    caller: {
        caller: {
            collar: boolean;
        };
    }
}

declare function getRover(): Dog;

export let x: Dog = getRover;

for which we issue

Type '() => Dog' is not assignable to type 'Dog'.
  The types of 'caller.caller' are incompatible between these types.
    Property 'collar' is missing in type 'Function' but required in type '{ collar: boolean; }'.(2322)
input.ts(4, 13): 'collar' is declared here.
input.ts(11, 21): Did you mean to call this expression?

what's the rule here? In the above example, I can't make a clear judgement call on which suggestion is more relevant.

@DanielRosenwasser
Copy link
Member Author

If you are in the off case where you have a name that isn't a string, a length that isn't a number, or any property named caller, I'm sure you'll figure it out on your own.

@RyanCavanaugh
Copy link
Member

When the return type of a function is an identity match for the target type, it seems extremely unlikely that "You forgot to call it?" is not the best possible error message. It requires a lot of construction to make something where a function and its return type are both plausible matches for the target.

I would be curious to find out how often a function is ever an intentional source for a target with zero signatures that isn't a top-ish type. Even assigning a function to something nominally uncontroversial like { name: string; age?; number } smells like a bug, and people have complained to that effect.

@weswigham
Copy link
Member

I would be curious to find out how often a function is ever an intentional source for a target with zero signatures that isn't a top-ish type

I've seen assignments to {length: number} in the wild.

@openorclose
Copy link

Does this still need help? I would like to work on this.

@RyanCavanaugh RyanCavanaugh added the Suggestion An idea for TypeScript label Jan 28, 2020
@DanielRosenwasser
Copy link
Member Author

Yes, we're still looking for help here!

@openorclose
Copy link

Ok, I'll work on this!

@DanielRosenwasser
Copy link
Member Author

DanielRosenwasser commented Mar 27, 2020

I think the fix should be relatively self-contained within structuredTypeRelatedTo, preceding or around the block

                    // Even if relationship doesn't hold for unions, intersections, or generic type references,
                    // it may hold in a structural comparison.
                    // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
                    // to X. Failing both of those we want to check if the aggregation of A and B's members structurally
                    // relates to X. Thus, we include intersection types on the source side here.
                    if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
                        // Report structural errors only if we haven't reported any errors yet
                        const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive;
                        result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, intersectionState);
                        if (result) {
                            result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors);
                            if (result) {
                                result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors);
                                if (result) {
                                    result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors, intersectionState);
                                    if (result) {
                                        result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors, intersectionState);
                                    }
                                }
                            }

@sandersn sandersn added the PursuitFellowship Help wanted from Pursuit fellowship; others please avoid until Dec 19 label Sep 18, 2020
@liewrichmond
Copy link
Contributor

Hi, I'm new here and would like to give this a try. Would anyone mind if I started working on this?

@RyanCavanaugh
Copy link
Member

@liewrichmond go for it! 👍

@liewrichmond
Copy link
Contributor

I've been working on a fix, and I think I might have one but it breaks a couple of tests that I'm not too sure if it's intended behavior or not.

For example,

interface Dog {
    barkable: true
}

declare function getRover(): Dog

let x: any[] = []
    
x = getRover;

Before Change

Type '() => Dog' is missing the following properties from type 'any[]': pop, push, concat, join, and 25 more.ts(2740)

After Change

Type '() => Dog' is not assignable to type 'any[]'.ts(2322)

Is this within spec? and should I submit a PR to get feedback/suggestions on the other failing tests due to the change?

@DanielRosenwasser
Copy link
Member Author

Yeah, that looks good. You can send a PR and we should be able to iterate on it.

liewrichmond added a commit to liewrichmond/TypeScript that referenced this issue Oct 6, 2020
liewrichmond added a commit to liewrichmond/TypeScript that referenced this issue Oct 7, 2020
weswigham added a commit that referenced this issue Mar 4, 2022
…all/construct signatures (#40973)

* Fixes #35735

* fixes #35735

* PR feedback

Co-authored-by: Wesley Wigham <[email protected]>
@DanielRosenwasser
Copy link
Member Author

Thank you @liewrichmond!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: Error Messages The issue relates to error messaging Experience Enhancement Noncontroversial enhancements Good First Issue Well scoped, documented and has the green light Help Wanted You can do this PursuitFellowship Help wanted from Pursuit fellowship; others please avoid until Dec 19 Suggestion An idea for TypeScript
Projects
None yet
6 participants