【问题标题】:Typescript generic type does not enforce type safetyTypescript 泛型类型不强制类型安全
【发布时间】:2021-05-05 09:44:28
【问题描述】:

我正在尝试生成一个通用函数,该函数允许我为给定类型生成强类型设置器,并带有回调 - 例如:

interface Foo {
   a: number,
   b: string
}

magick('a', 43) => {} // Should work
magick('a', '43') => {} // Should fail

我已经实现了一个通用函数来执行此操作 - 并且它有效。但是,如果我尝试复制该函数类型安全并没有强制执行(或者我更可能误解了打字稿!)

interface Test {
  a: number;
  b: string;
}

interface Test2 {
  a2: boolean;
  b2: '+' | '-';
}

const testVal: Test = {
  a: 42,
  b: 'test',
};

type Demo<T> = <K extends keyof T> (key: K, val: T[K]) => void

const implDemo: Demo<Test> = (key, val) => {
  testVal[key] = val;
};

首先 - 该功能按我想要的方式工作:

/* prints: {a: 10, b: "test"} - correct  */
implDemo('a', 10); console.log(testVal); 

/* Fails as expected - type safety - a should be number */
implDemo('a', 'text'); 

但是为什么会出现这种情况呢?怎么可能Demo&lt;Test2&gt; 可分配给Demo&lt;Test&gt;

/* Create a pointer to implDemo - but with WRONG type!!! */
const implDemo2: Demo<Test2> = implDemo; 
implDemo2('a2', true); 
console.log(testVal); 
/* prints: {a: 10, b: "test", a2: true} - we just violated out type!!!! */

我们刚才做的和上面做的一样:

testVal['a2'] = true; /* Doesn't work - which it shouldn't! */

这是另一种简单类型,实际上强制执行类型安全

type Demo2<T> = (val: T) => void;
const another: Demo2<string> = (val) => {};
/* This fails - as expected */
const another2: Demo2<number> = another;

这是打字稿中的错误 - 还是我误解了什么?我怀疑Demo&lt;T&gt; = &lt;K extends keyof T&gt; 类型是罪魁祸首,但我根本不明白我是如何被允许以这种方式“破解”类型系统的。

【问题讨论】:

  • 是的,这在我看来像是一个编译器错误;不确定是否已经被举报。

标签: typescript


【解决方案1】:

我会说这是一个编译器错误。 我不确定是不是reported;到目前为止,我还没有通过搜索找到任何东西,但这并不意味着它不存在。 编辑:我已经提交了an issue 关于这种行为;我们会看看会发生什么。更新:看起来这可能是fixed for TS3.5 TS3.6 TS3.7...谁知道?!

这是复制品:

interface A { a: number; }
interface B { b: string; }

type Demo<T> = <K extends keyof T> (key: K, val: T[K]) => void

// Demo<A> should not be assignable to Demo<B>, but it is?!
declare const implDemoA: Demo<A>;
const implDemoB: Demo<B> = implDemoA; // no error!? ?

// Note that we can manually make a concrete type DemoA and DemoB:
type DemoA = <K extends keyof A>(key: K, val: A[K]) => void;
type DemoB = <K extends keyof B>(key: K, val: B[K]) => void;

type MutuallyAssignable<T extends U, U extends V, V=T> = true;
// the compiler agrees that DemoA and Demo<A> are mutually assignable
declare const testAWitness: MutuallyAssignable<Demo<A>, DemoA>; // okay
// the compiler agrees that DemoB and Demo<B> are mutually assignable
declare const testBWitness: MutuallyAssignable<Demo<B>, DemoB>; // okay

// And the compiler *correctly* sees that DemoA is not assignable to DemoB
declare const implDemoAConcrete: DemoA;
const implDemoBConcrete: DemoB = implDemoAConcrete; // error as expected
//    ~~~~~~~~~~~~~~~~~ <-- Type 'DemoA' is not assignable to type 'DemoB'.

?Link to code?‍?

您可以看到DemoADemo&lt;A&gt; 基本上是同一类型(它们是可相互赋值的,这意味着可以将一种类型的值分配给另一种类型的变量)。而DemoBDemo&lt;B&gt; 也是同一类型。并且编译器确实知道DemoA 不能分配给DemoB

但是编译器认为Demo&lt;A&gt; 可以分配给Demo&lt;B&gt;,这是你的问题。就好像编译器“忘记”了 Demo&lt;T&gt; 中的 T 是什么。


如果您现在确实需要解决此问题,您可能希望使用能够“记住”T 是什么的东西来标记Demo&lt;T&gt;,但不会阻止您将实现分配给它:

// workaround    
type Demo<T> = (<K extends keyof T> (key: K, val: T[K]) => void) & {__brand?: T};

const implDemoA: Demo<A> = (key, val) => {} // no error
const implDemoB: Demo<B> = implDemoA; // error here as expected

好的,希望对您有所帮助;祝你好运!

【讨论】:

  • 是的,我一直在玩弄它并找到了一个类似的解决方案 - 它似乎真的忘记了 T:type Demo&lt;T&gt; = &lt;K extends keyof T&gt; (key: K, val: T[K], hack?: T) =&gt; void - 但你的解决方案更好!
  • 好吧,我搜索并搜索了 GitHub 问题,但找不到任何东西。除非您愿意,否则我可能会就此提出问题。
  • 我会让问题再开放一天,也许蜂巢有答案:) 也许等一天,如果没有人知道你提出问题?感谢您的反馈!
  • 并且已经有一个暂定的fix,可能会用于 TypeScript 3.5。
猜你喜欢
  • 2018-08-26
  • 1970-01-01
  • 1970-01-01
  • 2019-05-30
  • 1970-01-01
  • 2019-09-28
  • 2015-06-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多