Merge branch 'master' of https://github.com/Microsoft/TypeScript into feature/eslint

This commit is contained in:
Alexander T 2019-08-09 09:39:36 +03:00
commit 6988e25985
12 changed files with 650 additions and 8 deletions

View File

@ -14266,20 +14266,25 @@ namespace ts {
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary): Type | undefined;
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type): Type;
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type) {
let match: Type | undefined;
// undefined=unknown, true=discriminated, false=not discriminated
// The state of each type progresses from left to right. Discriminated types stop at 'true'.
const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[];
for (const [getDiscriminatingType, propertyName] of discriminators) {
let i = 0;
for (const type of target.types) {
const targetType = getTypeOfPropertyOfType(type, propertyName);
if (targetType && related(getDiscriminatingType(), targetType)) {
if (match) {
if (type === match) continue; // Finding multiple fields which discriminate to the same type is fine
return defaultValue;
}
match = type;
discriminable[i] = discriminable[i] === undefined ? true : discriminable[i];
}
else {
discriminable[i] = false;
}
i++;
}
}
return match || defaultValue;
const match = discriminable.indexOf(/*searchElement*/ true);
// make sure exactly 1 matches before returning it
return match === -1 || discriminable.indexOf(/*searchElement*/ true, match + 1) !== -1 ? defaultValue : target.types[match];
}
/**

View File

@ -980,4 +980,12 @@ namespace ts.server {
if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) {
ts.sys.tryEnableSourceMapsForHost();
}
// Overwrites the current console messages to instead write to
// the log. This is so that language service plugins which use
// console.log don't break the message passing between tsserver
// and the client
console.log = (...args) => logger.msg(args.length === 1 ? args[0] : args.join(", "), Msg.Info);
console.warn = (...args) => logger.msg(args.length === 1 ? args[0] : args.join(", "), Msg.Err);
console.error = (...args) => logger.msg(args.length === 1 ? args[0] : args.join(", "), Msg.Err);
}

View File

@ -0,0 +1,78 @@
tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(30,5): error TS2322: Type '{ type: "number"; value: number; multipleOf: number; format: string; }' is not assignable to type 'Primitive'.
Object literal may only specify known properties, and 'multipleOf' does not exist in type 'Float'.
tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(41,5): error TS2322: Type '{ p1: "left"; p2: false; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'.
Object literal may only specify known properties, and 'p3' does not exist in type '{ p1: "left"; p2: boolean; }'.
tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(57,5): error TS2322: Type '{ p1: "right"; p2: false; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'.
Object literal may only specify known properties, and 'p3' does not exist in type '{ p1: "right"; p2: false; p4: string; }'.
==== tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts (3 errors) ====
// Repro from #32657
interface Base<T> {
value: T;
}
interface Int extends Base<number> {
type: "integer";
multipleOf?: number;
}
interface Float extends Base<number> {
type: "number";
}
interface Str extends Base<string> {
type: "string";
format?: string;
}
interface Bool extends Base<boolean> {
type: "boolean";
}
type Primitive = Int | Float | Str | Bool;
const foo: Primitive = {
type: "number",
value: 10,
multipleOf: 5, // excess property
~~~~~~~~~~~~~
!!! error TS2322: Type '{ type: "number"; value: number; multipleOf: number; format: string; }' is not assignable to type 'Primitive'.
!!! error TS2322: Object literal may only specify known properties, and 'multipleOf' does not exist in type 'Float'.
format: "what?"
}
type DisjointDiscriminants = { p1: 'left'; p2: true; p3: number } | { p1: 'right'; p2: false; p4: string } | { p1: 'left'; p2: boolean };
// This has excess error because variant three is the only applicable case.
const a: DisjointDiscriminants = {
p1: 'left',
p2: false,
p3: 42,
~~~~~~
!!! error TS2322: Type '{ p1: "left"; p2: false; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'.
!!! error TS2322: Object literal may only specify known properties, and 'p3' does not exist in type '{ p1: "left"; p2: boolean; }'.
p4: "hello"
};
// This has no excess error because variant one and three are both applicable.
const b: DisjointDiscriminants = {
p1: 'left',
p2: true,
p3: 42,
p4: "hello"
};
// This has excess error because variant two is the only applicable case
const c: DisjointDiscriminants = {
p1: 'right',
p2: false,
p3: 42,
~~~~~~
!!! error TS2322: Type '{ p1: "right"; p2: false; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'.
!!! error TS2322: Object literal may only specify known properties, and 'p3' does not exist in type '{ p1: "right"; p2: false; p4: string; }'.
p4: "hello"
};

View File

@ -0,0 +1,91 @@
//// [excessPropertyCheckWithMultipleDiscriminants.ts]
// Repro from #32657
interface Base<T> {
value: T;
}
interface Int extends Base<number> {
type: "integer";
multipleOf?: number;
}
interface Float extends Base<number> {
type: "number";
}
interface Str extends Base<string> {
type: "string";
format?: string;
}
interface Bool extends Base<boolean> {
type: "boolean";
}
type Primitive = Int | Float | Str | Bool;
const foo: Primitive = {
type: "number",
value: 10,
multipleOf: 5, // excess property
format: "what?"
}
type DisjointDiscriminants = { p1: 'left'; p2: true; p3: number } | { p1: 'right'; p2: false; p4: string } | { p1: 'left'; p2: boolean };
// This has excess error because variant three is the only applicable case.
const a: DisjointDiscriminants = {
p1: 'left',
p2: false,
p3: 42,
p4: "hello"
};
// This has no excess error because variant one and three are both applicable.
const b: DisjointDiscriminants = {
p1: 'left',
p2: true,
p3: 42,
p4: "hello"
};
// This has excess error because variant two is the only applicable case
const c: DisjointDiscriminants = {
p1: 'right',
p2: false,
p3: 42,
p4: "hello"
};
//// [excessPropertyCheckWithMultipleDiscriminants.js]
// Repro from #32657
var foo = {
type: "number",
value: 10,
multipleOf: 5,
format: "what?"
};
// This has excess error because variant three is the only applicable case.
var a = {
p1: 'left',
p2: false,
p3: 42,
p4: "hello"
};
// This has no excess error because variant one and three are both applicable.
var b = {
p1: 'left',
p2: true,
p3: 42,
p4: "hello"
};
// This has excess error because variant two is the only applicable case
var c = {
p1: 'right',
p2: false,
p3: 42,
p4: "hello"
};

View File

@ -0,0 +1,143 @@
=== tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts ===
// Repro from #32657
interface Base<T> {
>Base : Symbol(Base, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 0, 0))
>T : Symbol(T, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 2, 15))
value: T;
>value : Symbol(Base.value, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 2, 19))
>T : Symbol(T, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 2, 15))
}
interface Int extends Base<number> {
>Int : Symbol(Int, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 4, 1))
>Base : Symbol(Base, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 0, 0))
type: "integer";
>type : Symbol(Int.type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 6, 36))
multipleOf?: number;
>multipleOf : Symbol(Int.multipleOf, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 7, 20))
}
interface Float extends Base<number> {
>Float : Symbol(Float, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 9, 1))
>Base : Symbol(Base, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 0, 0))
type: "number";
>type : Symbol(Float.type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 11, 38))
}
interface Str extends Base<string> {
>Str : Symbol(Str, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 13, 1))
>Base : Symbol(Base, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 0, 0))
type: "string";
>type : Symbol(Str.type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 15, 36))
format?: string;
>format : Symbol(Str.format, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 16, 19))
}
interface Bool extends Base<boolean> {
>Bool : Symbol(Bool, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 18, 1))
>Base : Symbol(Base, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 0, 0))
type: "boolean";
>type : Symbol(Bool.type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 20, 38))
}
type Primitive = Int | Float | Str | Bool;
>Primitive : Symbol(Primitive, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 22, 1))
>Int : Symbol(Int, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 4, 1))
>Float : Symbol(Float, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 9, 1))
>Str : Symbol(Str, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 13, 1))
>Bool : Symbol(Bool, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 18, 1))
const foo: Primitive = {
>foo : Symbol(foo, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 26, 5))
>Primitive : Symbol(Primitive, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 22, 1))
type: "number",
>type : Symbol(type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 26, 24))
value: 10,
>value : Symbol(value, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 27, 19))
multipleOf: 5, // excess property
>multipleOf : Symbol(multipleOf, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 28, 14))
format: "what?"
>format : Symbol(format, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 29, 18))
}
type DisjointDiscriminants = { p1: 'left'; p2: true; p3: number } | { p1: 'right'; p2: false; p4: string } | { p1: 'left'; p2: boolean };
>DisjointDiscriminants : Symbol(DisjointDiscriminants, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 31, 1))
>p1 : Symbol(p1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 30))
>p2 : Symbol(p2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 42))
>p3 : Symbol(p3, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 52))
>p1 : Symbol(p1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 69))
>p2 : Symbol(p2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 82))
>p4 : Symbol(p4, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 93))
>p1 : Symbol(p1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 110))
>p2 : Symbol(p2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 122))
// This has excess error because variant three is the only applicable case.
const a: DisjointDiscriminants = {
>a : Symbol(a, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 37, 5))
>DisjointDiscriminants : Symbol(DisjointDiscriminants, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 31, 1))
p1: 'left',
>p1 : Symbol(p1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 37, 34))
p2: false,
>p2 : Symbol(p2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 38, 15))
p3: 42,
>p3 : Symbol(p3, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 39, 14))
p4: "hello"
>p4 : Symbol(p4, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 40, 11))
};
// This has no excess error because variant one and three are both applicable.
const b: DisjointDiscriminants = {
>b : Symbol(b, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 45, 5))
>DisjointDiscriminants : Symbol(DisjointDiscriminants, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 31, 1))
p1: 'left',
>p1 : Symbol(p1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 45, 34))
p2: true,
>p2 : Symbol(p2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 46, 15))
p3: 42,
>p3 : Symbol(p3, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 47, 13))
p4: "hello"
>p4 : Symbol(p4, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 48, 11))
};
// This has excess error because variant two is the only applicable case
const c: DisjointDiscriminants = {
>c : Symbol(c, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 53, 5))
>DisjointDiscriminants : Symbol(DisjointDiscriminants, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 31, 1))
p1: 'right',
>p1 : Symbol(p1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 53, 34))
p2: false,
>p2 : Symbol(p2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 54, 16))
p3: 42,
>p3 : Symbol(p3, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 55, 14))
p4: "hello"
>p4 : Symbol(p4, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 56, 11))
};

View File

@ -0,0 +1,141 @@
=== tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts ===
// Repro from #32657
interface Base<T> {
value: T;
>value : T
}
interface Int extends Base<number> {
type: "integer";
>type : "integer"
multipleOf?: number;
>multipleOf : number
}
interface Float extends Base<number> {
type: "number";
>type : "number"
}
interface Str extends Base<string> {
type: "string";
>type : "string"
format?: string;
>format : string
}
interface Bool extends Base<boolean> {
type: "boolean";
>type : "boolean"
}
type Primitive = Int | Float | Str | Bool;
>Primitive : Primitive
const foo: Primitive = {
>foo : Primitive
>{ type: "number", value: 10, multipleOf: 5, // excess property format: "what?"} : { type: "number"; value: number; multipleOf: number; format: string; }
type: "number",
>type : "number"
>"number" : "number"
value: 10,
>value : number
>10 : 10
multipleOf: 5, // excess property
>multipleOf : number
>5 : 5
format: "what?"
>format : string
>"what?" : "what?"
}
type DisjointDiscriminants = { p1: 'left'; p2: true; p3: number } | { p1: 'right'; p2: false; p4: string } | { p1: 'left'; p2: boolean };
>DisjointDiscriminants : DisjointDiscriminants
>p1 : "left"
>p2 : true
>true : true
>p3 : number
>p1 : "right"
>p2 : false
>false : false
>p4 : string
>p1 : "left"
>p2 : boolean
// This has excess error because variant three is the only applicable case.
const a: DisjointDiscriminants = {
>a : DisjointDiscriminants
>{ p1: 'left', p2: false, p3: 42, p4: "hello"} : { p1: "left"; p2: false; p3: number; p4: string; }
p1: 'left',
>p1 : "left"
>'left' : "left"
p2: false,
>p2 : false
>false : false
p3: 42,
>p3 : number
>42 : 42
p4: "hello"
>p4 : string
>"hello" : "hello"
};
// This has no excess error because variant one and three are both applicable.
const b: DisjointDiscriminants = {
>b : DisjointDiscriminants
>{ p1: 'left', p2: true, p3: 42, p4: "hello"} : { p1: "left"; p2: true; p3: number; p4: string; }
p1: 'left',
>p1 : "left"
>'left' : "left"
p2: true,
>p2 : true
>true : true
p3: 42,
>p3 : number
>42 : 42
p4: "hello"
>p4 : string
>"hello" : "hello"
};
// This has excess error because variant two is the only applicable case
const c: DisjointDiscriminants = {
>c : DisjointDiscriminants
>{ p1: 'right', p2: false, p3: 42, p4: "hello"} : { p1: "right"; p2: false; p3: number; p4: string; }
p1: 'right',
>p1 : "right"
>'right' : "right"
p2: false,
>p2 : false
>false : false
p3: 42,
>p3 : number
>42 : 42
p4: "hello"
>p4 : string
>"hello" : "hello"
};

View File

@ -23,9 +23,11 @@ tests/cases/compiler/excessPropertyCheckWithUnions.ts(50,35): error TS2322: Type
tests/cases/compiler/excessPropertyCheckWithUnions.ts(66,9): error TS2326: Types of property 'n' are incompatible.
Type '{ a: string; b: string; }' is not assignable to type 'AN'.
Object literal may only specify known properties, and 'b' does not exist in type 'AN'.
tests/cases/compiler/excessPropertyCheckWithUnions.ts(87,5): error TS2322: Type '{ tag: "button"; type: "submit"; href: string; }' is not assignable to type 'Union'.
Object literal may only specify known properties, and 'href' does not exist in type 'Button'.
==== tests/cases/compiler/excessPropertyCheckWithUnions.ts (10 errors) ====
==== tests/cases/compiler/excessPropertyCheckWithUnions.ts (11 errors) ====
type ADT = {
tag: "A",
a1: string
@ -137,4 +139,20 @@ tests/cases/compiler/excessPropertyCheckWithUnions.ts(66,9): error TS2326: Types
c: "c", // ok -- kind: "A", an: { a: string } | { c: string }
}
}
// Excess property checks must match all discriminable properties
type Button = { tag: 'button'; type?: 'submit'; };
type Anchor = { tag: 'a'; type?: string; href: string };
type Union = Button | Anchor;
const obj: Union = {
tag: 'button',
type: 'submit',
// should have error here
href: 'foo',
~~~~~~~~~~~
!!! error TS2322: Type '{ tag: "button"; type: "submit"; href: string; }' is not assignable to type 'Union'.
!!! error TS2322: Object literal may only specify known properties, and 'href' does not exist in type 'Button'.
};

View File

@ -74,6 +74,19 @@ const abac: AB = {
c: "c", // ok -- kind: "A", an: { a: string } | { c: string }
}
}
// Excess property checks must match all discriminable properties
type Button = { tag: 'button'; type?: 'submit'; };
type Anchor = { tag: 'a'; type?: string; href: string };
type Union = Button | Anchor;
const obj: Union = {
tag: 'button',
type: 'submit',
// should have error here
href: 'foo',
};
//// [excessPropertyCheckWithUnions.js]
@ -125,3 +138,9 @@ var abac = {
c: "c"
}
};
var obj = {
tag: 'button',
type: 'submit',
// should have error here
href: 'foo'
};

View File

@ -221,3 +221,36 @@ const abac: AB = {
}
}
// Excess property checks must match all discriminable properties
type Button = { tag: 'button'; type?: 'submit'; };
>Button : Symbol(Button, Decl(excessPropertyCheckWithUnions.ts, 74, 1))
>tag : Symbol(tag, Decl(excessPropertyCheckWithUnions.ts, 77, 15))
>type : Symbol(type, Decl(excessPropertyCheckWithUnions.ts, 77, 30))
type Anchor = { tag: 'a'; type?: string; href: string };
>Anchor : Symbol(Anchor, Decl(excessPropertyCheckWithUnions.ts, 77, 50))
>tag : Symbol(tag, Decl(excessPropertyCheckWithUnions.ts, 78, 15))
>type : Symbol(type, Decl(excessPropertyCheckWithUnions.ts, 78, 25))
>href : Symbol(href, Decl(excessPropertyCheckWithUnions.ts, 78, 40))
type Union = Button | Anchor;
>Union : Symbol(Union, Decl(excessPropertyCheckWithUnions.ts, 78, 56))
>Button : Symbol(Button, Decl(excessPropertyCheckWithUnions.ts, 74, 1))
>Anchor : Symbol(Anchor, Decl(excessPropertyCheckWithUnions.ts, 77, 50))
const obj: Union = {
>obj : Symbol(obj, Decl(excessPropertyCheckWithUnions.ts, 81, 5))
>Union : Symbol(Union, Decl(excessPropertyCheckWithUnions.ts, 78, 56))
tag: 'button',
>tag : Symbol(tag, Decl(excessPropertyCheckWithUnions.ts, 81, 20))
type: 'submit',
>type : Symbol(type, Decl(excessPropertyCheckWithUnions.ts, 82, 18))
// should have error here
href: 'foo',
>href : Symbol(href, Decl(excessPropertyCheckWithUnions.ts, 83, 19))
};

View File

@ -278,3 +278,37 @@ const abac: AB = {
}
}
// Excess property checks must match all discriminable properties
type Button = { tag: 'button'; type?: 'submit'; };
>Button : Button
>tag : "button"
>type : "submit" | undefined
type Anchor = { tag: 'a'; type?: string; href: string };
>Anchor : Anchor
>tag : "a"
>type : string | undefined
>href : string
type Union = Button | Anchor;
>Union : Union
const obj: Union = {
>obj : Union
>{ tag: 'button', type: 'submit', // should have error here href: 'foo',} : { tag: "button"; type: "submit"; href: string; }
tag: 'button',
>tag : "button"
>'button' : "button"
type: 'submit',
>type : "submit"
>'submit' : "submit"
// should have error here
href: 'foo',
>href : string
>'foo' : "foo"
};

View File

@ -0,0 +1,59 @@
// Repro from #32657
interface Base<T> {
value: T;
}
interface Int extends Base<number> {
type: "integer";
multipleOf?: number;
}
interface Float extends Base<number> {
type: "number";
}
interface Str extends Base<string> {
type: "string";
format?: string;
}
interface Bool extends Base<boolean> {
type: "boolean";
}
type Primitive = Int | Float | Str | Bool;
const foo: Primitive = {
type: "number",
value: 10,
multipleOf: 5, // excess property
format: "what?"
}
type DisjointDiscriminants = { p1: 'left'; p2: true; p3: number } | { p1: 'right'; p2: false; p4: string } | { p1: 'left'; p2: boolean };
// This has excess error because variant three is the only applicable case.
const a: DisjointDiscriminants = {
p1: 'left',
p2: false,
p3: 42,
p4: "hello"
};
// This has no excess error because variant one and three are both applicable.
const b: DisjointDiscriminants = {
p1: 'left',
p2: true,
p3: 42,
p4: "hello"
};
// This has excess error because variant two is the only applicable case
const c: DisjointDiscriminants = {
p1: 'right',
p2: false,
p3: 42,
p4: "hello"
};

View File

@ -74,3 +74,16 @@ const abac: AB = {
c: "c", // ok -- kind: "A", an: { a: string } | { c: string }
}
}
// Excess property checks must match all discriminable properties
type Button = { tag: 'button'; type?: 'submit'; };
type Anchor = { tag: 'a'; type?: string; href: string };
type Union = Button | Anchor;
const obj: Union = {
tag: 'button',
type: 'submit',
// should have error here
href: 'foo',
};