Typescript: Destructuring of optional fn + default function value gets signature from the value

Created on 28 Nov 2019  路  2Comments  路  Source: microsoft/TypeScript

TypeScript Version: 3.7.2 & 3.8.0-dev.20191128

Search Terms:
destructuring default value function

Code

interface ReturnVal {
    something(a: string): string // has one param
}

function getFn(): ReturnVal {
    return {
        something(a) { return a }
    }
}

interface PartialOptions {
    something?(a: string, b?: string): string // has two params
}

function run(options: PartialOptions) {
    const { something = getFn().something } = options;

    const something2 = options.something ?? getFn().something;

    return {
        method() {
            something('', '') // this should not error
            something2('', '') // also wrong?

            // and now the twist: hovering "something" and something2 (above) shows *correct* signatures. :)
        }
    }
}

Expected behavior:
Pick up the function signature of PartialOptions when checking the call.

Actual behavior:
Hover shows correct signature. Type check breaks saying single parameter is expected.

Playground Link:
http://www.typescriptlang.org/play/?ssl=1&ssc=1&pln=29&pc=1#code/JYOwLgpgTgZghgYwgAgEoTAVyiAanAG2QG8AoZC5AZwHsBbDAC1AHMAKOALmrClYEpuVXq2QB6MckZwqyGiBQAHOFDh1SAX1KkYmEAjDB5yFhgBiINoLQZseQiXKUotnI8ofq9Jqw78SyC5YbnDIWh5aWqSgkLCIKAAKKoaEAPKKhvKyZB60DGDMICwA-BxCIkUANMgARsXlfEXWwo0s4pLSsmAA7jTIyqp0VJrauvqZIIF6bDQZRiBU3ElQKQTpE1T+OZQIWWABeT5FyAC8JuaW-AB0hwWiGqdyc1kA3Noeuwv7t4UsAEyPWYbG7eO7HYrFc5gCxWEH5X5vJwUIJ2dyeCjwmgAEysaPRuVBvzYAHJidVSf4JMg7rIqIwaJgCFjkCAaPtoFAaFAkfiKD9WH8SWTkBT2shCLRkN1OUViu9eZQqXAQMzWd1qYwUD1gMJuPSAG7QUQAIn5RWN4pVXnhAuQHBqNEN-jpNG6sgAVLsoC4DO7qMAWCA4MEIFQrshOPweREeZFSEA

Related Issues:
found many issues about destructuring, but couldn't identify an issue about default values for functions. some are about generics behaviour, etc.

Bug Fix Available

Most helpful comment

Fun one. Issue one is that types () => void and (x?: string) => void are both subtypes of each other. Issue two is that we sort union type constituents by their type ID before we perform subtype reduction. Type IDs depend on the order in which type relation checks are performed (or not performed), which is why commenting out the seemingly unrelated const declaration causes downstream effects.

Possible remedies include making the subtype relationship more selective or better preserving the order of constituent types for operations like || and && before performing subtype reduction.

All 2 comments

This is certainly the most interesting bug of the day. A reduced form:

interface ReturnVal {
    something(): void;
    // Workaround line:
    // something: () => void;
}

// Comment this out to remove the error
const k: ReturnVal = { something() { } }

declare const val: ReturnVal;
function run(options: { something?(b?: string): void }) {
    const something = options.something ?? val.something;
    something('');
}

Our hypothesis is that we're not using the method bivariance as part of the cache key for relationships and we end up over-reducing the something initializer to the zero-argument form.

Fun one. Issue one is that types () => void and (x?: string) => void are both subtypes of each other. Issue two is that we sort union type constituents by their type ID before we perform subtype reduction. Type IDs depend on the order in which type relation checks are performed (or not performed), which is why commenting out the seemingly unrelated const declaration causes downstream effects.

Possible remedies include making the subtype relationship more selective or better preserving the order of constituent types for operations like || and && before performing subtype reduction.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

OliverJAsh picture OliverJAsh  路  242Comments

rbuckton picture rbuckton  路  139Comments

fdecampredon picture fdecampredon  路  358Comments

born2net picture born2net  路  150Comments

blakeembrey picture blakeembrey  路  171Comments