Skip to content

Commit

Permalink
Merge branch 'master' into bastian/panic-go-runtime-errors
Browse files Browse the repository at this point in the history
  • Loading branch information
turbolent committed Mar 6, 2024
2 parents b6b1f0a + dc574a2 commit 583435c
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 62 deletions.
24 changes: 24 additions & 0 deletions runtime/interpreter/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,30 @@ func (e TypeMismatchError) Error() string {
)
}

// InvalidMemberReferenceError
type InvalidMemberReferenceError struct {
ExpectedType sema.Type
ActualType sema.Type
LocationRange
}

var _ errors.UserError = InvalidMemberReferenceError{}

func (InvalidMemberReferenceError) IsUserError() {}

func (e InvalidMemberReferenceError) Error() string {
expected, actual := sema.ErrorMessageExpectedActualTypes(
e.ExpectedType,
e.ActualType,
)

return fmt.Sprintf(
"cannot create reference: expected `%s`, got `%s`",
expected,
actual,
)
}

// InvalidPathDomainError
type InvalidPathDomainError struct {
LocationRange
Expand Down
14 changes: 14 additions & 0 deletions runtime/interpreter/interpreter_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,18 @@ func (interpreter *Interpreter) getReferenceValue(value Value, resultType sema.T
case NilValue, ReferenceValue:
// Reference to a nil, should return a nil.
// If the value is already a reference then return the same reference.
// However, we need to make sure that this reference is actually a subtype of the resultType,
// since the checker may not be aware that we are "short-circuiting" in this case

staticType := value.StaticType(interpreter)
if !interpreter.IsSubTypeOfSemaType(staticType, resultType) {
panic(InvalidMemberReferenceError{
ExpectedType: resultType,
ActualType: interpreter.MustConvertStaticToSemaType(staticType),
LocationRange: locationRange,
})
}

return value
case *SomeValue:
innerValue := interpreter.getReferenceValue(value.value, resultType, locationRange)
Expand Down Expand Up @@ -1030,13 +1042,15 @@ func (interpreter *Interpreter) maybeGetReference(
memberValue Value,
) Value {
indexExpressionTypes := interpreter.Program.Elaboration.IndexExpressionTypes(expression)

if indexExpressionTypes.ReturnReference {
expectedType := indexExpressionTypes.ResultType

locationRange := LocationRange{
Location: interpreter.Location,
HasPosition: expression,
}

// Get a reference to the value
memberValue = interpreter.getReferenceValue(memberValue, expectedType, locationRange)
}
Expand Down
119 changes: 57 additions & 62 deletions runtime/sema/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -4768,8 +4768,9 @@ type CompositeType struct {
ConstructorPurity FunctionPurity
HasComputedMembers bool
// Only applicable for native composite types
ImportableBuiltin bool
supportedEntitlements *EntitlementOrderedSet
ImportableBuiltin bool
supportedEntitlementsOnce sync.Once
supportedEntitlements *EntitlementOrderedSet
}

var _ Type = &CompositeType{}
Expand Down Expand Up @@ -4956,35 +4957,32 @@ func (t *CompositeType) MemberMap() *StringMemberOrderedMap {
return t.Members
}

func (t *CompositeType) SupportedEntitlements() (set *EntitlementOrderedSet) {
supportedEntitlements := t.supportedEntitlements
if supportedEntitlements != nil {
return supportedEntitlements
}

set = orderedmap.New[EntitlementOrderedSet](t.Members.Len())
t.Members.Foreach(func(_ string, member *Member) {
switch access := member.Access.(type) {
case *EntitlementMapAccess:
set.SetAll(access.Domain().Entitlements)
case EntitlementSetAccess:
set.SetAll(access.Entitlements)
}
})
t.EffectiveInterfaceConformanceSet().ForEach(func(it *InterfaceType) {
set.SetAll(it.SupportedEntitlements())
})
func (t *CompositeType) SupportedEntitlements() *EntitlementOrderedSet {
t.supportedEntitlementsOnce.Do(func() {
set := orderedmap.New[EntitlementOrderedSet](t.Members.Len())
t.Members.Foreach(func(_ string, member *Member) {
switch access := member.Access.(type) {
case *EntitlementMapAccess:
set.SetAll(access.Domain().Entitlements)
case EntitlementSetAccess:
set.SetAll(access.Entitlements)
}
})
t.EffectiveInterfaceConformanceSet().ForEach(func(it *InterfaceType) {
set.SetAll(it.SupportedEntitlements())
})

// attachments support at least the entitlements supported by their base,
// and we must ensure there is no recursive case
if entitlementSupportingBase, isEntitlementSupportingBase :=
t.GetBaseType().(EntitlementSupportingType); isEntitlementSupportingBase && entitlementSupportingBase != t {
// attachments support at least the entitlements supported by their base,
// and we must ensure there is no recursive case
if entitlementSupportingBase, isEntitlementSupportingBase :=
t.GetBaseType().(EntitlementSupportingType); isEntitlementSupportingBase && entitlementSupportingBase != t {

set.SetAll(entitlementSupportingBase.SupportedEntitlements())
}
set.SetAll(entitlementSupportingBase.SupportedEntitlements())
}

t.supportedEntitlements = set
return set
t.supportedEntitlements = set
})
return t.supportedEntitlements
}

func (t *CompositeType) IsResourceType() bool {
Expand Down Expand Up @@ -5659,6 +5657,7 @@ type InterfaceType struct {
ExplicitInterfaceConformances []*InterfaceType
effectiveInterfaceConformances []Conformance
effectiveInterfaceConformanceSet *InterfaceSet
supportedEntitlementsOnce sync.Once
supportedEntitlements *EntitlementOrderedSet

DefaultDestroyEvent *CompositeType
Expand Down Expand Up @@ -5768,32 +5767,29 @@ func (t *InterfaceType) MemberMap() *StringMemberOrderedMap {
return t.Members
}

func (t *InterfaceType) SupportedEntitlements() (set *EntitlementOrderedSet) {
supportedEntitlements := t.supportedEntitlements
if supportedEntitlements != nil {
return supportedEntitlements
}
func (t *InterfaceType) SupportedEntitlements() *EntitlementOrderedSet {
t.supportedEntitlementsOnce.Do(func() {
set := orderedmap.New[EntitlementOrderedSet](t.Members.Len())
t.Members.Foreach(func(_ string, member *Member) {
switch access := member.Access.(type) {
case *EntitlementMapAccess:
access.Domain().Entitlements.Foreach(func(entitlement *EntitlementType, _ struct{}) {
set.Set(entitlement, struct{}{})
})
case EntitlementSetAccess:
access.Entitlements.Foreach(func(entitlement *EntitlementType, _ struct{}) {
set.Set(entitlement, struct{}{})
})
}
})

set = orderedmap.New[EntitlementOrderedSet](t.Members.Len())
t.Members.Foreach(func(_ string, member *Member) {
switch access := member.Access.(type) {
case *EntitlementMapAccess:
access.Domain().Entitlements.Foreach(func(entitlement *EntitlementType, _ struct{}) {
set.Set(entitlement, struct{}{})
})
case EntitlementSetAccess:
access.Entitlements.Foreach(func(entitlement *EntitlementType, _ struct{}) {
set.Set(entitlement, struct{}{})
})
}
})
t.EffectiveInterfaceConformanceSet().ForEach(func(it *InterfaceType) {
set.SetAll(it.SupportedEntitlements())
})

t.EffectiveInterfaceConformanceSet().ForEach(func(it *InterfaceType) {
set.SetAll(it.SupportedEntitlements())
t.supportedEntitlements = set
})

t.supportedEntitlements = set
return set
return t.supportedEntitlements
}

func (t *InterfaceType) Map(_ common.MemoryGauge, _ map[*TypeParameter]*TypeParameter, f func(Type) Type) Type {
Expand Down Expand Up @@ -8146,6 +8142,7 @@ type IntersectionType struct {
effectiveIntersectionSetOnce sync.Once
memberResolvers map[string]MemberResolver
memberResolversOnce sync.Once
supportedEntitlementsOnce sync.Once
supportedEntitlements *EntitlementOrderedSet
// Deprecated
LegacyType Type
Expand Down Expand Up @@ -8401,19 +8398,17 @@ func (t *IntersectionType) initializeMemberResolvers() {
})
}

func (t *IntersectionType) SupportedEntitlements() (set *EntitlementOrderedSet) {
if t.supportedEntitlements != nil {
return t.supportedEntitlements
}

// an intersection type supports all the entitlements of its interfaces
set = orderedmap.New[EntitlementOrderedSet](t.EffectiveIntersectionSet().Len())
t.EffectiveIntersectionSet().ForEach(func(it *InterfaceType) {
set.SetAll(it.SupportedEntitlements())
func (t *IntersectionType) SupportedEntitlements() *EntitlementOrderedSet {
t.supportedEntitlementsOnce.Do(func() {
// an intersection type supports all the entitlements of its interfaces
set := orderedmap.New[EntitlementOrderedSet](t.EffectiveIntersectionSet().Len())
t.EffectiveIntersectionSet().ForEach(func(it *InterfaceType) {
set.SetAll(it.SupportedEntitlements())
})
t.supportedEntitlements = set
})

t.supportedEntitlements = set
return set
return t.supportedEntitlements
}

func (*IntersectionType) Unify(
Expand Down
49 changes: 49 additions & 0 deletions runtime/tests/interpreter/member_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1149,3 +1149,52 @@ func TestInterpretMemberAccess(t *testing.T) {
}
})
}

func TestInterpretNestedReferenceMemberAccess(t *testing.T) {

t.Parallel()

t.Run("indexing", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndInterpret(t, `
resource R {}
fun test() {
let r <- create R()
let arrayRef = &[&r as &R] as &[AnyStruct]
let ref: &AnyStruct = arrayRef[0] // <--- run-time error here
destroy r
}
`)

_, err := inter.Invoke("test")
require.ErrorAs(t, err, &interpreter.InvalidMemberReferenceError{})
})

t.Run("field", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndInterpret(t, `
resource R {}
struct Container {
let value: AnyStruct
init(value: AnyStruct) {
self.value = value
}
}
fun test() {
let r <- create R()
let containerRef = &Container(value: &r as &R) as &Container
let ref: &AnyStruct = containerRef.value // <--- run-time error here
destroy r
}
`)

_, err := inter.Invoke("test")
require.ErrorAs(t, err, &interpreter.InvalidMemberReferenceError{})
})
}

0 comments on commit 583435c

Please sign in to comment.