From 1ea998015e5fce3cb14885cfe09ec8ad96a8f203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 12 Nov 2025 15:23:29 -0800 Subject: [PATCH 1/4] optimize event emission only convert arguments, do not transfer (avoid unnecessary copies) --- bbq/compiler/compiler.go | 14 +++++++++- bbq/compiler/compiler_test.go | 2 +- interpreter/interpreter_statement.go | 2 +- interpreter/misc_test.go | 38 ++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index 596c563cc5..1c55013173 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -1619,7 +1619,19 @@ func (c *Compiler[_, _]) VisitEmitStatement(statement *ast.EmitStatement) (_ str invocationExpression := statement.InvocationExpression arguments := invocationExpression.Arguments invocationTypes := c.DesugaredElaboration.InvocationExpressionTypes(invocationExpression) - c.compileArguments(arguments, invocationTypes) + + // Instead of compiling arguments as usual (via compileArguments), + // only convert the arguments and don't transfer them. + + for index, argument := range arguments { + c.compileExpression(argument.Expression) + + parameterType := invocationTypes.ParameterTypes[index] + parameterTypeIndex := c.getOrAddType(parameterType) + c.emit(opcode.InstructionConvert{ + Type: parameterTypeIndex, + }) + } argCount := len(arguments) if argCount >= math.MaxUint16 { diff --git a/bbq/compiler/compiler_test.go b/bbq/compiler/compiler_test.go index 3ebbd96a1f..e8a3da2efc 100644 --- a/bbq/compiler/compiler_test.go +++ b/bbq/compiler/compiler_test.go @@ -1336,7 +1336,7 @@ func TestCompileEmit(t *testing.T) { opcode.InstructionStatement{}, // x opcode.InstructionGetLocal{Local: xIndex}, - opcode.InstructionTransferAndConvert{Type: 1}, + opcode.InstructionConvert{Type: 1}, // emit opcode.InstructionEmitEvent{ Type: 2, diff --git a/interpreter/interpreter_statement.go b/interpreter/interpreter_statement.go index 66c3c1344c..573470bf7f 100644 --- a/interpreter/interpreter_statement.go +++ b/interpreter/interpreter_statement.go @@ -403,7 +403,7 @@ func (interpreter *Interpreter) VisitEmitStatement(statement *ast.EmitStatement) argumentType := argumentTypes[i] parameterType := parameterTypes[i] - eventFields[i] = TransferAndConvert( + eventFields[i] = ConvertAndBoxWithValidation( interpreter, value, argumentType, diff --git a/interpreter/misc_test.go b/interpreter/misc_test.go index 7fb33446bc..c1cded3705 100644 --- a/interpreter/misc_test.go +++ b/interpreter/misc_test.go @@ -7340,6 +7340,44 @@ func TestInterpretEmitEvent(t *testing.T) { ) } +func BenchmarkEmit(b *testing.B) { + + inter, err := parseCheckAndPrepareWithOptions( + b, + ` + event Foo(ints: [Int]) + + fun test() { + var i = 0 + while i < 1000 { + emit Foo(ints: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + i = i + 1 + } + } + `, + ParseCheckAndInterpretOptions{ + InterpreterConfig: &interpreter.Config{ + OnEventEmitted: func( + _ interpreter.ValueExportContext, + _ *sema.CompositeType, + _ []interpreter.Value, + ) error { + return nil + }, + }, + }, + ) + require.NoError(b, err) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, err := inter.Invoke("test") + require.NoError(b, err) + } +} + func TestInterpretReferenceEventParameter(t *testing.T) { t.Parallel() From 6f3e24f2a828831e8474dc03f6fca0a119411ad0 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 13 Nov 2025 12:51:57 -0800 Subject: [PATCH 2/4] Cache static-authorization to sema-access conversion results --- bbq/vm/context.go | 22 +++++++++- interpreter/account_test.go | 38 +++++++++-------- interpreter/interface.go | 53 +++++++++++++----------- interpreter/interpreter.go | 15 ++----- interpreter/statictype.go | 8 +++- interpreter/statictype_test.go | 4 ++ interpreter/value_ephemeral_reference.go | 8 +++- interpreter/value_storage_reference.go | 5 ++- 8 files changed, 97 insertions(+), 56 deletions(-) diff --git a/bbq/vm/context.go b/bbq/vm/context.go index a218c23762..9053802e58 100644 --- a/bbq/vm/context.go +++ b/bbq/vm/context.go @@ -55,7 +55,8 @@ type Context struct { // This cache-alike is maintained per execution. // TODO: Re-use the conversions from the compiler. // TODO: Maybe extend/share this between executions. - semaTypeCache map[sema.TypeID]sema.Type + semaTypeCache map[sema.TypeID]sema.Type + semaAccessCache map[interpreter.Authorization]sema.Access // linkedGlobalsCache is a local cache-alike that is being used to hold already linked imports. linkedGlobalsCache map[common.Location]LinkedGlobals @@ -531,3 +532,22 @@ func (c *Context) GetEntitlementMapType( func (c *Context) LocationRange() interpreter.LocationRange { return c.getLocationRange() } + +func (c *Context) SemaAccessFromStaticAuthorization(auth interpreter.Authorization) (sema.Access, error) { + semaAccess, ok := c.semaAccessCache[auth] + if ok { + return semaAccess, nil + } + + semaAccess, err := interpreter.ConvertStaticAuthorizationToSemaAccess(auth, c) + if err != nil { + return nil, err + } + + if c.semaAccessCache == nil { + c.semaAccessCache = make(map[interpreter.Authorization]sema.Access) + } + c.semaAccessCache[auth] = semaAccess + + return semaAccess, nil +} diff --git a/interpreter/account_test.go b/interpreter/account_test.go index 4d6dc59e1e..5c2513f512 100644 --- a/interpreter/account_test.go +++ b/interpreter/account_test.go @@ -417,64 +417,68 @@ type NoOpReferenceCreationContext struct{} var _ interpreter.ReferenceCreationContext = NoOpReferenceCreationContext{} -func (n NoOpReferenceCreationContext) ClearReferencedResourceKindedValues(_ atree.ValueID) { +func (NoOpReferenceCreationContext) ClearReferencedResourceKindedValues(_ atree.ValueID) { // NO-OP } -func (n NoOpReferenceCreationContext) ReferencedResourceKindedValues(_ atree.ValueID) map[*interpreter.EphemeralReferenceValue]struct{} { +func (NoOpReferenceCreationContext) ReferencedResourceKindedValues(_ atree.ValueID) map[*interpreter.EphemeralReferenceValue]struct{} { // NO-OP return nil } -func (n NoOpReferenceCreationContext) MaybeTrackReferencedResourceKindedValue(_ *interpreter.EphemeralReferenceValue) { +func (NoOpReferenceCreationContext) MaybeTrackReferencedResourceKindedValue(_ *interpreter.EphemeralReferenceValue) { // NO-OP } -func (n NoOpReferenceCreationContext) MeterMemory(_ common.MemoryUsage) error { +func (NoOpReferenceCreationContext) MeterMemory(_ common.MemoryUsage) error { // NO-OP return nil } -func (n NoOpReferenceCreationContext) MeterComputation(_ common.ComputationUsage) error { +func (NoOpReferenceCreationContext) MeterComputation(_ common.ComputationUsage) error { // NO-OP return nil } -func (n NoOpReferenceCreationContext) ReadStored(_ common.Address, _ common.StorageDomain, _ interpreter.StorageMapKey) interpreter.Value { +func (NoOpReferenceCreationContext) ReadStored(_ common.Address, _ common.StorageDomain, _ interpreter.StorageMapKey) interpreter.Value { // NO-OP return nil } -func (n NoOpReferenceCreationContext) GetEntitlementType(_ interpreter.TypeID) (*sema.EntitlementType, error) { +func (NoOpReferenceCreationContext) GetEntitlementType(_ interpreter.TypeID) (*sema.EntitlementType, error) { // NO-OP return nil, nil } -func (n NoOpReferenceCreationContext) GetEntitlementMapType(_ interpreter.TypeID) (*sema.EntitlementMapType, error) { +func (NoOpReferenceCreationContext) GetEntitlementMapType(_ interpreter.TypeID) (*sema.EntitlementMapType, error) { // NO-OP return nil, nil } -func (n NoOpReferenceCreationContext) GetInterfaceType(_ common.Location, _ string, _ interpreter.TypeID) (*sema.InterfaceType, error) { +func (NoOpReferenceCreationContext) GetInterfaceType(_ common.Location, _ string, _ interpreter.TypeID) (*sema.InterfaceType, error) { // NO-OP return nil, nil } -func (n NoOpReferenceCreationContext) GetCompositeType(_ common.Location, _ string, _ interpreter.TypeID) (*sema.CompositeType, error) { +func (NoOpReferenceCreationContext) GetCompositeType(_ common.Location, _ string, _ interpreter.TypeID) (*sema.CompositeType, error) { // NO-OP return nil, nil } -func (n NoOpReferenceCreationContext) IsTypeInfoRecovered(_ common.Location) bool { +func (NoOpReferenceCreationContext) IsTypeInfoRecovered(_ common.Location) bool { // NO-OP return false } -func (n NoOpReferenceCreationContext) SemaTypeFromStaticType(_ interpreter.StaticType) sema.Type { +func (NoOpReferenceCreationContext) SemaTypeFromStaticType(_ interpreter.StaticType) sema.Type { // NO-OP return nil } +func (NoOpReferenceCreationContext) SemaAccessFromStaticAuthorization(interpreter.Authorization) (sema.Access, error) { + panic(errors.NewUnreachableError()) +} + type NoOpFunctionCreationContext struct { //Just to make the compiler happy interpreter.ResourceDestructionContext @@ -482,28 +486,28 @@ type NoOpFunctionCreationContext struct { var _ interpreter.FunctionCreationContext = NoOpFunctionCreationContext{} -func (n NoOpFunctionCreationContext) ClearReferencedResourceKindedValues(_ atree.ValueID) { +func (NoOpFunctionCreationContext) ClearReferencedResourceKindedValues(_ atree.ValueID) { // NO-OP } -func (n NoOpFunctionCreationContext) ReferencedResourceKindedValues( +func (NoOpFunctionCreationContext) ReferencedResourceKindedValues( _ atree.ValueID, ) map[*interpreter.EphemeralReferenceValue]struct{} { // NO-OP return nil } -func (n NoOpFunctionCreationContext) CheckInvalidatedResourceOrResourceReference( +func (NoOpFunctionCreationContext) CheckInvalidatedResourceOrResourceReference( _ interpreter.Value, ) { // NO-OP } -func (n NoOpFunctionCreationContext) MaybeTrackReferencedResourceKindedValue(_ *interpreter.EphemeralReferenceValue) { +func (NoOpFunctionCreationContext) MaybeTrackReferencedResourceKindedValue(_ *interpreter.EphemeralReferenceValue) { // NO-OP } -func (n NoOpFunctionCreationContext) MeterMemory(_ common.MemoryUsage) error { +func (NoOpFunctionCreationContext) MeterMemory(_ common.MemoryUsage) error { // NO-OP return nil } diff --git a/interpreter/interface.go b/interpreter/interface.go index ac1c537fc1..e81386f68b 100644 --- a/interpreter/interface.go +++ b/interpreter/interface.go @@ -30,6 +30,7 @@ type TypeConverter interface { common.MemoryGauge StaticTypeConversionHandler SemaTypeFromStaticType(staticType StaticType) sema.Type + SemaAccessFromStaticAuthorization(auth Authorization) (sema.Access, error) } var _ TypeConverter = &Interpreter{} @@ -538,98 +539,102 @@ type NoOpStringContext struct { var _ ValueStringContext = NoOpStringContext{} -func (ctx NoOpStringContext) MeterMemory(_ common.MemoryUsage) error { +func (NoOpStringContext) MeterMemory(_ common.MemoryUsage) error { return nil } -func (ctx NoOpStringContext) MeterComputation(_ common.ComputationUsage) error { +func (NoOpStringContext) MeterComputation(_ common.ComputationUsage) error { return nil } -func (ctx NoOpStringContext) WithContainerMutationPrevention(_ atree.ValueID, f func()) { +func (NoOpStringContext) WithContainerMutationPrevention(_ atree.ValueID, f func()) { f() } -func (ctx NoOpStringContext) ValidateContainerMutation(_ atree.ValueID) { +func (NoOpStringContext) ValidateContainerMutation(_ atree.ValueID) { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) EnforceNotResourceDestruction(_ atree.ValueID) { +func (NoOpStringContext) EnforceNotResourceDestruction(_ atree.ValueID) { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) ReadStored(_ common.Address, _ common.StorageDomain, _ StorageMapKey) Value { +func (NoOpStringContext) ReadStored(_ common.Address, _ common.StorageDomain, _ StorageMapKey) Value { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) WriteStored(_ common.Address, _ common.StorageDomain, _ StorageMapKey, _ Value) (existed bool) { +func (NoOpStringContext) WriteStored(_ common.Address, _ common.StorageDomain, _ StorageMapKey, _ Value) (existed bool) { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) Storage() Storage { +func (NoOpStringContext) Storage() Storage { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) MaybeValidateAtreeValue(_ atree.Value) { +func (NoOpStringContext) MaybeValidateAtreeValue(_ atree.Value) { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) MaybeValidateAtreeStorage() { +func (NoOpStringContext) MaybeValidateAtreeStorage() { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) MaybeTrackReferencedResourceKindedValue(_ *EphemeralReferenceValue) { +func (NoOpStringContext) MaybeTrackReferencedResourceKindedValue(_ *EphemeralReferenceValue) { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) ClearReferencedResourceKindedValues(_ atree.ValueID) { +func (NoOpStringContext) ClearReferencedResourceKindedValues(_ atree.ValueID) { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) ReferencedResourceKindedValues(_ atree.ValueID) map[*EphemeralReferenceValue]struct{} { +func (NoOpStringContext) ReferencedResourceKindedValues(_ atree.ValueID) map[*EphemeralReferenceValue]struct{} { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) OnResourceOwnerChange(_ *CompositeValue, _ common.Address, _ common.Address) { +func (NoOpStringContext) OnResourceOwnerChange(_ *CompositeValue, _ common.Address, _ common.Address) { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) RecordStorageMutation() { +func (NoOpStringContext) RecordStorageMutation() { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) StorageMutatedDuringIteration() bool { +func (NoOpStringContext) StorageMutatedDuringIteration() bool { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) InStorageIteration() bool { +func (NoOpStringContext) InStorageIteration() bool { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) SetInStorageIteration(_ bool) { +func (NoOpStringContext) SetInStorageIteration(_ bool) { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) GetEntitlementType(_ TypeID) (*sema.EntitlementType, error) { +func (NoOpStringContext) GetEntitlementType(_ TypeID) (*sema.EntitlementType, error) { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) GetEntitlementMapType(_ TypeID) (*sema.EntitlementMapType, error) { +func (NoOpStringContext) GetEntitlementMapType(_ TypeID) (*sema.EntitlementMapType, error) { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) GetInterfaceType(_ common.Location, _ string, _ TypeID) (*sema.InterfaceType, error) { +func (NoOpStringContext) GetInterfaceType(_ common.Location, _ string, _ TypeID) (*sema.InterfaceType, error) { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) GetCompositeType(_ common.Location, _ string, _ TypeID) (*sema.CompositeType, error) { +func (NoOpStringContext) GetCompositeType(_ common.Location, _ string, _ TypeID) (*sema.CompositeType, error) { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) IsTypeInfoRecovered(_ common.Location) bool { +func (NoOpStringContext) IsTypeInfoRecovered(_ common.Location) bool { panic(errors.NewUnreachableError()) } -func (ctx NoOpStringContext) SemaTypeFromStaticType(_ StaticType) sema.Type { +func (NoOpStringContext) SemaTypeFromStaticType(_ StaticType) sema.Type { + panic(errors.NewUnreachableError()) +} + +func (NoOpStringContext) SemaAccessFromStaticAuthorization(Authorization) (sema.Access, error) { panic(errors.NewUnreachableError()) } diff --git a/interpreter/interpreter.go b/interpreter/interpreter.go index 38535aca01..ac54b915e6 100644 --- a/interpreter/interpreter.go +++ b/interpreter/interpreter.go @@ -5187,17 +5187,6 @@ func (interpreter *Interpreter) GetEntitlementMapType(typeID common.TypeID) (*se return ty, nil } -func MustConvertStaticAuthorizationToSemaAccess( - handler StaticAuthorizationConversionHandler, - auth Authorization, -) sema.Access { - access, err := ConvertStaticAuthorizationToSemaAccess(auth, handler) - if err != nil { - panic(err) - } - return access -} - func (interpreter *Interpreter) getElaboration(location common.Location) *sema.Elaboration { // Ensure the program for this location is loaded, @@ -6400,6 +6389,10 @@ func (interpreter *Interpreter) MaybeUpdateStorageReferenceMemberReceiver( return member } +func (interpreter *Interpreter) SemaAccessFromStaticAuthorization(auth Authorization) (sema.Access, error) { + return ConvertStaticAuthorizationToSemaAccess(auth, interpreter) +} + func StorageReference( context ValueStaticTypeContext, storageReference *StorageReferenceValue, diff --git a/interpreter/statictype.go b/interpreter/statictype.go index 290a8563fa..2b7d99bc0b 100644 --- a/interpreter/statictype.go +++ b/interpreter/statictype.go @@ -1184,6 +1184,12 @@ func ConvertSemaTransactionToStaticTransactionType( ) } +// ConvertStaticAuthorizationToSemaAccess converts authorization of static-types +// to the sema-type representation of the same. +// +// **IMPORTANT**: Do not use this function directly. Instead, use the +// `SemaAccessFromStaticAuthorization` method of the `TypeConverter` interface, +// since it will cache and re-use the conversion results. func ConvertStaticAuthorizationToSemaAccess( auth Authorization, handler StaticAuthorizationConversionHandler, @@ -1370,7 +1376,7 @@ func ConvertStaticToSemaType( return nil, err } - access, err := ConvertStaticAuthorizationToSemaAccess(t.Authorization, context) + access, err := context.SemaAccessFromStaticAuthorization(t.Authorization) if err != nil { return nil, err diff --git a/interpreter/statictype_test.go b/interpreter/statictype_test.go index a4f23f7463..3d376a8004 100644 --- a/interpreter/statictype_test.go +++ b/interpreter/statictype_test.go @@ -1776,6 +1776,10 @@ func (s staticTypeConversionHandler) SemaTypeFromStaticType(staticType StaticTyp return MustConvertStaticToSemaType(staticType, s) } +func (s staticTypeConversionHandler) SemaAccessFromStaticAuthorization(auth Authorization) (sema.Access, error) { + return ConvertStaticAuthorizationToSemaAccess(auth, s) +} + func TestIntersectionStaticType_ID(t *testing.T) { t.Parallel() diff --git a/interpreter/value_ephemeral_reference.go b/interpreter/value_ephemeral_reference.go index b55cbdda55..50c30b5ba8 100644 --- a/interpreter/value_ephemeral_reference.go +++ b/interpreter/value_ephemeral_reference.go @@ -186,10 +186,16 @@ func (v *EphemeralReferenceValue) GetTypeKey(context MemberAccessibleContext, ke self := v.Value if selfComposite, isComposite := self.(*CompositeValue); isComposite { + + semaAccess, err := context.SemaAccessFromStaticAuthorization(v.Authorization) + if err != nil { + panic(err) + } + return selfComposite.getTypeKey( context, key, - MustConvertStaticAuthorizationToSemaAccess(context, v.Authorization), + semaAccess, ) } diff --git a/interpreter/value_storage_reference.go b/interpreter/value_storage_reference.go index c151351876..a2d33c943a 100644 --- a/interpreter/value_storage_reference.go +++ b/interpreter/value_storage_reference.go @@ -283,7 +283,10 @@ func (v *StorageReferenceValue) GetTypeKey( self := v.mustReferencedValue(context) if selfComposite, isComposite := self.(*CompositeValue); isComposite { - access := MustConvertStaticAuthorizationToSemaAccess(context, v.Authorization) + access, err := context.SemaAccessFromStaticAuthorization(v.Authorization) + if err != nil { + panic(err) + } return selfComposite.getTypeKey( context, key, From b05cdb377d47e8d147c27741e98829c036e9fabd Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 13 Nov 2025 15:10:39 -0800 Subject: [PATCH 3/4] Always return from each subtype check --- sema/type.go | 74 +++++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/sema/type.go b/sema/type.go index 8bd204edd4..88e6aee8ac 100644 --- a/sema/type.go +++ b/sema/type.go @@ -7874,6 +7874,8 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { IsSubsetOf(intersectionSubtype.EffectiveInterfaceConformanceSet()) } + return false + case ConformingType: // A type `T` // is a subtype of an intersection type `AnyResource{Us}` / `AnyStruct{Us}` / `Any{Us}`: @@ -7890,51 +7892,53 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { IsSubsetOf(typedSubType.EffectiveInterfaceConformanceSet()) } - default: - // Supertype (intersection) has a non-Any* legacy type + return false + } - switch typedSubType := subType.(type) { - case *IntersectionType: + // Supertype (intersection) has a non-Any* legacy type - // An intersection type `T{Us}` - // is a subtype of an intersection type `V{Ws}`: + switch subType { + case AnyResourceType, AnyStructType, AnyType: + // A type `T` + // is a subtype of an intersection type `AnyResource{Vs}` / `AnyStruct{Vs}` / `Any{Vs}`: + // not statically. + return false + } - intersectionSubType := typedSubType.LegacyType //nolint:staticcheck - switch intersectionSubType { - case nil, AnyResourceType, AnyStructType, AnyType: - // When `T == AnyResource || T == AnyStruct || T == Any`: - // not statically. - return false - } + switch typedSubType := subType.(type) { + case *IntersectionType: - if intersectionSubType, ok := intersectionSubType.(*CompositeType); ok { - // When `T != AnyResource && T != AnyStructType && T != Any`: if `T == V`. - // - // `Us` and `Ws` do *not* have to be subsets: - // The owner may freely restrict and unrestrict. + // An intersection type `T{Us}` + // is a subtype of an intersection type `V{Ws}`: - return intersectionSubType == intersectionSuperType - } + intersectionSubType := typedSubType.LegacyType //nolint:staticcheck + switch intersectionSubType { + case nil, AnyResourceType, AnyStructType, AnyType: + // When `T == AnyResource || T == AnyStruct || T == Any`: + // not statically. + return false + } - case *CompositeType: - // A type `T` - // is a subtype of an intersection type `U{Vs}`: if `T <: U`. + if intersectionSubType, ok := intersectionSubType.(*CompositeType); ok { + // When `T != AnyResource && T != AnyStructType && T != Any`: if `T == V`. // - // The owner may freely restrict. + // `Us` and `Ws` do *not* have to be subsets: + // The owner may freely restrict and unrestrict. - return IsSubType(typedSubType, intersectionSuperType) + return intersectionSubType == intersectionSuperType } - switch subType { - case AnyResourceType, AnyStructType, AnyType: - // A type `T` - // is a subtype of an intersection type `AnyResource{Vs}` / `AnyStruct{Vs}` / `Any{Vs}`: - // not statically. + case *CompositeType: + // A type `T` + // is a subtype of an intersection type `U{Vs}`: if `T <: U`. + // + // The owner may freely restrict. - return false - } + return IsSubType(typedSubType, intersectionSuperType) } + return false + case *CompositeType: // NOTE: type equality case (composite type `T` is subtype of composite type `U`) @@ -7966,11 +7970,15 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { return intersectionSubType == typedSuperType } + return false + case *CompositeType: // Non-equal composite types are never subtypes of each other return false } + return false + case *InterfaceType: switch typedSubType := subType.(type) { @@ -8005,6 +8013,8 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { Contains(typedSuperType) } + return false + case ParameterizedType: if superTypeBaseType := typedSuperType.BaseType(); superTypeBaseType != nil { From 527ec2b6146ccea1a9180eae84dcab388603f58c Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 13 Nov 2025 15:32:30 -0800 Subject: [PATCH 4/4] Simplify check for parameterized types --- sema/type.go | 54 ++++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/sema/type.go b/sema/type.go index 88e6aee8ac..c71d9bd4ea 100644 --- a/sema/type.go +++ b/sema/type.go @@ -8016,47 +8016,51 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { return false case ParameterizedType: + typedSubType, ok := subType.(ParameterizedType) + if !ok { + return false + } + if superTypeBaseType := typedSuperType.BaseType(); superTypeBaseType != nil { // T <: V // if T <: V && |Us| == |Ws| && U_i <: W_i - if typedSubType, ok := subType.(ParameterizedType); ok { - if subTypeBaseType := typedSubType.BaseType(); subTypeBaseType != nil { + if subTypeBaseType := typedSubType.BaseType(); subTypeBaseType != nil { - if !IsSubType(subTypeBaseType, superTypeBaseType) { - return false - } + if !IsSubType(subTypeBaseType, superTypeBaseType) { + return false + } + + subTypeTypeArguments := typedSubType.TypeArguments() + superTypeTypeArguments := typedSuperType.TypeArguments() - subTypeTypeArguments := typedSubType.TypeArguments() - superTypeTypeArguments := typedSuperType.TypeArguments() + if len(subTypeTypeArguments) != len(superTypeTypeArguments) { + return false + } - if len(subTypeTypeArguments) != len(superTypeTypeArguments) { + for i, superTypeTypeArgument := range superTypeTypeArguments { + subTypeTypeArgument := subTypeTypeArguments[i] + if !IsSubType(subTypeTypeArgument, superTypeTypeArgument) { return false } + } - for i, superTypeTypeArgument := range superTypeTypeArguments { - subTypeTypeArgument := subTypeTypeArguments[i] - if !IsSubType(subTypeTypeArgument, superTypeTypeArgument) { - return false - } - } + return true + } + } else { + // TODO: enforce type arguments, remove this rule - return true - } + // T <: V + // if T <: V + + if baseType := typedSubType.BaseType(); baseType != nil { + return IsSubType(baseType, superType) } } - } - - // TODO: enforce type arguments, remove this rule - // T <: V - // if T <: V + return false - if typedSubType, ok := subType.(ParameterizedType); ok { - if baseType := typedSubType.BaseType(); baseType != nil { - return IsSubType(baseType, superType) - } } return false