diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index d9cb44e00a903..e953f3039d471 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -913,7 +913,6 @@ type Checker struct { syncIterationTypesResolver *IterationTypesResolver asyncIterationTypesResolver *IterationTypesResolver isPrimitiveOrObjectOrEmptyType func(*Type) bool - containsMissingType func(*Type) bool couldContainTypeVariables func(*Type) bool isStringIndexSignatureOnlyType func(*Type) bool markNodeAssignments func(*ast.Node) bool @@ -1288,15 +1287,16 @@ func (c *Checker) initializeClosures() { c.isPrimitiveOrObjectOrEmptyType = func(t *Type) bool { return t.flags&(TypeFlagsPrimitive|TypeFlagsNonPrimitive) != 0 || c.IsEmptyAnonymousObjectType(t) } - c.containsMissingType = func(t *Type) bool { - return t == c.missingType || t.flags&TypeFlagsUnion != 0 && t.Types()[0] == c.missingType - } c.couldContainTypeVariables = c.couldContainTypeVariablesWorker c.isStringIndexSignatureOnlyType = c.isStringIndexSignatureOnlyTypeWorker c.markNodeAssignments = c.markNodeAssignmentsWorker c.compareTypesAssignable = c.compareTypesAssignableWorker } +func (c *Checker) containsMissingType(t *Type) bool { + return t == c.missingType || t.flags&TypeFlagsUnion != 0 && t.Types()[0] == c.missingType +} + func (c *Checker) initializeIterationResolvers() { c.syncIterationTypesResolver = &IterationTypesResolver{ iteratorSymbolName: "iterator", @@ -27311,10 +27311,21 @@ func everyContainedType(t *Type, f func(*Type) bool) bool { func (c *Checker) filterType(t *Type, f func(*Type) bool) *Type { if t.flags&TypeFlagsUnion != 0 { types := t.Types() - filtered := core.Filter(types, f) - if core.Same(types, filtered) { + firstRemoved := slices.IndexFunc(types, func(u *Type) bool { return !f(u) }) + if firstRemoved < 0 { return t } + // The filtered lists are only needed while constructing the result, which copies them + // into a new type if needed, so borrow temporary storage for them. + set := c.getTemporaryTypeSet() + defer c.releaseTemporaryTypeSet(set) + filtered := append(set.values[:0], types[:firstRemoved]...) + for _, u := range types[firstRemoved+1:] { + if f(u) { + filtered = append(filtered, u) + } + } + set.values = filtered origin := t.AsUnionType().origin var newOrigin *Type if origin != nil && origin.flags&TypeFlagsUnion != 0 { @@ -27324,9 +27335,15 @@ func (c *Checker) filterType(t *Type, f func(*Type) bool) *Type { // Otherwise, if we have exactly one type left in the origin set, return that as the filtered type. // Otherwise, construct a new filtered origin type. originTypes := origin.Types() - originFiltered := core.Filter(originTypes, func(u *Type) bool { - return u.flags&TypeFlagsUnion != 0 || f(u) - }) + originSet := c.getTemporaryTypeSet() + defer c.releaseTemporaryTypeSet(originSet) + originFiltered := originSet.values[:0] + for _, u := range originTypes { + if u.flags&TypeFlagsUnion != 0 || f(u) { + originFiltered = append(originFiltered, u) + } + } + originSet.values = originFiltered if len(originTypes)-len(originFiltered) == len(types)-len(filtered) { if len(originFiltered) == 1 { return originFiltered[0] @@ -27355,10 +27372,16 @@ func (c *Checker) removeType(t *Type, targetType *Type) *Type { return c.filterType(t, func(t *Type) bool { return t != targetType }) } types := t.Types() - if i, ok := slices.BinarySearchFunc(types, targetType, CompareTypes); ok { - if len(types) == 2 { - return types[1-i] + if len(types) == 2 { + switch targetType { + case types[0]: + return types[1] + case types[1]: + return types[0] } + return t + } + if i, ok := slices.BinarySearchFunc(types, targetType, CompareTypes); ok { // Remove the target type from the slice. set := c.getTemporaryTypeSet() defer c.releaseTemporaryTypeSet(set) diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index 562ae0451cb43..61f405dd5fe35 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -3313,53 +3313,56 @@ func (r *Relater) isSourceIntersectionNeedingExtraCheck(source *Type, target *Ty }) } +// relateVariances relates type arguments according to the given variances. The boolean result +// reports whether the returned Ternary is final; otherwise a structural comparison should follow. +func (r *Relater) relateVariances(sourceTypeArguments []*Type, targetTypeArguments []*Type, variances []VarianceFlags, reportErrors bool, intersectionState IntersectionState, saveErrorState errorState, varianceCheckFailed *bool, originalErrorChain **ErrorChain) (Ternary, bool) { + if result := r.typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState); result != TernaryFalse { + return result, true + } + if core.Some(variances, func(v VarianceFlags) bool { return v&VarianceFlagsAllowsStructuralFallback != 0 }) { + // If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we + // have to allow a structural fallback check + // We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially + // be assuming identity of the type parameter. + *originalErrorChain = nil + r.restoreErrorState(saveErrorState) + return TernaryFalse, false + } + allowStructuralFallback := r.c.hasCovariantVoidArgument(targetTypeArguments, variances) + *varianceCheckFailed = !allowStructuralFallback + // The type arguments did not relate appropriately, but it may be because we have no variance + // information (in which case typeArgumentsRelatedTo defaulted to covariance for all type + // arguments). It might also be the case that the target type has a 'void' type argument for + // a covariant type parameter that is only used in return positions within the generic type + // (in which case any type argument is permitted on the source side). In those cases we proceed + // with a structural comparison. Otherwise, we know for certain the instantiations aren't + // related and we can return here. + if len(variances) != 0 && !allowStructuralFallback { + // In some cases generic types that are covariant in regular type checking mode become + // invariant in --strictFunctionTypes mode because one or more type parameters are used in + // both co- and contravariant positions. In order to make it easier to diagnose *why* such + // types are invariant, if any of the type parameters are invariant we reset the reported + // errors and instead force a structural comparison (which will include elaborations that + // reveal the reason). + // We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`, + // we can return `False` early here to skip calculating the structural error message we don't need. + if *varianceCheckFailed && !(reportErrors && core.Some(variances, func(v VarianceFlags) bool { return (v & VarianceFlagsVarianceMask) == VarianceFlagsInvariant })) { + return TernaryFalse, true + } + // We remember the original error information so we can restore it in case the structural + // comparison unexpectedly succeeds. This can happen when the structural comparison result + // is a Ternary.Maybe for example caused by the recursion depth limiter. + *originalErrorChain = r.errorChain + r.restoreErrorState(saveErrorState) + } + return TernaryFalse, false +} + func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, reportErrors bool, intersectionState IntersectionState) Ternary { var result Ternary var varianceCheckFailed bool var originalErrorChain *ErrorChain saveErrorState := r.getErrorState() - relateVariances := func(sourceTypeArguments []*Type, targetTypeArguments []*Type, variances []VarianceFlags, intersectionState IntersectionState) (Ternary, bool) { - if result = r.typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState); result != TernaryFalse { - return result, true - } - if core.Some(variances, func(v VarianceFlags) bool { return v&VarianceFlagsAllowsStructuralFallback != 0 }) { - // If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we - // have to allow a structural fallback check - // We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially - // be assuming identity of the type parameter. - originalErrorChain = nil - r.restoreErrorState(saveErrorState) - return TernaryFalse, false - } - allowStructuralFallback := r.c.hasCovariantVoidArgument(targetTypeArguments, variances) - varianceCheckFailed = !allowStructuralFallback - // The type arguments did not relate appropriately, but it may be because we have no variance - // information (in which case typeArgumentsRelatedTo defaulted to covariance for all type - // arguments). It might also be the case that the target type has a 'void' type argument for - // a covariant type parameter that is only used in return positions within the generic type - // (in which case any type argument is permitted on the source side). In those cases we proceed - // with a structural comparison. Otherwise, we know for certain the instantiations aren't - // related and we can return here. - if len(variances) != 0 && !allowStructuralFallback { - // In some cases generic types that are covariant in regular type checking mode become - // invariant in --strictFunctionTypes mode because one or more type parameters are used in - // both co- and contravariant positions. In order to make it easier to diagnose *why* such - // types are invariant, if any of the type parameters are invariant we reset the reported - // errors and instead force a structural comparison (which will include elaborations that - // reveal the reason). - // We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`, - // we can return `False` early here to skip calculating the structural error message we don't need. - if varianceCheckFailed && !(reportErrors && core.Some(variances, func(v VarianceFlags) bool { return (v & VarianceFlagsVarianceMask) == VarianceFlagsInvariant })) { - return TernaryFalse, true - } - // We remember the original error information so we can restore it in case the structural - // comparison unexpectedly succeeds. This can happen when the structural comparison result - // is a Ternary.Maybe for example caused by the recursion depth limiter. - originalErrorChain = r.errorChain - r.restoreErrorState(saveErrorState) - } - return TernaryFalse, false - } switch { case r.relation == r.c.identityRelation: // We've already checked that source.flags and target.flags are identical @@ -3455,7 +3458,7 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo nodeIsInJsFile := ast.IsInJSFile(source.alias.symbol.ValueDeclaration()) sourceTypes := r.c.fillMissingTypeArguments(source.alias.typeArguments, params, minParams, nodeIsInJsFile) targetTypes := r.c.fillMissingTypeArguments(target.alias.typeArguments, params, minParams, nodeIsInJsFile) - varianceResult, ok := relateVariances(sourceTypes, targetTypes, variances, intersectionState) + varianceResult, ok := r.relateVariances(sourceTypes, targetTypes, variances, reportErrors, intersectionState, saveErrorState, &varianceCheckFailed, &originalErrorChain) if ok { return varianceResult } @@ -3889,7 +3892,7 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo if len(variances) == 0 { return TernaryUnknown } - varianceResult, ok := relateVariances(r.c.getTypeArguments(source), r.c.getTypeArguments(target), variances, intersectionState) + varianceResult, ok := r.relateVariances(r.c.getTypeArguments(source), r.c.getTypeArguments(target), variances, reportErrors, intersectionState, saveErrorState, &varianceCheckFailed, &originalErrorChain) if ok { return varianceResult } diff --git a/tsc/internal/core/core.go b/tsc/internal/core/core.go index f5c296b9b5bb0..c5f25d5caa6c8 100644 --- a/tsc/internal/core/core.go +++ b/tsc/internal/core/core.go @@ -36,11 +36,21 @@ func ApplyDebugStackLimit() { func Filter[T any](slice []T, f func(T) bool) []T { for i, value := range slice { if !f(value) { - result := slices.Clone(slice[:i]) - for i++; i < len(slice); i++ { - value = slice[i] - if f(value) { - result = append(result, value) + // Find the next kept element so the result can be sized to an upper bound + // up front, and nothing is allocated if no elements are kept. + j := i + 1 + for j < len(slice) && !f(slice[j]) { + j++ + } + result := make([]T, i, i+len(slice)-j) + copy(result, slice[:i]) + if j < len(slice) { + result = append(result, slice[j]) + for j++; j < len(slice); j++ { + value = slice[j] + if f(value) { + result = append(result, value) + } } } return result @@ -64,11 +74,19 @@ func FilterSeq[T any](slice []T, f func(T) bool) iter.Seq[T] { func FilterIndex[T any](slice []T, f func(T, int, []T) bool) []T { for i, value := range slice { if !f(value, i, slice) { - result := slices.Clone(slice[:i]) - for i++; i < len(slice); i++ { - value = slice[i] - if f(value, i, slice) { - result = append(result, value) + j := i + 1 + for j < len(slice) && !f(slice[j], j, slice) { + j++ + } + result := make([]T, i, i+len(slice)-j) + copy(result, slice[:i]) + if j < len(slice) { + result = append(result, slice[j]) + for j++; j < len(slice); j++ { + value = slice[j] + if f(value, j, slice) { + result = append(result, value) + } } } return result @@ -825,7 +843,8 @@ func Deduplicate[T comparable](slice []T) []T { if len(slice) > 1 { for i, value := range slice { if slices.Contains(slice[:i], value) { - result := slices.Clone(slice[:i]) + result := make([]T, i, len(slice)-1) + copy(result, slice[:i]) for i++; i < len(slice); i++ { value = slice[i] if !slices.Contains(result, value) {