From 19170c3488882f84105f2291c4802048a4d8149a Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 13 Oct 2026 21:58:41 +0000 Subject: [PATCH 1/4] Implement packed relation cache and associated tests for subtype relations --- tsc/internal/checker/checker.go | 3 +- tsc/internal/checker/relater.go | 62 ++++++++++++++++- tsc/internal/checker/relater_test.go | 100 +++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 tsc/internal/checker/relater_test.go diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index d2759e02c2bf9..b65a6dfdc9022 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -1066,8 +1066,7 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { c.flowLoopCache = make(map[FlowLoopKey]*Type) c.flowNodeReachable = make(map[*ast.FlowNode]bool) c.flowNodePostSuper = make(map[*ast.FlowNode]bool) - c.subtypeRelation = &Relation{} - c.strictSubtypeRelation = &Relation{} + c.subtypeRelation, c.strictSubtypeRelation = newSubtypeRelationPair() c.assignableRelation = &Relation{} c.comparableRelation = &Relation{} c.identityRelation = &Relation{} diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index a9bc484fef1d2..d12b388eeddd7 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -96,21 +96,81 @@ func asRecursionId[T *ast.Node | *ast.Symbol | *Type](value T) RecursionId { } type Relation struct { - results map[CacheHashKey]RelationComparisonResult + results map[CacheHashKey]RelationComparisonResult + packedResults *packedRelationCache + packedShift uint8 + count int + implySuccess *Relation + implyFailure *Relation +} + +type packedRelationCache struct { + results map[CacheHashKey]uint16 +} + +func newSubtypeRelationPair() (*Relation, *Relation) { + packedResults := &packedRelationCache{} + subtype := &Relation{packedResults: packedResults} + strictSubtype := &Relation{packedResults: packedResults, packedShift: 6} + subtype.implyFailure = strictSubtype + strictSubtype.implySuccess = subtype + return subtype, strictSubtype } func (r *Relation) get(key CacheHashKey) RelationComparisonResult { + if r.packedResults != nil { + return RelationComparisonResult(r.packedResults.results[key]>>r.packedShift) & relationComparisonResultMask + } return r.results[key] } func (r *Relation) set(key CacheHashKey, result RelationComparisonResult) { + if r.packedResults != nil { + r.setPacked(key, result) + return + } if r.results == nil { r.results = make(map[CacheHashKey]RelationComparisonResult) } r.results[key] = result } +const relationComparisonResultMask = RelationComparisonResultSucceeded | + RelationComparisonResultFailed | + RelationComparisonResultReportsUnmeasurable | + RelationComparisonResultReportsUnreliable | + RelationComparisonResultComplexityOverflow + +func (r *Relation) setPacked(key CacheHashKey, result RelationComparisonResult) { + if r.packedResults.results == nil { + r.packedResults.results = make(map[CacheHashKey]uint16) + } + packed := r.packedResults.results[key] + packed = r.setPackedResult(packed, result, false) + if result&RelationComparisonResultSucceeded != 0 && r.implySuccess != nil { + packed = r.implySuccess.setPackedResult(packed, result, true) + } + if result&RelationComparisonResultFailed != 0 && result&RelationComparisonResultOverflow == 0 && r.implyFailure != nil { + packed = r.implyFailure.setPackedResult(packed, result, true) + } + r.packedResults.results[key] = packed +} + +func (r *Relation) setPackedResult(packed uint16, result RelationComparisonResult, onlyIfEmpty bool) uint16 { + mask := uint16(relationComparisonResultMask) << r.packedShift + if onlyIfEmpty && packed&mask != 0 { + return packed + } + if packed&mask == 0 { + r.count++ + } + return packed&^mask | uint16(result&relationComparisonResultMask)< Date: Tue, 13 Oct 2026 23:52:50 +0000 Subject: [PATCH 2/4] Share a single cache between all relationships and pack results into the same value entry. --- tsc/internal/checker/checker.go | 5 +- tsc/internal/checker/relater.go | 74 ++++++++++++------------- tsc/internal/checker/relater_test.go | 82 ++++++++++++++++++++++++++-- 3 files changed, 115 insertions(+), 46 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index b65a6dfdc9022..93277337f31f2 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -1066,10 +1066,7 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { c.flowLoopCache = make(map[FlowLoopKey]*Type) c.flowNodeReachable = make(map[*ast.FlowNode]bool) c.flowNodePostSuper = make(map[*ast.FlowNode]bool) - c.subtypeRelation, c.strictSubtypeRelation = newSubtypeRelationPair() - c.assignableRelation = &Relation{} - c.comparableRelation = &Relation{} - c.identityRelation = &Relation{} + c.subtypeRelation, c.strictSubtypeRelation, c.assignableRelation, c.comparableRelation, c.identityRelation = newRelations() c.enumRelation = make(map[EnumRelationKey]RelationComparisonResult) c.moduleImportAttributesTypes = make(map[*ast.Symbol]*Type) c.getGlobalESSymbolType = c.getGlobalTypeResolver("Symbol", 0 /*arity*/, false /*reportErrors*/) diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index d12b388eeddd7..3cf46379470d8 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -96,43 +96,35 @@ func asRecursionId[T *ast.Node | *ast.Symbol | *Type](value T) RecursionId { } type Relation struct { - results map[CacheHashKey]RelationComparisonResult packedResults *packedRelationCache packedShift uint8 - count int - implySuccess *Relation - implyFailure *Relation } type packedRelationCache struct { - results map[CacheHashKey]uint16 + results map[CacheHashKey]uint32 + counts [packedRelationCount]int } -func newSubtypeRelationPair() (*Relation, *Relation) { +func newRelations() (subtype *Relation, strictSubtype *Relation, assignable *Relation, comparableRelation *Relation, identity *Relation) { packedResults := &packedRelationCache{} - subtype := &Relation{packedResults: packedResults} - strictSubtype := &Relation{packedResults: packedResults, packedShift: 6} - subtype.implyFailure = strictSubtype - strictSubtype.implySuccess = subtype - return subtype, strictSubtype + identity = newRelation(packedResults, 0) + strictSubtype = newRelation(packedResults, 6) + subtype = newRelation(packedResults, 12) + assignable = newRelation(packedResults, 18) + comparableRelation = newRelation(packedResults, 24) + return subtype, strictSubtype, assignable, comparableRelation, identity +} + +func newRelation(packedResults *packedRelationCache, packedShift uint8) *Relation { + return &Relation{packedResults: packedResults, packedShift: packedShift} } func (r *Relation) get(key CacheHashKey) RelationComparisonResult { - if r.packedResults != nil { - return RelationComparisonResult(r.packedResults.results[key]>>r.packedShift) & relationComparisonResultMask - } - return r.results[key] + return RelationComparisonResult(r.packedResults.results[key]>>r.packedShift) & relationComparisonResultMask } func (r *Relation) set(key CacheHashKey, result RelationComparisonResult) { - if r.packedResults != nil { - r.setPacked(key, result) - return - } - if r.results == nil { - r.results = make(map[CacheHashKey]RelationComparisonResult) - } - r.results[key] = result + r.setPacked(key, result) } const relationComparisonResultMask = RelationComparisonResultSucceeded | @@ -141,37 +133,45 @@ const relationComparisonResultMask = RelationComparisonResultSucceeded | RelationComparisonResultReportsUnreliable | RelationComparisonResultComplexityOverflow +const ( + packedRelationResultBits = 6 + packedRelationCount = 5 + packedRelationResultsMask = 1<<(packedRelationResultBits*packedRelationCount) - 1 +) + func (r *Relation) setPacked(key CacheHashKey, result RelationComparisonResult) { if r.packedResults.results == nil { - r.packedResults.results = make(map[CacheHashKey]uint16) + r.packedResults.results = make(map[CacheHashKey]uint32) } packed := r.packedResults.results[key] - packed = r.setPackedResult(packed, result, false) - if result&RelationComparisonResultSucceeded != 0 && r.implySuccess != nil { - packed = r.implySuccess.setPackedResult(packed, result, true) + shiftedResult := uint32(result&relationComparisonResultMask) << r.packedShift + packed = r.packedResults.setPackedResult(packed, shiftedResult, r.packedShift, false) + if result&RelationComparisonResultSucceeded != 0 { + for implied, shift := shiftedResult<>packedRelationResultBits, int(r.packedShift)-packedRelationResultBits; implied != 0; implied, shift = implied>>packedRelationResultBits, shift-packedRelationResultBits { + packed = r.packedResults.setPackedResult(packed, implied, uint8(shift), true) + } } r.packedResults.results[key] = packed } -func (r *Relation) setPackedResult(packed uint16, result RelationComparisonResult, onlyIfEmpty bool) uint16 { - mask := uint16(relationComparisonResultMask) << r.packedShift +func (c *packedRelationCache) setPackedResult(packed uint32, shiftedResult uint32, shift uint8, onlyIfEmpty bool) uint32 { + mask := uint32(relationComparisonResultMask) << shift if onlyIfEmpty && packed&mask != 0 { return packed } if packed&mask == 0 { - r.count++ + c.counts[shift/packedRelationResultBits]++ } - return packed&^mask | uint16(result&relationComparisonResultMask)<= sourceRank { + expected = result + } + assert.Equal(t, relation.get(key), expected) + } + }) + + t.Run("failure/"+strconv.Itoa(sourceRank), func(t *testing.T) { + t.Parallel() + + subtype, strictSubtype, assignable, comparableRelation, identity := newRelations() + relations := []*Relation{identity, strictSubtype, subtype, assignable, comparableRelation} + key := CacheHashKey{Lo: 1} + result := RelationComparisonResultFailed | RelationComparisonResultReportsUnmeasurable + relations[sourceRank].set(key, result) + for rank, relation := range relations { + expected := RelationComparisonResultNone + if rank <= sourceRank { + expected = result + } + assert.Equal(t, relation.get(key), expected) + } + }) + } +} + func BenchmarkSubtypeRelationCacheStorage(b *testing.B) { const ( subtypeSize = 277_973 @@ -70,11 +135,18 @@ func BenchmarkSubtypeRelationCacheStorage(b *testing.B) { }{ { name: "independent", - new: func() (*Relation, *Relation) { return &Relation{}, &Relation{} }, + new: func() (*Relation, *Relation) { + subtype, _, _, _, _ := newRelations() + strictSubtype, _, _, _, _ := newRelations() + return subtype, strictSubtype + }, }, { name: "packed", - new: newSubtypeRelationPair, + new: func() (*Relation, *Relation) { + subtype, strictSubtype, _, _, _ := newRelations() + return subtype, strictSubtype + }, }, } { b.Run(test.name, func(b *testing.B) { From 8e8e695ce9a3ab18702dc3e672d3bfdabfe92be2 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 14 Oct 2026 03:03:57 +0000 Subject: [PATCH 3/4] Lazily propagate results. --- tsc/internal/checker/relater.go | 64 +++++++++++++------------ tsc/internal/checker/relater_test.go | 71 ++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 31 deletions(-) diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index 3cf46379470d8..2de6fdb1bfff3 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -120,11 +120,34 @@ func newRelation(packedResults *packedRelationCache, packedShift uint8) *Relatio } func (r *Relation) get(key CacheHashKey) RelationComparisonResult { - return RelationComparisonResult(r.packedResults.results[key]>>r.packedShift) & relationComparisonResultMask + packed := r.packedResults.results[key] + if packed == 0 { + return RelationComparisonResultNone + } + result := RelationComparisonResult(packed>>r.packedShift) & relationComparisonResultMask + if result != RelationComparisonResultNone { + return result + } + for implied := packed >> (r.packedShift + packedRelationResultBits); implied != 0; implied >>= packedRelationResultBits { + result = RelationComparisonResult(implied) & relationComparisonResultMask + if result&RelationComparisonResultFailed != 0 && result&RelationComparisonResultOverflow == 0 { + return r.setPacked(key, packed, result) + } + } + for implied := packed & (uint32(1)<>= packedRelationResultBits { + result = RelationComparisonResult(implied) & relationComparisonResultMask + if result&RelationComparisonResultSucceeded != 0 { + return r.setPacked(key, packed, result) + } + } + return RelationComparisonResultNone } func (r *Relation) set(key CacheHashKey, result RelationComparisonResult) { - r.setPacked(key, result) + if r.packedResults.results == nil { + r.packedResults.results = make(map[CacheHashKey]uint32) + } + r.setPacked(key, r.packedResults.results[key], result) } const relationComparisonResultMask = RelationComparisonResultSucceeded | @@ -134,40 +157,19 @@ const relationComparisonResultMask = RelationComparisonResultSucceeded | RelationComparisonResultComplexityOverflow const ( - packedRelationResultBits = 6 - packedRelationCount = 5 - packedRelationResultsMask = 1<<(packedRelationResultBits*packedRelationCount) - 1 + packedRelationResultBits = 6 + packedRelationCount = 5 ) -func (r *Relation) setPacked(key CacheHashKey, result RelationComparisonResult) { - if r.packedResults.results == nil { - r.packedResults.results = make(map[CacheHashKey]uint32) - } - packed := r.packedResults.results[key] +func (r *Relation) setPacked(key CacheHashKey, packed uint32, result RelationComparisonResult) RelationComparisonResult { + result &= relationComparisonResultMask shiftedResult := uint32(result&relationComparisonResultMask) << r.packedShift - packed = r.packedResults.setPackedResult(packed, shiftedResult, r.packedShift, false) - if result&RelationComparisonResultSucceeded != 0 { - for implied, shift := shiftedResult<>packedRelationResultBits, int(r.packedShift)-packedRelationResultBits; implied != 0; implied, shift = implied>>packedRelationResultBits, shift-packedRelationResultBits { - packed = r.packedResults.setPackedResult(packed, implied, uint8(shift), true) - } - } - r.packedResults.results[key] = packed -} - -func (c *packedRelationCache) setPackedResult(packed uint32, shiftedResult uint32, shift uint8, onlyIfEmpty bool) uint32 { - mask := uint32(relationComparisonResultMask) << shift - if onlyIfEmpty && packed&mask != 0 { - return packed - } + mask := uint32(relationComparisonResultMask) << r.packedShift if packed&mask == 0 { - c.counts[shift/packedRelationResultBits]++ + r.packedResults.counts[r.packedShift/packedRelationResultBits]++ } - return packed&^mask | shiftedResult + r.packedResults.results[key] = packed&^mask | shiftedResult + return result } func (r *Relation) size() int { diff --git a/tsc/internal/checker/relater_test.go b/tsc/internal/checker/relater_test.go index d65d4f2ddcb0c..2a3408b3c0bf7 100644 --- a/tsc/internal/checker/relater_test.go +++ b/tsc/internal/checker/relater_test.go @@ -46,6 +46,77 @@ func TestSubtypeRelationLocalResultTakesPrecedence(t *testing.T) { assert.Equal(t, strictSubtype.get(key), RelationComparisonResultSucceeded) } +func TestPackedRelationImplicationsAreLazy(t *testing.T) { + t.Parallel() + + t.Run("success", func(t *testing.T) { + t.Parallel() + + subtype, strictSubtype, assignable, comparableRelation, identity := newRelations() + key := CacheHashKey{Lo: 1} + result := RelationComparisonResultSucceeded | RelationComparisonResultReportsUnreliable + + identity.set(key, result) + assert.Equal(t, identity.size(), 1) + assert.Equal(t, strictSubtype.size(), 0) + assert.Equal(t, subtype.size(), 0) + assert.Equal(t, assignable.size(), 0) + assert.Equal(t, comparableRelation.size(), 0) + + assert.Equal(t, assignable.get(key), result) + assert.Equal(t, identity.size(), 1) + assert.Equal(t, strictSubtype.size(), 0) + assert.Equal(t, subtype.size(), 0) + assert.Equal(t, assignable.size(), 1) + assert.Equal(t, comparableRelation.size(), 0) + + assert.Equal(t, assignable.get(key), result) + assert.Equal(t, assignable.size(), 1) + }) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + + subtype, strictSubtype, assignable, comparableRelation, identity := newRelations() + key := CacheHashKey{Lo: 1} + result := RelationComparisonResultFailed | RelationComparisonResultReportsUnmeasurable + + comparableRelation.set(key, result) + assert.Equal(t, identity.size(), 0) + assert.Equal(t, strictSubtype.size(), 0) + assert.Equal(t, subtype.size(), 0) + assert.Equal(t, assignable.size(), 0) + assert.Equal(t, comparableRelation.size(), 1) + + assert.Equal(t, strictSubtype.get(key), result) + assert.Equal(t, identity.size(), 0) + assert.Equal(t, strictSubtype.size(), 1) + assert.Equal(t, subtype.size(), 0) + assert.Equal(t, assignable.size(), 0) + assert.Equal(t, comparableRelation.size(), 1) + + assert.Equal(t, strictSubtype.get(key), result) + assert.Equal(t, strictSubtype.size(), 1) + }) +} + +func TestIdentityRelationDoesNotCountDirectionalImplication(t *testing.T) { + t.Parallel() + + _, _, assignable, _, identity := newRelations() + lowerId := &Type{id: 1} + higherId := &Type{id: 2} + directionalKey, _ := getRelationKey(higherId, lowerId, IntersectionStateNone, false, false) + identityKey, _ := getRelationKey(higherId, lowerId, IntersectionStateNone, true, false) + + assert.Assert(t, directionalKey != identityKey) + assignable.set(directionalKey, RelationComparisonResultFailed) + + assert.Equal(t, identity.size(), 0) + assert.Equal(t, identity.get(identityKey), RelationComparisonResultNone) + assert.Equal(t, identity.size(), 0) +} + func TestIndependentRelationCache(t *testing.T) { t.Parallel() From 9c1b835ef97b9fcc9782a84ba06656c3e5233294 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 14 Oct 2026 05:58:16 +0000 Subject: [PATCH 4/4] Get rid of loops. Just check bit patterns. --- tsc/internal/checker/relater.go | 54 +++++++++++++++++++--------- tsc/internal/checker/relater_test.go | 21 +++++++++++ 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index 2de6fdb1bfff3..0d33ff9e318d7 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -1,6 +1,7 @@ package checker import ( + "math/bits" "slices" "strconv" "strings" @@ -64,12 +65,18 @@ const ( type RelationComparisonResult uint32 const ( + relationComparisonResultSucceededBitShift = 0 + relationComparisonResultFailedBitShift = 1 + relationComparisonResultReportsUnmeasurableBitShift = 3 + relationComparisonResultReportsUnreliableBitShift = 4 + relationComparisonResultOverflowBitShift = 5 + RelationComparisonResultNone RelationComparisonResult = 0 - RelationComparisonResultSucceeded RelationComparisonResult = 1 << 0 - RelationComparisonResultFailed RelationComparisonResult = 1 << 1 - RelationComparisonResultReportsUnmeasurable RelationComparisonResult = 1 << 3 - RelationComparisonResultReportsUnreliable RelationComparisonResult = 1 << 4 - RelationComparisonResultComplexityOverflow RelationComparisonResult = 1 << 5 + RelationComparisonResultSucceeded RelationComparisonResult = 1 << relationComparisonResultSucceededBitShift + RelationComparisonResultFailed RelationComparisonResult = 1 << relationComparisonResultFailedBitShift + RelationComparisonResultReportsUnmeasurable RelationComparisonResult = 1 << relationComparisonResultReportsUnmeasurableBitShift + RelationComparisonResultReportsUnreliable RelationComparisonResult = 1 << relationComparisonResultReportsUnreliableBitShift + RelationComparisonResultComplexityOverflow RelationComparisonResult = 1 << relationComparisonResultOverflowBitShift RelationComparisonResultReportsMask = RelationComparisonResultReportsUnmeasurable | RelationComparisonResultReportsUnreliable RelationComparisonResultOverflow = RelationComparisonResultComplexityOverflow ) @@ -128,17 +135,28 @@ func (r *Relation) get(key CacheHashKey) RelationComparisonResult { if result != RelationComparisonResultNone { return result } - for implied := packed >> (r.packedShift + packedRelationResultBits); implied != 0; implied >>= packedRelationResultBits { - result = RelationComparisonResult(implied) & relationComparisonResultMask - if result&RelationComparisonResultFailed != 0 && result&RelationComparisonResultOverflow == 0 { - return r.setPacked(key, packed, result) - } - } - for implied := packed & (uint32(1)<>= packedRelationResultBits { - result = RelationComparisonResult(implied) & relationComparisonResultMask - if result&RelationComparisonResultSucceeded != 0 { - return r.setPacked(key, packed, result) - } + + // A failure in a laxer relation implies failure in this relation. Align each + // slot's failure and overflow bits with its respective slot marker, then keep + // failures that did not result from an overflow. The mask excludes this and + // all stricter relations. + laxerSlots := packedRelationSlotMask &^ (uint32(1)<<(r.packedShift+packedRelationResultBits) - 1) + if failedSlots := ((packed >> relationComparisonResultFailedBitShift) &^ (packed >> relationComparisonResultOverflowBitShift)) & laxerSlots; failedSlots != 0 { + // The lowest set marker is the nearest laxer relation with a usable failure. + shift := bits.TrailingZeros32(failedSlots) + result = RelationComparisonResult(packed>>shift) & relationComparisonResultMask + return r.setPacked(key, packed, result) + } + + // A success in a stricter relation implies success in this relation. Align + // each slot's success bit with its respective slot marker, then exclude this + // and all laxer relations. + stricterSlots := packedRelationSlotMask & (uint32(1)<> relationComparisonResultSucceededBitShift) & stricterSlots; succeededSlots != 0 { + // Prefer the strictest available success, matching the previous scan order. + shift := bits.TrailingZeros32(succeededSlots) + result = RelationComparisonResult(packed>>shift) & relationComparisonResultMask + return r.setPacked(key, packed, result) } return RelationComparisonResultNone } @@ -156,9 +174,13 @@ const relationComparisonResultMask = RelationComparisonResultSucceeded | RelationComparisonResultReportsUnreliable | RelationComparisonResultComplexityOverflow +// Each map value contains one six-bit result slot per relation, ordered from +// strictest to laxest: identity, strict subtype, subtype, assignable, comparable. const ( packedRelationResultBits = 6 packedRelationCount = 5 + // Marks the low bit of every slot, which is also its success bit. + packedRelationSlotMask = 1 | 1<<6 | 1<<12 | 1<<18 | 1<<24 ) func (r *Relation) setPacked(key CacheHashKey, packed uint32, result RelationComparisonResult) RelationComparisonResult { diff --git a/tsc/internal/checker/relater_test.go b/tsc/internal/checker/relater_test.go index 2a3408b3c0bf7..cdfa3fe224a38 100644 --- a/tsc/internal/checker/relater_test.go +++ b/tsc/internal/checker/relater_test.go @@ -100,6 +100,27 @@ func TestPackedRelationImplicationsAreLazy(t *testing.T) { }) } +func TestPackedRelationImplicationPrecedence(t *testing.T) { + t.Parallel() + + subtype, strictSubtype, _, comparableRelation, identity := newRelations() + failureKey := CacheHashKey{Lo: 1} + successKey := CacheHashKey{Lo: 2} + overflowKey := CacheHashKey{Lo: 3} + + subtype.set(failureKey, RelationComparisonResultFailed|RelationComparisonResultReportsUnreliable) + comparableRelation.set(failureKey, RelationComparisonResultFailed|RelationComparisonResultReportsUnmeasurable) + assert.Equal(t, strictSubtype.get(failureKey), RelationComparisonResultFailed|RelationComparisonResultReportsUnreliable) + + identity.set(successKey, RelationComparisonResultSucceeded|RelationComparisonResultReportsUnreliable) + subtype.set(successKey, RelationComparisonResultSucceeded|RelationComparisonResultReportsUnmeasurable) + assert.Equal(t, comparableRelation.get(successKey), RelationComparisonResultSucceeded|RelationComparisonResultReportsUnreliable) + + subtype.set(overflowKey, RelationComparisonResultFailed|RelationComparisonResultComplexityOverflow) + comparableRelation.set(overflowKey, RelationComparisonResultFailed|RelationComparisonResultReportsUnmeasurable) + assert.Equal(t, strictSubtype.get(overflowKey), RelationComparisonResultFailed|RelationComparisonResultReportsUnmeasurable) +} + func TestIdentityRelationDoesNotCountDirectionalImplication(t *testing.T) { t.Parallel()