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
88 changes: 82 additions & 6 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -21787,10 +21787,23 @@ func (c *Checker) getUnionOrIntersectionProperty(t *Type, name string, skipObjec
return prop
}

func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name string, skipObjectFunctionPropertyAugment bool) *ast.Symbol {
// unionOrIntersectionPropertyParts is what the constituents of a union or
// intersection contribute to its property of a given name.
type unionOrIntersectionPropertyParts struct {
singleProp *ast.Symbol
propSet collections.OrderedSet[*ast.Symbol] // distinct constituent properties, if more than one
indexTypes []*Type
propFlags ast.SymbolFlags
checkFlags ast.CheckFlags
optionalFlag ast.SymbolFlags
syntheticFlag ast.CheckFlags
mergedInstantiations bool
}

func (c *Checker) collectUnionOrIntersectionPropertyParts(containingType *Type, name string, skipObjectFunctionPropertyAugment bool, parts *unionOrIntersectionPropertyParts) {
propFlags := ast.SymbolFlagsNone
var singleProp *ast.Symbol
var propSet collections.OrderedSet[*ast.Symbol]
propSet := &parts.propSet
var indexTypes []*Type
isUnion := containingType.flags&TypeFlagsUnion != 0
// Flags we want to propagate to the result if they exist in all source symbols
Expand Down Expand Up @@ -21895,14 +21908,24 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
}
}
}
parts.singleProp, parts.indexTypes, parts.mergedInstantiations = singleProp, indexTypes, mergedInstantiations
parts.propFlags, parts.checkFlags, parts.optionalFlag, parts.syntheticFlag = propFlags, checkFlags, optionalFlag, syntheticFlag
}

