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
47 changes: 35 additions & 12 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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 {
Expand All @@ -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]
Expand Down Expand Up @@ -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)
Expand Down
91 changes: 47 additions & 44 deletions tsc/internal/checker/relater.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
41 changes: 30 additions & 11 deletions tsc/internal/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Loading