Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions tsc/internal/checker/emitresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,12 +589,30 @@ func (r *EmitResolver) requiresAddingImplicitUndefined(declaration *ast.Node, sy
}
switch declaration.Kind {
case ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindJSDocPropertyTag:
if !isOptionalDeclaration(declaration) {
return false
}
if symbol == nil {
symbol = r.checker.getSymbolOfDeclaration(declaration)
}
isReverseMapped := r.checker.ReverseMappedSymbolLinks.Has(symbol) && r.checker.ReverseMappedSymbolLinks.Get(symbol).mappedType != nil
isMapped := symbol.CheckFlags&ast.CheckFlagsMapped != 0
if symbol.Flags&ast.SymbolFlagsProperty == 0 || !isReverseMapped && !isMapped {
return false
}
if isMapped && symbol.Flags&ast.SymbolFlagsOptional != 0 {
mappedType := r.checker.valueSymbolLinks.Get(symbol).containingType
mappedType = core.OrElse(mappedType.AsMappedType().target, mappedType)
if getMappedTypeModifiers(mappedType)&MappedTypeModifiersIncludeOptional != 0 || !mappedTypeTemplateContainsNonMissingUndefined(r.checker, mappedType) {
return false
}
}
t := r.checker.getTypeOfSymbol(symbol)
r.checker.mappedSymbolLinks.Has(symbol)
return (symbol.Flags&ast.SymbolFlagsProperty != 0) && (symbol.Flags&ast.SymbolFlagsOptional != 0) && isOptionalDeclaration(declaration) && r.checker.ReverseMappedSymbolLinks.Has(symbol) && r.checker.ReverseMappedSymbolLinks.Get(symbol).mappedType != nil && containsNonMissingUndefinedType(r.checker, t)
if !containsNonMissingUndefinedType(r.checker, t) {
return false
}
declaredType := declaration.Type()
return declaredType == nil || !r.checker.containsUndefinedType(r.checker.getTypeFromTypeNode(declaredType))
case ast.KindParameter, ast.KindJSDocParameterTag:
return r.requiresAddingImplicitUndefinedWorker(declaration, enclosingDeclaration)
default:
Expand Down
33 changes: 33 additions & 0 deletions tsc/internal/checker/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -1599,6 +1599,39 @@ func containsNonMissingUndefinedType(c *Checker, t *Type) bool {
return candidate.flags&TypeFlagsUndefined != 0 && candidate != c.missingType
}

func mappedTypeTemplateContainsNonMissingUndefined(c *Checker, mappedType *Type) bool {
seen := make(map[*Type]struct{})
var visit func(t *Type) bool
visit = func(t *Type) bool {
if t == nil {
return false
}
if containsNonMissingUndefinedType(c, t) {
return true
}
if _, ok := seen[t]; ok {
return false
}
seen[t] = struct{}{}
switch {
case t.flags&TypeFlagsConditional != 0:
return visit(c.getTrueTypeFromConditionalType(t)) || visit(c.getFalseTypeFromConditionalType(t))
case t.flags&TypeFlagsIndexedAccess != 0:
objectType := t.AsIndexedAccessType().ObjectType()
if objectType.flags&TypeFlagsObject != 0 && objectType.ObjectFlags()&ObjectFlagsMapped != 0 {
objectType = core.OrElse(objectType.AsMappedType().target, objectType)
return visit(c.getTemplateTypeFromMappedType(objectType))
}
case t.flags&TypeFlagsObject != 0 && t.ObjectFlags()&ObjectFlagsMapped != 0:
t = core.OrElse(t.AsMappedType().target, t)
return visit(c.getTemplateTypeFromMappedType(t))
}
return false
}
mappedType = core.OrElse(mappedType.AsMappedType().target, mappedType)
return visit(c.getTemplateTypeFromMappedType(mappedType))
}

func getAnyImportSyntax(node *ast.Node) *ast.Node {
var importNode *ast.Node
switch node.Kind {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/TypeScript/tsc/internal/fourslash"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)

func TestQuickInfoMappedPropertyUnionUndefined1(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @strict: true
// @exactOptionalPropertyTypes: true
// https://github.com/microsoft/TypeScript/issues/59948
type OptionalToUnionWithUndefined<T> = {
[K in keyof T]: T extends Record<K, T[K]> ? T[K] : T[K] | undefined;
};
type Intermediate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
type Literal/*2*/ = { a?: string | undefined };
type Res1/*3*/ = Required<Intermediate>;
type Res2/*4*/ = Required<Literal>;`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "1", "type Intermediate = {\n a?: string | undefined;\n}", "")
f.VerifyQuickInfoAt(t, "2", "type Literal = {\n a?: string | undefined;\n}", "")
f.VerifyQuickInfoAt(t, "3", "type Res1 = {\n a: string | undefined;\n}", "")
f.VerifyQuickInfoAt(t, "4", "type Res2 = {\n a: string | undefined;\n}", "")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/TypeScript/tsc/internal/fourslash"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)

func TestQuickInfoMappedPropertyUnionUndefined2(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @strict: true
type OptionalToUnionWithUndefined<T> = {
[K in keyof T]: T extends Record<K, T[K]> ? T[K] : T[K] | undefined;
};
type Intermediate/*1*/ = OptionalToUnionWithUndefined<{ a?: string }>;
type Literal/*2*/ = { a?: string | undefined };
type Res1/*3*/ = Required<Intermediate>;
type Res2/*4*/ = Required<Literal>;`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "1", "type Intermediate = {\n a?: string | undefined;\n}", "")
f.VerifyQuickInfoAt(t, "2", "type Literal = {\n a?: string | undefined;\n}", "")
f.VerifyQuickInfoAt(t, "3", "type Res1 = {\n a: string;\n}", "")
f.VerifyQuickInfoAt(t, "4", "type Res2 = {\n a: string;\n}", "")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/TypeScript/tsc/internal/fourslash"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)

func TestQuickInfoMappedPropertyUnionUndefined3(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// https://github.com/microsoft/TypeScript/issues/60411
// @strict: true
type UnsetUndefinedToOblivion<T> = { [P in keyof T]-?: T[P] | undefined };
type SetUndefined<T> = { [P in keyof T]: T[P] | undefined };
type TheWhat/**/ = SetUndefined<UnsetUndefinedToOblivion<{ a?: 1 }>>;`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "", "type TheWhat = {\n a: 1 | undefined;\n}", "")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/TypeScript/tsc/internal/fourslash"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)

func TestQuickInfoMappedPropertyUnionUndefined4(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @strict: true
type A/*1*/ = { [K in keyof { a?: string }]-?: string };
type B/*2*/ = { [K in keyof A]: string | undefined };`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "1", "type A = {\n a: string;\n}", "")
f.VerifyQuickInfoAt(t, "2", "type B = {\n a: string | undefined;\n}", "")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/TypeScript/tsc/internal/fourslash"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)

func TestQuickInfoMappedPropertyUnionUndefined5(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @strict: true
// https://github.com/microsoft/TypeScript/issues/62325
type RequiredKeys<T extends object> = {
[K in keyof Required<T>]: T[K];
};
type Foo = {
a?: string;
b?: number;
c: string;
d: boolean | undefined;
};
type Bar/*1*/ = RequiredKeys<Foo>;`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "1", "type Bar = {\n a: string | undefined;\n b: number | undefined;\n c: string;\n d: boolean | undefined;\n}", "")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/TypeScript/tsc/internal/fourslash"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)

func TestQuickInfoMappedTypeOptionalPropertyExplicitUndefined(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @strict: true
type X = { x?: number | undefined };
type /*Y*/Y = { [K in keyof X]: X[K] };`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "Y", "type Y = {\n x?: number | undefined;\n}", "")
}

func TestQuickInfoMappedTypeOptionalPropertyExplicitUndefinedExactOptionalPropertyTypes(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @strict: true
// @exactOptionalPropertyTypes: true
type X = { x?: number | undefined };
type /*Y*/Y = { [K in keyof X]: X[K] };`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "Y", "type Y = {\n x?: number | undefined;\n}", "")
}

func TestQuickInfoMappedTypeRequiredPropertyExplicitUndefinedExactOptionalPropertyTypes(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @strict: true
// @exactOptionalPropertyTypes: true
type X = { x?: number | undefined };
type /*Y*/Y = { [K in keyof X]-?: X[K] };`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "Y", "type Y = {\n x: number | undefined;\n}", "")
}

func TestQuickInfoMappedTypeOptionalInferredPropertyExplicitUndefined(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @strict: true
class C { x? = 1 as number }
type M<T> = { [K in keyof T]: T[K] | undefined };
type /*Y*/Y = M<C>;`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "Y", "type Y = {\n x?: number | undefined;\n}", "")
}

func TestQuickInfoMappedTypeOptionalInferredPropertyExplicitUndefinedExactOptionalPropertyTypes(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @strict: true
// @exactOptionalPropertyTypes: true
class C { x? = 1 as number }
type M<T> = { [K in keyof T]: T[K] | undefined };
type /*Y*/Y = M<C>;`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "Y", "type Y = {\n x?: number | undefined;\n}", "")
}

func TestQuickInfoMappedTypeOptionalInferredPropertyAliasedUndefined(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @strict: true
type Maybe<T> = T | undefined;
class C { x? = 1 as number }
type M<T> = { [K in keyof T]: Maybe<T[K]> };
type /*Y*/Y = M<C>;`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "Y", "type Y = {\n x?: number | undefined;\n}", "")
}

func TestQuickInfoMappedTypeOptionalInferredPropertyNestedMappedType(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @strict: true
class C { x? = 1 as number }
type Inner<T> = { [K in keyof T]: T[K] | undefined };
type Outer<T> = { [K in keyof Inner<T>]: Inner<T>[K] };
type /*Y*/Y = Outer<C>;`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyQuickInfoAt(t, "Y", "type Y = {\n x?: number | undefined;\n}", "")
}