func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name string, skipObjectFunctionPropertyAugment bool) *ast.Symbol {
var parts unionOrIntersectionPropertyParts
c.collectUnionOrIntersectionPropertyParts(containingType, name, skipObjectFunctionPropertyAugment, &parts)
singleProp, propSet, indexTypes := parts.singleProp, &parts.propSet, parts.indexTypes
propFlags, checkFlags, optionalFlag, syntheticFlag := parts.propFlags, parts.checkFlags, parts.optionalFlag, parts.syntheticFlag
isUnion := containingType.flags&TypeFlagsUnion != 0
if singleProp == nil {
// No property was found
return nil
}
if isUnion &&
(propSet.Size() != 0 || checkFlags&ast.CheckFlagsPartial != 0) &&
checkFlags&(ast.CheckFlagsContainsPrivate|ast.CheckFlagsContainsProtected|ast.CheckFlagsContainsWritePrivate|ast.CheckFlagsContainsWriteProtected) != 0 &&
!(propSet.Size() != 0 && c.hasCommonDeclaration(&propSet)) {
!(propSet.Size() != 0 && c.hasCommonDeclaration(propSet)) {
// A property in a union has a private or protected declaration in one constituent, but is missing
// or has a different declaration in another constituent. If the private or protected declaration is
// for reading, we don't create a property.
Expand All @@ -21918,7 +21941,7 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
}
}
if propSet.Size() == 0 && checkFlags&ast.CheckFlagsReadPartial == 0 && len(indexTypes) == 0 {
if !mergedInstantiations {
if !parts.mergedInstantiations {
return singleProp
}
// No symbol from a union/intersection should have a `.parent` set (since unions/intersections don't act as symbol parents)
Expand Down Expand Up @@ -21975,7 +21998,7 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
if t != firstType {
checkFlags |= ast.CheckFlagsHasNonUniformType
}
if isLiteralType(t) || c.isPatternLiteralType(t) {
if c.isLiteralOrPatternLiteralType(t) {
checkFlags |= ast.CheckFlagsHasLiteralType
}
if t.flags&TypeFlagsNever != 0 && t != c.uniqueLiteralType {
Expand Down Expand Up @@ -22189,7 +22212,7 @@ func (c *Checker) getReducedType(t *Type) *Type {
case t.flags&TypeFlagsIntersection != 0:
if t.objectFlags&ObjectFlagsIsNeverIntersectionComputed == 0 {
t.objectFlags |= ObjectFlagsIsNeverIntersectionComputed
if core.Some(c.getPropertiesOfUnionOrIntersectionType(t), c.isNeverReducedProperty) {
if c.isNeverReducedIntersection(t) {
t.objectFlags |= ObjectFlagsIsNeverIntersection
}
}
Expand All @@ -22216,6 +22239,59 @@ func (c *Checker) isNeverReducedProperty(prop *ast.Symbol) bool {
return c.isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop)
}

// isNeverReducedIntersection returns
// core.Some(c.getPropertiesOfUnionOrIntersectionType(t), c.isNeverReducedProperty)
// without creating the combined properties that can't satisfy
// isNeverReducedProperty, which in large intersections are most of them.
func (c *Checker) isNeverReducedIntersection(t *Type) bool {
if props := t.AsUnionOrIntersectionType().resolvedProperties; props != nil {
return core.Some(props, c.isNeverReducedProperty)
}
var checked collections.Set[string]
for _, current := range t.Types() {
for _, prop := range c.getPropertiesOfType(current) {
if !checked.AddIfAbsent(prop.Name) {
continue
}
var parts unionOrIntersectionPropertyParts
c.collectUnionOrIntersectionPropertyParts(t, prop.Name, true /*skipObjectFunctionPropertyAugment*/, &parts)
var combined *ast.Symbol
switch {
case parts.propSet.Size() == 0 && !parts.mergedInstantiations:
// What createUnionOrIntersectionProperty returns. Merged instantiations
// get a copy without check flags, which can't reduce.
combined = parts.singleProp
case parts.propSet.Size() != 0 && c.mayBeNeverReducedProperty(&parts):
combined = c.getPropertyOfUnionOrIntersectionType(t, prop.Name, true /*skipObjectFunctionPropertyAugment*/)
}
if combined != nil && c.isNeverReducedProperty(combined) {
return true
}
}
}
return false
}

// mayBeNeverReducedProperty reports whether the property combined from parts
// with more than one distinct constituent property can satisfy
// isNeverReducedProperty, which requires CheckFlagsHasLiteralType or
// CheckFlagsContainsPrivate.
func (c *Checker) mayBeNeverReducedProperty(parts *unionOrIntersectionPropertyParts) bool {
if parts.checkFlags&ast.CheckFlagsContainsPrivate != 0 {
return true
}
for prop := range parts.propSet.Values() {
if c.isLiteralOrPatternLiteralType(c.getTypeOfSymbol(prop)) {
return true
}
}
return false
}

func (c *Checker) isLiteralOrPatternLiteralType(t *Type) bool {
return isLiteralType(t) || c.isPatternLiteralType(t)
}

func (c *Checker) getReducedApparentType(t *Type) *Type {
// Since getApparentType may return a non-reduced union or intersection type, we need to perform
// type reduction both before and after obtaining the apparent type. For example, given a type parameter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
intersectionNeverReductionByProperty.ts(24,4): error TS2339: Property 'kind' does not exist on type 'never'.
The intersection 'A1' was reduced to 'never' because property 'kind' has conflicting types in some constituents.
intersectionNeverReductionByProperty.ts(30,4): error TS2339: Property 'flag' does not exist on type 'never'.
The intersection 'A7' was reduced to 'never' because property 'flag' has conflicting types in some constituents.
intersectionNeverReductionByProperty.ts(31,4): error TS2339: Property 'tag' does not exist on type 'never'.
The intersection 'A8' was reduced to 'never' because property 'tag' has conflicting types in some constituents.
intersectionNeverReductionByProperty.ts(58,4): error TS2339: Property 'x' does not exist on type 'never'.
The intersection 'P1 & P2' was reduced to 'never' because property 'x' exists in multiple constituents and is private in some.
intersectionNeverReductionByProperty.ts(59,4): error TS2445: Property 'x' is protected and only accessible within class 'Q1 & Q2' and its subclasses.
intersectionNeverReductionByProperty.ts(63,4): error TS2339: Property 'kind' does not exist on type 'never'.
The intersection 'Generic<"a"> & Generic<"b">' was reduced to 'never' because property 'value' has conflicting types in some constituents.
intersectionNeverReductionByProperty.ts(70,4): error TS2339: Property 'kind' does not exist on type 'never'.
The intersection 'Tagged<"b">' was reduced to 'never' because property 'kind' has conflicting types in some constituents.
intersectionNeverReductionByProperty.ts(74,7): error TS2339: Property 'kind' does not exist on type 'never'.
Property 'kind' does not exist on type 'never'.
intersectionNeverReductionByProperty.ts(90,4): error TS2339: Property 'p1' does not exist on type 'never'.
The intersection 'Many & { kind: "other"; }' was reduced to 'never' because property 'kind' has conflicting types in some constituents.


==== intersectionNeverReductionByProperty.ts (9 errors) ====
// Intersections that are and aren't reduced to never because of conflicting
// discriminants or private properties.

type A1 = { kind: "a"; x: string } & { kind: "b"; y: string };
type A2 = { kind: "a" | "b"; x: string } & { kind: "b"; y: string };
type A3 = { kind?: "a" } & { kind?: "b" };
type A4 = { kind: "a" } & { kind: string };
type A5 = { kind: `a${string}` } & { kind: `b${string}` };
type A6 = { kind: never } & { kind: "a" };
type A7 = { flag: true } & { flag: false };
type A8 = { tag: 1; a: string } & { b: number } & { tag: 2; c: boolean };
type A9 = { x: string } & { x: number };

declare const a1: A1;
declare const a2: A2;
declare const a3: A3;
declare const a4: A4;
declare const a5: A5;
declare const a6: A6;
declare const a7: A7;
declare const a8: A8;
declare const a9: A9;

a1.kind;
~~~~
!!! error TS2339: Property 'kind' does not exist on type 'never'.
!!! error TS2339: The intersection 'A1' was reduced to 'never' because property 'kind' has conflicting types in some constituents.
a2.kind;
a3.kind;
a4.kind;
a5.kind;
a6.kind;
a7.flag;
~~~~
!!! error TS2339: Property 'flag' does not exist on type 'never'.
!!! error TS2339: The intersection 'A7' was reduced to 'never' because property 'flag' has conflicting types in some constituents.
a8.tag;
~~~
!!! error TS2339: Property 'tag' does not exist on type 'never'.
!!! error TS2339: The intersection 'A8' was reduced to 'never' because property 'tag' has conflicting types in some constituents.
a9.x;

class P1 {
private x = 1;
}
class P2 {
private x = 1;
}
class Q1 {
protected x = 1;
}
class Q2 {
protected x = 1;
}
class Generic<T> {
private value!: T;
kind!: T;
}

declare const b1: P1 & P2;
declare const b2: Q1 & Q2;
declare const b3: P1 & { y: string };
declare const b4: Generic<string> & Generic<string>;
declare const b5: Generic<string> & Generic<number>;
declare const b6: Generic<"a"> & Generic<"b">;

b1.x;
~
!!! error TS2339: Property 'x' does not exist on type 'never'.
!!! error TS2339: The intersection 'P1 & P2' was reduced to 'never' because property 'x' exists in multiple constituents and is private in some.
b2.x;
~
!!! error TS2445: Property 'x' is protected and only accessible within class 'Q1 & Q2' and its subclasses.
b3.y;
b4.kind;
b5.kind;
b6.kind;
~~~~
!!! error TS2339: Property 'kind' does not exist on type 'never'.
!!! error TS2339: The intersection 'Generic<"a"> & Generic<"b">' was reduced to 'never' because property 'value' has conflicting types in some constituents.

type Tagged<T> = { kind: T } & { kind: "a" };
declare const c1: Tagged<"a">;
declare const c2: Tagged<"b">;
declare const c3: Tagged<string>;
c1.kind;
c2.kind;
~~~~
!!! error TS2339: Property 'kind' does not exist on type 'never'.
!!! error TS2339: The intersection 'Tagged<"b">' was reduced to 'never' because property 'kind' has conflicting types in some constituents.
c3.kind;

function f<T extends { kind: "a" } | { kind: "b" }>(x: T & { kind: "c" }, y: T & { kind: "a" }) {
x.kind;
~~~~
!!! error TS2339: Property 'kind' does not exist on type 'never'.
!!! error TS2339: Property 'kind' does not exist on type 'never'.
y.kind;
}

interface Many {
p1: string;
p2: number;
p3: boolean;
p4: string[];
p5: () => void;
kind: "many";
}
declare const d1: Many & { kind: "many"; extra: string };
declare const d2: Many & { kind: "other" };
declare const d3: Many & Partial<Many> & { p1: "literal" };
d1.extra;
d2.p1;
~~
!!! error TS2339: Property 'p1' does not exist on type 'never'.
!!! error TS2339: The intersection 'Many & { kind: "other"; }' was reduced to 'never' because property 'kind' has conflicting types in some constituents.
d3.p1;

Loading