Skip to content
Open
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
151 changes: 129 additions & 22 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,7 @@ type Checker struct {
structuredTypeExtraArena core.Arena[structuredTypeExtra]
freeTypeSets *temporaryTypeSet
freePropertyCounts *temporaryPropertyCounts
freeNameSets []*collections.Set[string]
mergedSymbols map[*ast.Symbol]*ast.Symbol
mergedExportsChecked collections.Set[*ast.Symbol]
factory ast.NodeFactory
Expand Down Expand Up @@ -19323,12 +19324,12 @@ func (c *Checker) getPropertiesOfObjectType(t *Type) []*ast.Symbol {
func (c *Checker) getPropertiesOfUnionOrIntersectionType(t *Type) []*ast.Symbol {
d := t.AsUnionOrIntersectionType()
if d.resolvedProperties == nil {
var checked collections.Set[string]
checked := c.getTemporaryNameSet()
defer c.releaseTemporaryNameSet(checked)
props := []*ast.Symbol{}
for _, current := range d.types {
for _, prop := range c.getPropertiesOfType(current) {
if !checked.Has(prop.Name()) {
checked.Add(prop.Name())
if checked.AddIfAbsent(prop.Name()) {
combinedProp := c.getPropertyOfUnionOrIntersectionType(t, prop.Name(), t.flags&TypeFlagsIntersection != 0 /*skipObjectFunctionPropertyAugment*/)
if combinedProp != nil {
props = append(props, combinedProp)
Expand Down Expand Up @@ -21916,15 +21917,41 @@ func (c *Checker) getUnionOrIntersectionProperty(t *Type, name string, skipObjec
return prop
}

func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name string, skipObjectFunctionPropertyAugment bool) *ast.Symbol {
// unionOrIntersectionPropertySources describes the constituent properties from
// which a union or intersection property would be synthesized.
type unionOrIntersectionPropertySources struct {
singleProp *ast.Symbol
// props holds distinct constituent properties in first-seen order, or is
// empty when every constituent shares singleProp.
props orderedSet[*ast.Symbol]
indexTypes []*Type
propFlags ast.SymbolFlags
optionalFlag ast.SymbolFlags
checkFlags ast.CheckFlags
syntheticFlag ast.CheckFlags
mergedInstantiations bool
}

// isSynthetic reports whether the sources require a new synthetic symbol rather
// than reusing (or cloning) singleProp.
func (s *unionOrIntersectionPropertySources) isSynthetic() bool {
return len(s.props.values) != 0 || s.checkFlags&ast.CheckFlagsReadPartial != 0 || len(s.indexTypes) != 0
}

func (s *unionOrIntersectionPropertySources) addProp(prop *ast.Symbol) {
if !s.props.contains(prop) {
s.props.add(prop)
}
}

func (c *Checker) getUnionOrIntersectionPropertySources(containingType *Type, name string, skipObjectFunctionPropertyAugment bool, sources *unionOrIntersectionPropertySources) bool {
propFlags := ast.SymbolFlagsNone
var singleProp *ast.Symbol
var propSet collections.OrderedSet[*ast.Symbol]
var indexTypes []*Type
isUnion := containingType.flags&TypeFlagsUnion != 0
// Flags we want to propagate to the result if they exist in all source symbols
var checkFlags ast.CheckFlags
var optionalFlag ast.SymbolFlags
var singleProp *ast.Symbol
var indexTypes []*Type
if !isUnion {
checkFlags = ast.CheckFlagsReadonly
optionalFlag = ast.SymbolFlagsOptional
Expand Down Expand Up @@ -21960,10 +21987,10 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
// back and not `Array<string>.length` when we're looking at a `.length` access on a `string[] | number[]`
mergedInstantiations = singleProp.Parent() != nil && len(c.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(singleProp.Parent())) != 0
} else {
if propSet.Size() == 0 {
propSet.Add(singleProp)
if len(sources.props.values) == 0 {
sources.addProp(singleProp)
}
propSet.Add(prop)
sources.addProp(prop)
}
// classes created by mixins are represented as intersections
// and overriding a property in a derived class redefines it completely at runtime
Expand Down Expand Up @@ -22026,17 +22053,17 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
}
if singleProp == nil {
// No property was found
return nil
return false
}
if isUnion &&
(propSet.Size() != 0 || checkFlags&ast.CheckFlagsPartial != 0) &&
(len(sources.props.values) != 0 || checkFlags&ast.CheckFlagsPartial != 0) &&
checkFlags&(ast.CheckFlagsContainsPrivate|ast.CheckFlagsContainsProtected|ast.CheckFlagsContainsWritePrivate|ast.CheckFlagsContainsWriteProtected) != 0 &&
!(propSet.Size() != 0 && c.hasCommonDeclaration(&propSet)) {
!(len(sources.props.values) != 0 && c.hasCommonDeclaration(sources.props.values)) {
// 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.
if checkFlags&(ast.CheckFlagsContainsPrivate|ast.CheckFlagsContainsProtected) != 0 {
return nil
return false
}
// Otherwise, if the private or protected declaration is for writing, reduce accessibility to that of
// the most restricted constituent.
Expand All @@ -22046,8 +22073,30 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
checkFlags &^= ast.CheckFlagsContainsWritePublic
}
}
if propSet.Size() == 0 && checkFlags&ast.CheckFlagsReadPartial == 0 && len(indexTypes) == 0 {
if !mergedInstantiations {
sources.singleProp = singleProp
sources.indexTypes = indexTypes
sources.propFlags = propFlags
sources.optionalFlag = optionalFlag
sources.checkFlags = checkFlags
sources.syntheticFlag = syntheticFlag
sources.mergedInstantiations = mergedInstantiations
return true
}

func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name string, skipObjectFunctionPropertyAugment bool) *ast.Symbol {
var sources unionOrIntersectionPropertySources
if !c.getUnionOrIntersectionPropertySources(containingType, name, skipObjectFunctionPropertyAugment, &sources) {
return nil
}
singleProp := sources.singleProp
indexTypes := sources.indexTypes
propFlags := sources.propFlags
optionalFlag := sources.optionalFlag
checkFlags := sources.checkFlags
syntheticFlag := sources.syntheticFlag
isUnion := containingType.flags&TypeFlagsUnion != 0
if !sources.isSynthetic() {
if !sources.mergedInstantiations {
return singleProp
}
// No symbol from a union/intersection should have a `.parent` set (since unions/intersections don't act as symbol parents)
Expand All @@ -22070,8 +22119,8 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
links.setWriteType(c, c.getWriteTypeOfSymbol(singleProp))
return clone
}
if propSet.Size() == 0 {
propSet.Add(singleProp)
if len(sources.props.values) == 0 {
sources.props.add(singleProp)
}
var declarations []*ast.Node
var firstType *Type
Expand All @@ -22080,7 +22129,7 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
var writeTypes []*Type
var firstValueDeclaration *ast.Node
var hasNonUniformValueDeclaration bool
for prop := range propSet.Values() {
for _, prop := range sources.props.values {
if firstValueDeclaration == nil {
firstValueDeclaration = prop.ValueDeclaration()
} else if prop.ValueDeclaration() != nil && prop.ValueDeclaration() != firstValueDeclaration {
Expand Down Expand Up @@ -22164,9 +22213,9 @@ func isPrototypeProperty(symbol *ast.Symbol) bool {
return symbol.Flags()&ast.SymbolFlagsMethod != 0 || symbol.CheckFlags()&ast.CheckFlagsSyntheticMethod != 0
}

func (c *Checker) hasCommonDeclaration(symbols *collections.OrderedSet[*ast.Symbol]) bool {
func (c *Checker) hasCommonDeclaration(symbols []*ast.Symbol) bool {
var commonDeclarations collections.Set[*ast.Node]
for symbol := range symbols.Values() {
for _, symbol := range symbols {
if len(symbol.Declarations()) == 0 {
return false
}
Expand Down Expand Up @@ -22356,14 +22405,72 @@ func (c *Checker) somePropertyReducesToNever(t *Type) bool {
// Go in the order the properties were found so the combined properties are created in the same order every time.
for propName, count := range counts.Entries() {
if count > 1 {
if prop := c.getPropertyOfUnionOrIntersectionType(t, propName, true /*skipObjectFunctionPropertyAugment*/); prop != nil && c.isNeverReducedProperty(prop) {
if c.intersectionPropertyReducesToNever(t, propName) {
return true
}
}
}
return false
}

// intersectionPropertyReducesToNever reports whether the combined property named
// name would satisfy isNeverReducedProperty, without creating and caching that
// property. Most such properties are never otherwise looked up, so creating
// them only to inspect their flags retains a large number of symbols.
func (c *Checker) intersectionPropertyReducesToNever(t *Type, name string) bool {
if prop := t.AsUnionOrIntersectionType().propertyCacheWithoutFunctionPropertyAugment[name]; prop != nil {
return c.isNeverReducedProperty(prop)
}
var sources unionOrIntersectionPropertySources
if !c.getUnionOrIntersectionPropertySources(t, name, true /*skipObjectFunctionPropertyAugment*/, &sources) {
return false
}
if !sources.isSynthetic() {
if !sources.mergedInstantiations {
return c.isNeverReducedProperty(sources.singleProp)
}
prop := c.getUnionOrIntersectionProperty(t, name, true /*skipObjectFunctionPropertyAugment*/)
return prop != nil && c.isNeverReducedProperty(prop)
}
if len(sources.props.values) == 0 {
sources.props.add(sources.singleProp)
}
checkFlags := sources.checkFlags
var firstType *Type
var firstValueDeclaration *ast.Node
hasNonUniformValueDeclaration := false
propTypes := make([]*Type, 0, len(sources.props.values)+len(sources.indexTypes))
for _, prop := range sources.props.values {
if firstValueDeclaration == nil {
firstValueDeclaration = prop.ValueDeclaration()
} else if prop.ValueDeclaration() != nil && prop.ValueDeclaration() != firstValueDeclaration {
hasNonUniformValueDeclaration = true
}
propType := c.getTypeOfSymbol(prop)
if firstType == nil {
firstType = propType
}
if propType != firstType {
checkFlags |= ast.CheckFlagsHasNonUniformType
}
if isLiteralType(propType) || c.isPatternLiteralType(propType) {
checkFlags |= ast.CheckFlagsHasLiteralType
}
if propType.flags&TypeFlagsNever != 0 && propType != c.uniqueLiteralType {
checkFlags |= ast.CheckFlagsHasNeverType
}
propTypes = append(propTypes, propType)
}
propTypes = append(propTypes, sources.indexTypes...)
hasValueDeclaration := !hasNonUniformValueDeclaration && firstValueDeclaration != nil
if !hasValueDeclaration && checkFlags&ast.CheckFlagsContainsPrivate != 0 {
return true
}
return (sources.propFlags|sources.optionalFlag)&ast.SymbolFlagsOptional == 0 &&
checkFlags&(ast.CheckFlagsNonUniformAndLiteral|ast.CheckFlagsHasNeverType) == ast.CheckFlagsNonUniformAndLiteral &&
c.getIntersectionType(propTypes).flags&TypeFlagsNever != 0
}

func (c *Checker) getReducedUnionType(unionType *Type) *Type {
reducedTypes := core.SameMap(unionType.Types(), c.getReducedType)
if core.Same(reducedTypes, unionType.Types()) {
Expand Down
21 changes: 21 additions & 0 deletions tsc/internal/checker/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,27 @@ func (c *Checker) releaseTemporaryPropertyCounts(counts *temporaryPropertyCounts
c.freePropertyCounts = counts
}

// Property enumeration of a combined type may recursively enumerate other
// combined types, so each call borrows a separate name set.
func (c *Checker) getTemporaryNameSet() *collections.Set[string] {
if n := len(c.freeNameSets); n != 0 {
set := c.freeNameSets[n-1]
c.freeNameSets[n-1] = nil
c.freeNameSets = c.freeNameSets[:n-1]
Comment thread
jakebailey marked this conversation as resolved.
return set
}
return &collections.Set[string]{}
}

func (c *Checker) releaseTemporaryNameSet(set *collections.Set[string]) {
// Do not retain unusually large tables in the free list.
if set.Len() > 4096 {
return
}
set.Clear()
c.freeNameSets = append(c.freeNameSets, set)
}

func (s *orderedSet[T]) contains(value T) bool {
if s.valuesByKey == nil {
return slices.Contains(s.values, value)
Expand Down
Loading