By joshstrike


2019-05-13 20:50:57 8 Comments

I'm playing around with conditional types and trying to get this this to validate. Interestingly, the test() function does validate if I pass "val" as the parameter, and fails if I pass "name" ...which is the expected behavior. However, Typescript apparently doesn't think o[p] can be relied on to be a number, and throws this:

Operator '+=' cannot be applied to types 'number' and 'T[{ [K in keyof T]: T[K] extends number ? K : never; }[keyof T]]'.

Am I misunderstanding the usage somehow? I thought 'never' would prohibit any parameters that were not explicitly a number...

class Test {
    public static SumParam<T>
        (t: T[], p:{ [K in keyof T]: T[K] extends number ? K : never }[keyof T]): number {
        let n:number = 0;
        for (let o of t) {
            n += o[p]; //Operator '+=' cannot be applied to types 'number' and 'T[{ [K in keyof T]: T[K] extends number ? K : never; }[keyof T]]'.
        }
        return (n);
    }
    public test(): void {
        Test.SumParam(
            [{ name: "alice", val: 3 },
            { name: "bob", val: 4 }],
            "val"); //validates
        Test.SumParam(
            [{ name: "alice", val: 3 },
            { name: "bob", val: 4 }],
            "name"); //Argument of type '"name"' is not assignable to parameter of type '"val"'.
    }
}

The fact that the compiler narrows and recognizes that 'val' is the only enumerable property extending number ...doesn't that imply that the conditional syntax is working...?

2 comments

@Qwertiy 2019-12-18 00:07:45

I bit other error for name, but in general it works fine:

Playground

class Test {
  public static SumParam<K extends string, T extends { [key in K]: number }>(t: T[], p: K): number {
        let n:number = 0;
        for (let o of t) {
            n += o[p];
        }
        return (n);
    }
    public test(): void {
        Test.SumParam(
            [{ name: "alice", val: 3 },
            { name: "bob", val: 4 }],
            "val"); //validates
        Test.SumParam(
            [{ name: "alice", val: 3 },
            { name: "bob", val: 4 }],
            "name"); // Type 'string' is not assignable to type 'number'.
    }
}

@Titian Cernicova-Dragomir 2019-05-13 21:03:54

Typescript will not be able to follow conditional types that still have unresolved type parameters in them. As such it will not be able to know that o[p] is of type number.

If you don't mind having the error on the items in the array, you can type the function in a way that allows typescript to know o[p] is number:

class Test {
    public static SumParam<T extends Record<K, number>, K extends keyof T>
        (t: T[], p: K): number {
        let n:number = 0;
        if (!t) return (n);
        for (let o of t) {
            n += o[p]; //ok.
        }
        return (n);
    }
    public test(): void {
        Test.SumParam(
            [{ name: "alice", val: 3 },
            { name: "bob", val: 4 }],
            "val"); //ok, no error here
        Test.SumParam(
            [{ name: "alice", val: 3 }, // Type 'string' is not assignable to type 'number'.
            { name: "bob", val: 4 }], // Type 'string' is not assignable to type 'number'.
            "name"); 
    }
} 

@joshstrike 2019-05-13 21:10:14

Yes but I don't want to limit the types in the incoming object (or have that error). What is unresolved in the original example... simply the T? It seems that if there is no keyof T which satisfies 'number', it would resolve 'never', so it should not matter what T is...

@joshstrike 2019-05-13 21:12:16

Interestingly I find that just changing the incoming type to <T extends any> allows it to validate. If T is unresolved, shouldn't it be assumed that it extends any??

@Titian Cernicova-Dragomir 2019-05-13 21:14:16

@joshstrike the version above does limit the incoming object, you still get errors on the second call. Unresolved means that it still contains free type parameters (such as T)

@Titian Cernicova-Dragomir 2019-05-13 21:15:55

@joshstrike T extends any will effectively disable type checking, o[p] will be of type any and no errors will appear no matter what you do with o[p]

@Titian Cernicova-Dragomir 2019-05-13 21:16:34

@joshstrike a type assertion is also an option o[p] as unknown as number

@Titian Cernicova-Dragomir 2019-05-13 21:21:04

@joshstrike I misread you comment about not wanting the error.. shouldn't the second call produce an error since you expect only number keys to be passed in ?

@joshstrike 2019-05-13 21:23:07

TS won't allow o[p] to be coerced into unknown or number... however with <T extends any> the type checking on the key DOES work (it allows "val" and disallows "name"). Yes the second call should produce an error. But there should be no error on the first call (which is where with Record<K, number> we would have an error on name:"Alice")... I think <T extends any> makes the most sense but why shouldn't <T> already be considered to extend any until it is narrowed and resolved?

@Titian Cernicova-Dragomir 2019-05-13 21:27:12

@joshstrike have you tried the code above ? It does not produce an error on the first call. excess property checks do not kick in here, since the type must be T it can have more properties

@Titian Cernicova-Dragomir 2019-05-13 21:32:43

@joshstrike also any is a dangerous type, since it is at the same time a super type for all types and a sub type of all types and is assignable to and from any other type, considering T by default as any would allow any code to be written against T in the method (ie o.nopeNotHere would be valid). If no constraint is specified T is more akin to unknown you can't do anything with it unless you use assertions

@joshstrike 2019-05-13 21:41:13

Okay, I see - the first call doesn't have the error. It seems like a very strange way to narrow T instead of K but this makes sense. Thank you for the explanation.

Related Questions

Sponsored Content

22 Answered Questions

[SOLVED] How do you explicitly set a new property on `window` in TypeScript?

  • 2012-10-03 13:01:15
  • joshuapoehls
  • 342418 View
  • 639 Score
  • 22 Answer
  • Tags:   typescript

36 Answered Questions

[SOLVED] Can't bind to 'ngModel' since it isn't a known property of 'input'

9 Answered Questions

[SOLVED] TypeScript: Interfaces vs Types

  • 2016-05-15 01:53:52
  • user6101582
  • 139894 View
  • 758 Score
  • 9 Answer
  • Tags:   typescript

8 Answered Questions

[SOLVED] How do you specify that a class property is an integer?

  • 2012-10-15 14:25:58
  • Josh
  • 70667 View
  • 96 Score
  • 8 Answer
  • Tags:   typescript

1 Answered Questions

[SOLVED] Type narrowing of event callback based on the event key

  • 2019-12-04 11:16:27
  • Codeceps
  • 88 View
  • 2 Score
  • 1 Answer
  • Tags:   typescript

1 Answered Questions

[SOLVED] Use of Conditional types and mapped types with array reduce method

  • 2018-08-11 08:41:51
  • ford04
  • 119 View
  • 0 Score
  • 1 Answer
  • Tags:   typescript

1 Answered Questions

[SOLVED] TypeScript ignores optional parameter of type never

  • 2017-08-26 14:50:53
  • Wilco Bakker
  • 184 View
  • 1 Score
  • 1 Answer
  • Tags:   typescript types

1 Answered Questions

[SOLVED] Typescript type narrowed to never with instanceof in an if-else statement

3 Answered Questions

[SOLVED] Double Equals (==) in TypeScript

  • 2012-12-19 12:55:59
  • RichardTowers
  • 35986 View
  • 15 Score
  • 3 Answer
  • Tags:   typescript

Sponsored Content