Skip to content

Contextual typing not working on optional members #16817

Closed

Description

The following sample works perfectly in noImplicitAny unless I turn on strictNullChecks. In that event, the s parameter on foo seems to get an implicit any.

interface ActionsObject<State> {
    [prop: string]: (state: State) => State
}

interface Options<State, Actions> {
    state?: State;
    view?: (state: State, actions: Actions) => any;
    actions?: Actions;
}

declare function app<State, Actions extends ActionsObject<State>>(obj: Options<State, Actions>): void;

app({
    state: 100,
    actions: {
        foo: s => s
    },
    view: (s, a) => null as any,
})

If I make actions non-optional, then things work perfectly again.

Context: hyperapp/hyperapp#87

Activity

  1. weswigham commented on Aug 7, 2017

    @weswigham
    Member

    This isn't strictNullChecks specific (though it's much easier to trigger the bug there); we're failing to produce parameter inferences for any parameter within an index within a union:

        interface ActionsObject<State> {
            [prop: string]: (state: State) => State;
        }
        
        interface Options<State, Actions> {
            state?: State;
            view?: (state: State, actions: Actions) => any;
            actions: string | Actions;
        }
        
        declare function app<State, Actions extends ActionsObject<State>>(obj: Options<State, Actions>): void;
        
        app({
            state: 100,
            actions: {
                foo: s => s
                     ~
    !!! error TS7006: Parameter 's' implicitly has an 'any' type.
            },
            view: (s, a) => undefined as any,
        });
  2. ahejlsberg commented on Oct 20, 2017

    @ahejlsberg
    Member

    This is working as intended. The actions property of the object literal is contextually typed by a type parameter type Actions, and during the inference process we don't yet know what the type argument for Actions will be (and constraints don't factor into type inference). I suggest rewriting the example to:

    interface ActionsObject<State> {
        [prop: string]: (state: State) => State
    }
    
    interface Options<State> {
        state?: State;
        actions?: ActionsObject<State>;
    }
    
    declare function app<State>(obj: Options<State>): void;
    
    app({
        state: 100,
        actions: {
            foo: s => s
        },
    })

    If it is really important to capture the actual type of the object literal, it can be done with an intersection type:

    interface ActionsObject<State> {
        [prop: string]: (state: State) => State
    }
    
    interface Options<State> {
        state?: State;
        actions?: ActionsObject<State>;
    }
    
    declare function app<State, Actions>(obj: Options<State> & Actions): void;
    
    app({
        state: 100,
        actions: {
            foo: s => s
        },
    })

    (I omitted the view property to reduce noise.)

  3. added
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    and removed
    BugA bug in TypeScript
    on Oct 20, 2017
  4. mhegazy commented on Nov 6, 2017

    @mhegazy
    Contributor

    Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

  5. weswigham commented on Nov 6, 2017

    @weswigham
    Member

    This issue is tagged incorrectly and we have a fix up, it's pretty simple. We actually do contextually type the nonunion case right (the generic's constraint is the contextual type), so it is, in fact, a bug that a union is not appropriately contextually typed. The issue is that when we get the apparent type of a contextual type, we didn't get the apparent type of all union members - we just returned the union.

  6. added
    BugA bug in TypeScript
    and removed
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    on Nov 6, 2017
  7. locked and limited conversation to collaborators on Jun 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptFixedA PR has been merged for this issue

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions