Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.9k views
in Technique[技术] by (71.8m points)

typescript - How to resolve generic type of property value from decorated property in decorator

I'm playing with some code, which resolve generic type of property value and doesn't allow provide wrong value. But when I change from TValue to (t: TValue) => TValue, type TValue doesn't going resolved more. It's now unknown {} type, not number more

Example without function. Working good

type ProtoOf<T> = Pick<T, keyof T>;

function decorate<TValue>(value: TValue) {
  return <T extends { [KA in TKey]: TValue }, TKey extends keyof T>(
    proto: ProtoOf<T> & { [P in TKey]: TValue },
    propertyKey: TKey
  ) => {};
}

class Foo {
  // TS error: none
  // Result: EXPECTED
  @decorate(1) bar: number = 1;

  // TS Error:
  // Types of property 'wrongBar' are incompatible
  // Type 'number' is not assignable to type 'string'
  // Result: EXPECTED
  @decorate('') wrongBar: number = 1;
}

Example with function. Don't working as expected

type ProtoOf<T> = Pick<T, keyof T>;

function decorate<TValue>(getValue: (t: TValue) => TValue) {
  return <T extends { [KA in TKey]: TValue }, TKey extends keyof T>(
    proto: ProtoOf<T> & { [P in TKey]: TValue },
    propertyKey: TKey
  ) => {};
}

class Foo {
  // TS Error: Operator '+' cannot be applied to types '{}' and '1'
  // Result: NOT EXPECTED: because we can assign `number` to `number`
  @decorate(v => v + 1) bar: number = 1;

  // TS error: none
  // Result: NOT EXPECTED: we should have error, we cannot assign `string` to `number`
  @decorate(v => v + '') wrongBar: number = 1;
}

I'm expected TValue equal number in example with function as in example without function

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

This is a known issue, as you are aware from your GitHub comments. Summarizing here:

Currently, type inference does not work the way you want it to, as the compiler treats the original as equivalent to something like this:

const barDeco = decorate(v => v + 1); // error
barDeco(Foo.prototype, "bar");
const wrongBarDeco = decorate(v => v + '');
wrongBarDeco(Foo.prototype, "wrongBar");

And the calls to decorate() in barDeco and wrongBarDeco don't have enough type information for the compiler to infer the generic type, and thus it is inferred as {}, resulting in much sadness. The decorator is basically a curried function f(x)(y), and to fix this the compiler would have to infer the type of f from the type of y, which is a new sort of contextual typing. Maybe decorators could be special-cased for such inference; it would likely be a massive breaking change to do that with curried functions in general.

For now the only way to deal with this is to manually specify the generic parameter when calling the decorator as in

class Foo {
  @decorate<number>(v => v + 1) bar: number = 1; // okay
  @decorate<number>(v => v + '') wrongBar: number = 1; // error
}

or to manually annotate your callback as in

class Foo {
  @decorate((v: number) => v + 1) bar: number = 1; // okay
  @decorate((v: number) => v + '') wrongBar: number = 1; // error
}

These workarounds are not optimal, but they do work, so you have some way to deal with things unless and until Microsoft/TypeScript#2607 is addressed. There are many, many open issues, so I wouldn't expect to see much movement on this one. The likelihood will increase if more people go to that issue and give it a ?? and describe compelling use cases and compelling reasons why the workarounds are insufficient. Since you have already done this, I think there isn't much left for you to do but move on. If future readers care about this, they can check out the issue in GitHub and contribute.

Sorry there isn't a better answer for you. Good luck!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...