mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 12:51:30 -05:00
Contextually typed binding element initializers (#35855)
* Binding element initializers contextually typed by parent initializers * Accept new baselines * Literal type widening should be last step in inference * Accept new baselines * Add tests
This commit is contained in:
@@ -6981,7 +6981,7 @@ namespace ts {
|
||||
getTypeWithFacts(type, TypeFacts.NEUndefined) :
|
||||
type;
|
||||
}
|
||||
return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype);
|
||||
return widenTypeInferredFromInitializer(declaration, getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype));
|
||||
}
|
||||
|
||||
function getTypeForDeclarationFromJSDocComment(declaration: Node) {
|
||||
@@ -7098,7 +7098,7 @@ namespace ts {
|
||||
// Use the type of the initializer expression if one is present and the declaration is
|
||||
// not a parameter of a contextually typed function
|
||||
if (declaration.initializer && !isParameterOfContextuallyTypedFunction(declaration)) {
|
||||
const type = checkDeclarationInitializer(declaration);
|
||||
const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration));
|
||||
return addOptionality(type, isOptional);
|
||||
}
|
||||
|
||||
@@ -7330,7 +7330,11 @@ namespace ts {
|
||||
// pattern. Otherwise, it is the type any.
|
||||
function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type {
|
||||
if (element.initializer) {
|
||||
return addOptionality(checkDeclarationInitializer(element));
|
||||
// The type implied by a binding pattern is independent of context, so we check the initializer with no
|
||||
// contextual type or, if the element itself is a binding pattern, with the type implied by that binding
|
||||
// pattern.
|
||||
const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType;
|
||||
return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, contextualType)));
|
||||
}
|
||||
if (isBindingPattern(element.name)) {
|
||||
return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors);
|
||||
@@ -21195,9 +21199,10 @@ namespace ts {
|
||||
}
|
||||
|
||||
function getContextualTypeForBindingElement(declaration: BindingElement): Type | undefined {
|
||||
const parentDeclaration = declaration.parent.parent;
|
||||
const parent = declaration.parent.parent;
|
||||
const name = declaration.propertyName || declaration.name;
|
||||
const parentType = getContextualTypeForVariableLikeDeclaration(parentDeclaration);
|
||||
const parentType = getContextualTypeForVariableLikeDeclaration(parent) ||
|
||||
parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent);
|
||||
if (parentType && !isBindingPattern(name) && !isComputedNonLiteralName(name)) {
|
||||
const nameType = getLiteralTypeFromPropertyName(name);
|
||||
if (isTypeUsableAsPropertyName(nameType)) {
|
||||
@@ -27661,27 +27666,13 @@ namespace ts {
|
||||
return node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression;
|
||||
}
|
||||
|
||||
function checkDeclarationInitializer(declaration: HasExpressionInitializer) {
|
||||
function checkDeclarationInitializer(declaration: HasExpressionInitializer, contextualType?: Type | undefined) {
|
||||
const initializer = getEffectiveInitializer(declaration)!;
|
||||
const type = getQuickTypeOfExpression(initializer) || checkExpressionCached(initializer);
|
||||
const padded = isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
|
||||
const type = getQuickTypeOfExpression(initializer) ||
|
||||
(contextualType ? checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, CheckMode.Normal) : checkExpressionCached(initializer));
|
||||
return isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
|
||||
isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ?
|
||||
padTupleType(type, declaration.name) : type;
|
||||
const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const ||
|
||||
isDeclarationReadonly(declaration) ||
|
||||
isTypeAssertion(initializer) ||
|
||||
isLiteralOfContextualType(padded, getContextualType(initializer)) ? padded : getWidenedLiteralType(padded);
|
||||
if (isInJSFile(declaration)) {
|
||||
if (widened.flags & TypeFlags.Nullable) {
|
||||
reportImplicitAny(declaration, anyType);
|
||||
return anyType;
|
||||
}
|
||||
else if (isEmptyArrayLiteralType(widened)) {
|
||||
reportImplicitAny(declaration, anyArrayType);
|
||||
return anyArrayType;
|
||||
}
|
||||
}
|
||||
return widened;
|
||||
}
|
||||
|
||||
function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) {
|
||||
@@ -27700,6 +27691,21 @@ namespace ts {
|
||||
return createTupleType(elementTypes, type.target.minLength, /*hasRestElement*/ false, type.target.readonly);
|
||||
}
|
||||
|
||||
function widenTypeInferredFromInitializer(declaration: HasExpressionInitializer, type: Type) {
|
||||
const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const || isDeclarationReadonly(declaration) ? type : getWidenedLiteralType(type);
|
||||
if (isInJSFile(declaration)) {
|
||||
if (widened.flags & TypeFlags.Nullable) {
|
||||
reportImplicitAny(declaration, anyType);
|
||||
return anyType;
|
||||
}
|
||||
else if (isEmptyArrayLiteralType(widened)) {
|
||||
reportImplicitAny(declaration, anyArrayType);
|
||||
return anyArrayType;
|
||||
}
|
||||
}
|
||||
return widened;
|
||||
}
|
||||
|
||||
function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean {
|
||||
if (contextualType) {
|
||||
if (contextualType.flags & TypeFlags.UnionOrIntersection) {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
tests/cases/compiler/crashInGetTextOfComputedPropertyName.ts(23,24): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
|
||||
|
||||
|
||||
==== tests/cases/compiler/crashInGetTextOfComputedPropertyName.ts (1 errors) ====
|
||||
// https://github.com/Microsoft/TypeScript/issues/29006
|
||||
export interface A { type: 'a' }
|
||||
export interface B { type: 'b' }
|
||||
export type AB = A | B
|
||||
|
||||
const itemId = 'some-id'
|
||||
|
||||
// --- test on first level ---
|
||||
const items: { [id: string]: AB } = {}
|
||||
const { [itemId]: itemOk1 } = items
|
||||
typeof itemOk1 // pass
|
||||
|
||||
// --- test on second level ---
|
||||
interface ObjWithItems {
|
||||
items: {[s: string]: AB}
|
||||
}
|
||||
const objWithItems: ObjWithItems = { items: {}}
|
||||
|
||||
const itemOk2 = objWithItems.items[itemId]
|
||||
typeof itemOk2 // pass
|
||||
|
||||
const {
|
||||
items: { [itemId]: itemWithTSError } = {} /*happens when default value is provided*/
|
||||
~~~~~~~~~~~~~~~
|
||||
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
|
||||
} = objWithItems
|
||||
|
||||
// in order to re-produce the error, uncomment next line:
|
||||
typeof itemWithTSError // :(
|
||||
|
||||
// will result in:
|
||||
// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined
|
||||
@@ -56,8 +56,8 @@ const {
|
||||
items: { [itemId]: itemWithTSError } = {} /*happens when default value is provided*/
|
||||
>items : any
|
||||
>itemId : "some-id"
|
||||
>itemWithTSError : any
|
||||
>{} : { "some-id": any; }
|
||||
>itemWithTSError : AB
|
||||
>{} : {}
|
||||
|
||||
} = objWithItems
|
||||
>objWithItems : ObjWithItems
|
||||
@@ -65,7 +65,7 @@ const {
|
||||
// in order to re-produce the error, uncomment next line:
|
||||
typeof itemWithTSError // :(
|
||||
>typeof itemWithTSError : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>itemWithTSError : any
|
||||
>itemWithTSError : AB
|
||||
|
||||
// will result in:
|
||||
// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined
|
||||
|
||||
37
tests/baselines/reference/literalTypesAndDestructuring.js
Normal file
37
tests/baselines/reference/literalTypesAndDestructuring.js
Normal file
@@ -0,0 +1,37 @@
|
||||
//// [literalTypesAndDestructuring.ts]
|
||||
declare let x: { a: 0 | 1 | undefined };
|
||||
|
||||
let { a: a1 } = x;
|
||||
let { a: a2 = 0 } = x;
|
||||
let { a: a3 = 2 } = x;
|
||||
let { a: a4 = 2 as const } = x;
|
||||
|
||||
let b1 = x.a;
|
||||
let b2 = x.a ?? 0;
|
||||
let b3 = x.a ?? 2;
|
||||
let b4 = x.a ?? 2 as const;
|
||||
|
||||
// Repro from #35693
|
||||
|
||||
interface Foo {
|
||||
bar: 'yo' | 'ha' | undefined;
|
||||
}
|
||||
|
||||
let { bar = 'yo' } = {} as Foo;
|
||||
|
||||
bar; // "yo" | "ha"
|
||||
|
||||
|
||||
//// [literalTypesAndDestructuring.js]
|
||||
"use strict";
|
||||
var _a, _b, _c;
|
||||
var a1 = x.a;
|
||||
var _d = x.a, a2 = _d === void 0 ? 0 : _d;
|
||||
var _e = x.a, a3 = _e === void 0 ? 2 : _e;
|
||||
var _f = x.a, a4 = _f === void 0 ? 2 : _f;
|
||||
var b1 = x.a;
|
||||
var b2 = (_a = x.a) !== null && _a !== void 0 ? _a : 0;
|
||||
var b3 = (_b = x.a) !== null && _b !== void 0 ? _b : 2;
|
||||
var b4 = (_c = x.a) !== null && _c !== void 0 ? _c : 2;
|
||||
var _g = {}.bar, bar = _g === void 0 ? 'yo' : _g;
|
||||
bar; // "yo" | "ha"
|
||||
@@ -0,0 +1,65 @@
|
||||
=== tests/cases/conformance/types/literal/literalTypesAndDestructuring.ts ===
|
||||
declare let x: { a: 0 | 1 | undefined };
|
||||
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
|
||||
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
|
||||
let { a: a1 } = x;
|
||||
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
>a1 : Symbol(a1, Decl(literalTypesAndDestructuring.ts, 2, 5))
|
||||
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
|
||||
|
||||
let { a: a2 = 0 } = x;
|
||||
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
>a2 : Symbol(a2, Decl(literalTypesAndDestructuring.ts, 3, 5))
|
||||
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
|
||||
|
||||
let { a: a3 = 2 } = x;
|
||||
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
>a3 : Symbol(a3, Decl(literalTypesAndDestructuring.ts, 4, 5))
|
||||
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
|
||||
|
||||
let { a: a4 = 2 as const } = x;
|
||||
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
>a4 : Symbol(a4, Decl(literalTypesAndDestructuring.ts, 5, 5))
|
||||
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
|
||||
|
||||
let b1 = x.a;
|
||||
>b1 : Symbol(b1, Decl(literalTypesAndDestructuring.ts, 7, 3))
|
||||
>x.a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
|
||||
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
|
||||
let b2 = x.a ?? 0;
|
||||
>b2 : Symbol(b2, Decl(literalTypesAndDestructuring.ts, 8, 3))
|
||||
>x.a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
|
||||
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
|
||||
let b3 = x.a ?? 2;
|
||||
>b3 : Symbol(b3, Decl(literalTypesAndDestructuring.ts, 9, 3))
|
||||
>x.a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
|
||||
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
|
||||
let b4 = x.a ?? 2 as const;
|
||||
>b4 : Symbol(b4, Decl(literalTypesAndDestructuring.ts, 10, 3))
|
||||
>x.a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
|
||||
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
|
||||
|
||||
// Repro from #35693
|
||||
|
||||
interface Foo {
|
||||
>Foo : Symbol(Foo, Decl(literalTypesAndDestructuring.ts, 10, 27))
|
||||
|
||||
bar: 'yo' | 'ha' | undefined;
|
||||
>bar : Symbol(Foo.bar, Decl(literalTypesAndDestructuring.ts, 14, 15))
|
||||
}
|
||||
|
||||
let { bar = 'yo' } = {} as Foo;
|
||||
>bar : Symbol(bar, Decl(literalTypesAndDestructuring.ts, 18, 5))
|
||||
>Foo : Symbol(Foo, Decl(literalTypesAndDestructuring.ts, 10, 27))
|
||||
|
||||
bar; // "yo" | "ha"
|
||||
>bar : Symbol(bar, Decl(literalTypesAndDestructuring.ts, 18, 5))
|
||||
|
||||
76
tests/baselines/reference/literalTypesAndDestructuring.types
Normal file
76
tests/baselines/reference/literalTypesAndDestructuring.types
Normal file
@@ -0,0 +1,76 @@
|
||||
=== tests/cases/conformance/types/literal/literalTypesAndDestructuring.ts ===
|
||||
declare let x: { a: 0 | 1 | undefined };
|
||||
>x : { a: 0 | 1 | undefined; }
|
||||
>a : 0 | 1 | undefined
|
||||
|
||||
let { a: a1 } = x;
|
||||
>a : any
|
||||
>a1 : 0 | 1 | undefined
|
||||
>x : { a: 0 | 1 | undefined; }
|
||||
|
||||
let { a: a2 = 0 } = x;
|
||||
>a : any
|
||||
>a2 : 0 | 1
|
||||
>0 : 0
|
||||
>x : { a: 0 | 1 | undefined; }
|
||||
|
||||
let { a: a3 = 2 } = x;
|
||||
>a : any
|
||||
>a3 : number
|
||||
>2 : 2
|
||||
>x : { a: 0 | 1 | undefined; }
|
||||
|
||||
let { a: a4 = 2 as const } = x;
|
||||
>a : any
|
||||
>a4 : 0 | 1 | 2
|
||||
>2 as const : 2
|
||||
>2 : 2
|
||||
>x : { a: 0 | 1 | undefined; }
|
||||
|
||||
let b1 = x.a;
|
||||
>b1 : 0 | 1 | undefined
|
||||
>x.a : 0 | 1 | undefined
|
||||
>x : { a: 0 | 1 | undefined; }
|
||||
>a : 0 | 1 | undefined
|
||||
|
||||
let b2 = x.a ?? 0;
|
||||
>b2 : 0 | 1
|
||||
>x.a ?? 0 : 0 | 1
|
||||
>x.a : 0 | 1 | undefined
|
||||
>x : { a: 0 | 1 | undefined; }
|
||||
>a : 0 | 1 | undefined
|
||||
>0 : 0
|
||||
|
||||
let b3 = x.a ?? 2;
|
||||
>b3 : number
|
||||
>x.a ?? 2 : 0 | 1 | 2
|
||||
>x.a : 0 | 1 | undefined
|
||||
>x : { a: 0 | 1 | undefined; }
|
||||
>a : 0 | 1 | undefined
|
||||
>2 : 2
|
||||
|
||||
let b4 = x.a ?? 2 as const;
|
||||
>b4 : 0 | 1 | 2
|
||||
>x.a ?? 2 as const : 0 | 1 | 2
|
||||
>x.a : 0 | 1 | undefined
|
||||
>x : { a: 0 | 1 | undefined; }
|
||||
>a : 0 | 1 | undefined
|
||||
>2 as const : 2
|
||||
>2 : 2
|
||||
|
||||
// Repro from #35693
|
||||
|
||||
interface Foo {
|
||||
bar: 'yo' | 'ha' | undefined;
|
||||
>bar : "yo" | "ha" | undefined
|
||||
}
|
||||
|
||||
let { bar = 'yo' } = {} as Foo;
|
||||
>bar : "yo" | "ha"
|
||||
>'yo' : "yo"
|
||||
>{} as Foo : Foo
|
||||
>{} : {}
|
||||
|
||||
bar; // "yo" | "ha"
|
||||
>bar : "yo" | "ha"
|
||||
|
||||
@@ -36,7 +36,7 @@ let { a = "foo" } = { a: "foo" };
|
||||
>"foo" : "foo"
|
||||
|
||||
let { b = "foo" as "foo" } = { b: "bar" };
|
||||
>b : "foo" | "bar"
|
||||
>b : string
|
||||
>"foo" as "foo" : "foo"
|
||||
>"foo" : "foo"
|
||||
>{ b: "bar" } : { b?: "bar"; }
|
||||
|
||||
@@ -144,7 +144,7 @@ for (let {
|
||||
>"secondary" : "secondary"
|
||||
|
||||
} = { primary: "none", secondary: "none" }
|
||||
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
|
||||
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
|
||||
>primary : string
|
||||
>"none" : "none"
|
||||
>secondary : string
|
||||
@@ -182,7 +182,7 @@ for (let {
|
||||
>"secondary" : "secondary"
|
||||
|
||||
} = { primary: "none", secondary: "none" }
|
||||
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
|
||||
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
|
||||
>primary : string
|
||||
>"none" : "none"
|
||||
>secondary : string
|
||||
@@ -221,7 +221,7 @@ for (let {
|
||||
>"secondary" : "secondary"
|
||||
|
||||
} = { primary: "none", secondary: "none" }
|
||||
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
|
||||
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
|
||||
>primary : string
|
||||
>"none" : "none"
|
||||
>secondary : string
|
||||
@@ -351,7 +351,7 @@ for (let {
|
||||
>"secondary" : "secondary"
|
||||
|
||||
} = { primary: "none", secondary: "none" }
|
||||
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
|
||||
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
|
||||
>primary : string
|
||||
>"none" : "none"
|
||||
>secondary : string
|
||||
@@ -394,7 +394,7 @@ for (let {
|
||||
>"secondary" : "secondary"
|
||||
|
||||
} = { primary: "none", secondary: "none" }
|
||||
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
|
||||
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
|
||||
>primary : string
|
||||
>"none" : "none"
|
||||
>secondary : string
|
||||
@@ -438,7 +438,7 @@ for (let {
|
||||
>"secondary" : "secondary"
|
||||
|
||||
} = { primary: "none", secondary: "none" }
|
||||
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
|
||||
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
|
||||
>primary : string
|
||||
>"none" : "none"
|
||||
>secondary : string
|
||||
|
||||
@@ -60,7 +60,7 @@ var {
|
||||
>"noSkill" : "noSkill"
|
||||
|
||||
} = { primary: "noSkill", secondary: "noSkill" }
|
||||
>{ primary: "noSkill", secondary: "noSkill" } : { primary?: string; secondary?: string; }
|
||||
>{ primary: "noSkill", secondary: "noSkill" } : { primary: string; secondary: string; }
|
||||
>primary : string
|
||||
>"noSkill" : "noSkill"
|
||||
>secondary : string
|
||||
@@ -89,7 +89,7 @@ var {
|
||||
>"noSkill" : "noSkill"
|
||||
|
||||
} = { primary: "noSkill", secondary: "noSkill" }
|
||||
>{ primary: "noSkill", secondary: "noSkill" } : { primary?: string; secondary?: string; }
|
||||
>{ primary: "noSkill", secondary: "noSkill" } : { primary: string; secondary: string; }
|
||||
>primary : string
|
||||
>"noSkill" : "noSkill"
|
||||
>secondary : string
|
||||
@@ -118,7 +118,7 @@ var {
|
||||
>"noSkill" : "noSkill"
|
||||
|
||||
} = { primary: "noSkill", secondary: "noSkill" }
|
||||
>{ primary: "noSkill", secondary: "noSkill" } : { primary?: string; secondary?: string; }
|
||||
>{ primary: "noSkill", secondary: "noSkill" } : { primary: string; secondary: string; }
|
||||
>primary : string
|
||||
>"noSkill" : "noSkill"
|
||||
>secondary : string
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// @strict: true
|
||||
|
||||
declare let x: { a: 0 | 1 | undefined };
|
||||
|
||||
let { a: a1 } = x;
|
||||
let { a: a2 = 0 } = x;
|
||||
let { a: a3 = 2 } = x;
|
||||
let { a: a4 = 2 as const } = x;
|
||||
|
||||
let b1 = x.a;
|
||||
let b2 = x.a ?? 0;
|
||||
let b3 = x.a ?? 2;
|
||||
let b4 = x.a ?? 2 as const;
|
||||
|
||||
// Repro from #35693
|
||||
|
||||
interface Foo {
|
||||
bar: 'yo' | 'ha' | undefined;
|
||||
}
|
||||
|
||||
let { bar = 'yo' } = {} as Foo;
|
||||
|
||||
bar; // "yo" | "ha"
|
||||
Reference in New Issue
Block a user