Skip to content
Open
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: 114 additions & 37 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -21338,38 +21338,40 @@ func (c *Checker) instantiateSymbolTable(symbols ast.SymbolTable, m *TypeMapper)
}

func (c *Checker) instantiateSymbol(symbol *ast.Symbol, m *TypeMapper) *ast.Symbol {
if symbol == nil || m == nil {
if symbol == nil || m == nil || m.MapsThisOnly() && isThisless(symbol) {
return symbol
}
links := c.valueSymbolLinks.Get(symbol)
if m.MapsThisOnly() && isThisless(symbol) {
return symbol
}
// If the type of the symbol is already resolved, and if that type could not possibly
// be affected by instantiation, simply return the symbol itself.
if links.resolvedType != nil && !c.couldContainTypeVariables(links.resolvedType) {
if symbol.Flags()&ast.SymbolFlagsSetAccessor == 0 {
return symbol
// Use TryGet to avoid allocating links for a symbol whose type has never been resolved.
// Instantiated symbols always have links.
var nameType *Type
if links := c.valueSymbolLinks.TryGet(symbol); links != nil {
// If the type of the symbol is already resolved, and if that type could not possibly
// be affected by instantiation, simply return the symbol itself.
if links.resolvedType != nil && !c.couldContainTypeVariables(links.resolvedType) {
if symbol.Flags()&ast.SymbolFlagsSetAccessor == 0 {
return symbol
}
// If we're a setter, check writeType.
if links.getWriteType() != nil && !c.couldContainTypeVariables(links.getWriteType()) {
return symbol
}
}
// If we're a setter, check writeType.
if links.getWriteType() != nil && !c.couldContainTypeVariables(links.getWriteType()) {
return symbol
if symbol.CheckFlags()&ast.CheckFlagsInstantiated != 0 {
// If symbol being instantiated is itself a instantiation, fetch the original target and combine the
// type mappers. This ensures that original type identities are properly preserved and that aliases
// always reference a non-aliases.
symbol = links.target
m = c.combineTypeMappers(links.mapper, m)
}
}
if symbol.CheckFlags()&ast.CheckFlagsInstantiated != 0 {
// If symbol being instantiated is itself a instantiation, fetch the original target and combine the
// type mappers. This ensures that original type identities are properly preserved and that aliases
// always reference a non-aliases.
symbol = links.target
m = c.combineTypeMappers(links.mapper, m)
nameType = links.getNameType()
}
// Create a new transient symbol that shares the underlying data with the original symbol.
result := c.newSharedDataSymbol(symbol)
result.SetCheckFlags(ast.CheckFlagsInstantiated | symbol.CheckFlags()&(ast.CheckFlagsReadonly|ast.CheckFlagsLate|ast.CheckFlagsOptionalParameter|ast.CheckFlagsRestParameter))
resultLinks := c.valueSymbolLinks.Get(result)
resultLinks.target = symbol
resultLinks.mapper = m
resultLinks.setNameType(c, links.getNameType())
resultLinks.setNameType(c, nameType)
return result
}

Expand Down Expand Up @@ -22757,7 +22759,7 @@ func (c *Checker) instantiateTypeWithAlias(t *Type, m *TypeMapper, alias *TypeAl
// Check for type variables in the alias, so things like `type Brand<T> = number & {}` can potentially be copied with new alias type args, despite them being unreferenced.
// This is the behavior most people using aliases expect, and prevents the cache from leaking type parameters outside their scope of validity.
// tests/cases/compiler/declarationEmitArrowFunctionNoRenaming.ts contains an example of this, which previously only worked in strada via some input node reuse logic instead.
if t == nil || m == nil || !(c.couldContainTypeVariables(t) || (t.alias != nil && len(t.alias.typeArguments) > 0 && core.Some(t.alias.typeArguments, c.couldContainTypeVariables))) {
if t == nil || m == nil || c.isTypeVariableFree(t) {
return t
}
if len(c.instantiationStack) == 100 || c.instantiationCount >= 5_000_000 {
Expand Down Expand Up @@ -22803,6 +22805,18 @@ func (c *Checker) instantiateTypeWithAlias(t *Type, m *TypeMapper, alias *TypeAl
return result
}

// Returns true if no instantiation of t can differ from t itself, because neither t nor its alias
// type arguments could contain type variables. The result is cached on the types involved.
func (c *Checker) isTypeVariableFree(t *Type) bool {
// Check the cached result inline before falling back to couldContainTypeVariables, as this is on a hot path.
knownFree := t.flags&TypeFlagsStructuredOrInstantiable == 0 ||
t.objectFlags&(ObjectFlagsCouldContainTypeVariablesComputed|ObjectFlagsCouldContainTypeVariables) == ObjectFlagsCouldContainTypeVariablesComputed
if !knownFree && c.couldContainTypeVariables(t) {
return false
}
return t.alias == nil || !core.Some(t.alias.typeArguments, c.couldContainTypeVariables)
}

// Without an alias the key is just the type ID, which avoids hashing. Hashed keys have
// a nonzero high half with overwhelming probability, so the two kinds do not collide.
func getActiveMapperCacheKey(t *Type, alias *TypeAlias) CacheHashKey {
Expand Down Expand Up @@ -22915,12 +22929,7 @@ func (c *Checker) instantiateTypeWorker(t *Type, m *TypeMapper, alias *TypeAlias
objectFlags := t.objectFlags
if objectFlags&(ObjectFlagsReference|ObjectFlagsAnonymous|ObjectFlagsMapped) != 0 {
if objectFlags&ObjectFlagsReference != 0 && t.AsTypeReference().node == nil {
resolvedTypeArguments := t.AsTypeReference().resolvedTypeArguments
newTypeArguments := c.instantiateTypes(resolvedTypeArguments, m)
if core.Same(newTypeArguments, resolvedTypeArguments) {
return t
}
return c.createNormalizedTypeReference(t.Target(), newTypeArguments)
return c.instantiateTypeReference(t, m)
}
if objectFlags&ObjectFlagsReverseMapped != 0 {
return c.instantiateReverseMappedType(t, m)
Expand Down Expand Up @@ -22985,6 +22994,42 @@ func (c *Checker) instantiateTypeWorker(t *Type, m *TypeMapper, alias *TypeAlias
return t
}

func (c *Checker) instantiateTypeReference(t *Type, m *TypeMapper) *Type {
resolvedTypeArguments := t.AsTypeReference().resolvedTypeArguments
// Instantiate small argument lists into a temporary buffer, and only copy them once we know
// they're needed for a new type reference. Retained arguments are kept in a separate variable
// that never aliases buf, so that buf does not escape to the heap.
var buf [8]*Type
var ownedTypeArguments []*Type
var newTypeArguments []*Type
if len(resolvedTypeArguments) <= len(buf) {
newTypeArguments = c.instantiateListInto(resolvedTypeArguments, m, (*Checker).instantiateType, buf[:])
} else {
ownedTypeArguments = c.instantiateList(resolvedTypeArguments, m, (*Checker).instantiateType)
newTypeArguments = ownedTypeArguments
}
if core.Same(newTypeArguments, resolvedTypeArguments) {
return t
}
target := t.Target()
normalize := target.objectFlags&ObjectFlagsTuple != 0 && target.AsTupleType().combinedFlags&ElementFlagsNonRequired != 0
var id CacheHashKey
if !normalize {
// No normalization is needed, so check for an existing instantiation first.
id = getTypeListKey(newTypeArguments)
if existing, ok := target.AsInterfaceType().instantiations[id]; ok {
return existing
}
}
if ownedTypeArguments == nil {
ownedTypeArguments = slices.Clone(newTypeArguments)
}
if normalize {
return c.createNormalizedTypeReference(target, ownedTypeArguments)
}
return c.newTypeReferenceInstantiation(target, id, ownedTypeArguments, ObjectFlagsNone)
}

// Handles instantiation of the following object types:
// AnonymousType (ObjectFlagsAnonymous|ObjectFlagsSingleSignatureType)
// TypeReference with node != nil (ObjectFlagsReference)
Expand Down Expand Up @@ -23041,8 +23086,18 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type
}
// We are instantiating an anonymous type that has one or more type parameters in scope. Apply the
// mapper to the type parameters to produce the effective list of type arguments, and compute the
// instantiation cache key from the type IDs of the type arguments.
typeArguments := make([]*Type, len(typeParameters))
// instantiation cache key from the type IDs of the type arguments. The type arguments are computed
// into a temporary buffer and only copied when creating a new instantiation. Retained arguments
// are kept in a separate variable that never aliases buf, so that buf does not escape to the heap.
var buf [8]*Type
var ownedTypeArguments []*Type
var typeArguments []*Type
if len(typeParameters) <= len(buf) {
typeArguments = buf[:len(typeParameters)]
} else {
ownedTypeArguments = make([]*Type, len(typeParameters))
typeArguments = ownedTypeArguments
}
for i, tp := range typeParameters {
typeArguments[i] = c.mapTypeWithCompositeMapper(tp, t.Mapper(), m)
}
Expand All @@ -23058,7 +23113,10 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type
}
result := data.instantiations[key]
if result == nil {
newMapper := newTypeMapper(typeParameters, typeArguments)
if ownedTypeArguments == nil {
ownedTypeArguments = slices.Clone(typeArguments)
}
newMapper := newTypeMapper(typeParameters, ownedTypeArguments)
if target.objectFlags&ObjectFlagsSingleSignatureType != 0 && m != nil {
newMapper = c.combineTypeMappers(newMapper, m)
}
Expand Down Expand Up @@ -23450,7 +23508,11 @@ func (c *Checker) instantiateTypeAlias(alias *TypeAlias, m *TypeMapper) *TypeAli
if alias == nil {
return nil
}
return &TypeAlias{symbol: alias.symbol, typeArguments: c.instantiateTypes(alias.typeArguments, m)}
typeArguments := c.instantiateTypes(alias.typeArguments, m)
if core.Same(typeArguments, alias.typeArguments) {
return alias
}
return &TypeAlias{symbol: alias.symbol, typeArguments: typeArguments}
}

func (c *Checker) instantiateTypes(types []*Type, m *TypeMapper) []*Type {
Expand All @@ -23470,10 +23532,21 @@ func (c *Checker) instantiateIndexInfos(indexInfos []*IndexInfo, m *TypeMapper)
}

func (c *Checker) instantiateList[T comparable](values []T, m *TypeMapper, instantiator func(c *Checker, value T, m *TypeMapper) T) []T {
return c.instantiateListInto(values, m, instantiator, nil)
}

// Like instantiateList, but if any value changes, the result is written into buf when it has
// sufficient capacity. Callers must copy such a result before retaining it.
func (c *Checker) instantiateListInto[T comparable](values []T, m *TypeMapper, instantiator func(c *Checker, value T, m *TypeMapper) T, buf []T) []T {
for i, value := range values {
mapped := instantiator(c, value, m)
if mapped != value {
result := make([]T, len(values))
var result []T
if cap(buf) >= len(values) {
result = buf[:len(values)]
} else {
result = make([]T, len(values))
}
copy(result, values[:i])
result[i] = mapped
for j := i + 1; j < len(values); j++ {
Expand Down Expand Up @@ -25212,7 +25285,7 @@ func (c *Checker) isDeferredType(t *Type, checkTuples bool) bool {
}

func (c *Checker) getPermissiveInstantiation(t *Type) *Type {
if t.flags&(TypeFlagsPrimitive|TypeFlagsAnyOrUnknown|TypeFlagsNever) != 0 {
if t.flags&(TypeFlagsPrimitive|TypeFlagsAnyOrUnknown|TypeFlagsNever) != 0 || c.isTypeVariableFree(t) {
return t
}
key := CachedTypeKey{kind: CachedTypeKindPermissiveInstantiation, typeId: t.id}
Expand All @@ -25225,7 +25298,7 @@ func (c *Checker) getPermissiveInstantiation(t *Type) *Type {
}

func (c *Checker) getRestrictiveInstantiation(t *Type) *Type {
if t.flags&(TypeFlagsPrimitive|TypeFlagsAnyOrUnknown|TypeFlagsNever) != 0 {
if t.flags&(TypeFlagsPrimitive|TypeFlagsAnyOrUnknown|TypeFlagsNever) != 0 || c.isTypeVariableFree(t) {
return t
}
key := CachedTypeKey{kind: CachedTypeKindRestrictiveInstantiation, typeId: t.id}
Expand Down Expand Up @@ -25849,10 +25922,14 @@ func (c *Checker) createTypeReference(target *Type, typeArguments []*Type) *Type

func (c *Checker) createTypeReferenceEx(target *Type, typeArguments []*Type, objectFlags ObjectFlags) *Type {
id := getTypeListKey(typeArguments)
intf := target.AsInterfaceType()
if t, ok := intf.instantiations[id]; ok {
if t, ok := target.AsInterfaceType().instantiations[id]; ok {
return t
}
return c.newTypeReferenceInstantiation(target, id, typeArguments, objectFlags)
}

func (c *Checker) newTypeReferenceInstantiation(target *Type, id CacheHashKey, typeArguments []*Type, objectFlags ObjectFlags) *Type {
intf := target.AsInterfaceType()
t := c.newObjectType(ObjectFlagsReference|objectFlags|c.getPropagatingFlagsOfTypes(typeArguments, TypeFlagsNone), target.symbol)
d := t.AsTypeReference()
d.target = target
Expand Down
Loading