Skip to content

Commit

Permalink
Merge pull request #3156 from onflow/sainati/entitlements-common-supe…
Browse files Browse the repository at this point in the history
…rtype

Improve type inference for authorized references
  • Loading branch information
dsainati1 authored Mar 8, 2024
2 parents 13610ac + 35cad2c commit 32e81b0
Show file tree
Hide file tree
Showing 5 changed files with 409 additions and 9 deletions.
21 changes: 21 additions & 0 deletions runtime/common/orderedmap/orderedmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,27 @@ func (om *OrderedMap[K, V]) KeySetIsDisjointFrom(other *OrderedMap[K, V]) bool {
return isDisjoint
}

// KeySetIntersection returns a map containing the intersection of the keys in the two maps
// this is only well defined for sets (i.e. maps without meaningful values)
func KeySetIntersection[K comparable, V any](om *OrderedMap[K, V], other *OrderedMap[K, V]) *OrderedMap[K, V] {
intersection := New[OrderedMap[K, V]](len(om.pairs))
om.Foreach(func(key K, value V) {
if other.Contains(key) {
intersection.Set(key, value)
}
})
return intersection
}

// KeySetUnion returns a map containing the union of the keys in the two maps
// this is only well defined for sets (i.e. maps without meaningful values)
func KeySetUnion[K comparable, V any](om *OrderedMap[K, V], other *OrderedMap[K, V]) *OrderedMap[K, V] {
union := New[OrderedMap[K, V]](len(om.pairs))
union.SetAll(om)
union.SetAll(other)
return union
}

// Pair is an entry in an OrderedMap
type Pair[K any, V any] struct {
Key K
Expand Down
133 changes: 130 additions & 3 deletions runtime/sema/type_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package sema

import (
"github.com/onflow/cadence/runtime/common/orderedmap"
"github.com/onflow/cadence/runtime/errors"
)

Expand Down Expand Up @@ -671,9 +672,9 @@ func findSuperTypeFromLowerMask(joinedTypeTag TypeTag, types []Type) Type {
return commonSuperTypeOfVariableSizedArrays(types)
case dictionaryTypeMask:
return commonSuperTypeOfDictionaries(types)
case referenceTypeMask,
genericTypeMask:

case referenceTypeMask:
return commonSuperTypeOfReferences(types)
case genericTypeMask:
return getSuperTypeOfDerivedTypes(types)
default:
// not homogenous. Return nil and continue on advanced checks.
Expand Down Expand Up @@ -719,6 +720,132 @@ func findSuperTypeFromUpperMask(joinedTypeTag TypeTag, types []Type) Type {
}
}

func leastCommonAccess(accessA, accessB Access) Access {
setAccessA, isSetAccessA := accessA.(EntitlementSetAccess)
setAccessB, isSetAccessB := accessB.(EntitlementSetAccess)

if !isSetAccessA || !isSetAccessB {
return UnauthorizedAccess
}

switch setAccessA.SetKind {
case Conjunction:
switch setAccessB.SetKind {
case Conjunction:
// least common access of two non-disjoint conjunctions is their intersection
// e.g. the least common supertype of (E, F) and (E, G) is just E
intersection := orderedmap.KeySetIntersection(setAccessA.Entitlements, setAccessB.Entitlements)
if intersection.Len() != 0 {
return NewAccessFromEntitlementSet(intersection, Conjunction)
}
// if the intersection is completely empty (i.e. the two sets are totally disjoint)
// the least common supertype is the union of one element arbitrarily chosen from each conjunction.
// E.g., `(A | C)`, `(A | D)`, `(B | C)`, and `(B | D)`
// are all equally valid least common supertypes of (A, B)` and `(C, D)`.
//
// To get a more consistent behavior here,
// we take the least common supertype of all the possible least common supertypes from the previous step,
// which luckily here is just the union of the elements of the conjunctions converted to a disjunction.
// e.g. the least common supertype of E and F is `(E | F)`
// and the least common supertype of `(A, B)` and `(C, D)` is `(A | B | C | D)`
union := orderedmap.KeySetUnion(setAccessA.Entitlements, setAccessB.Entitlements)
return NewAccessFromEntitlementSet(union, Disjunction)

case Disjunction:
// least common supertype of a non-disjoint conjunction and a disjunction is
// just the disjunction. This is because, in this case, the disjunction is
// already a supertype of the conjunction
// e.g. the least common access of `(E, F)` and `(E | G)` is `(E | G)`
if setAccessB.PermitsAccess(setAccessA) {
return setAccessB
}

// if the conjunction and disjunction are completely disjoint, their least common supertype
// is the union of the disjunction and an arbitrary element chosen from the conjunction.
// E.g. `(E | G | H)` and `(F | G | H)` are both valid least common supertypes of `(E, F)` and `(G | H)`
//
// In order to have a consistent behavior here,
// we take the least common supertype of all the possible least common supertypes from the previous step,
// which luckily here is just the union of the elements of the disjunction and the conjunction.
// E.g. our computed supertype of `(E, F)` and `(G | H)` is `(E | F | G | H)`
union := orderedmap.KeySetUnion(setAccessA.Entitlements, setAccessB.Entitlements)
return NewAccessFromEntitlementSet(union, Disjunction)
}

case Disjunction:
switch setAccessB.SetKind {
case Conjunction:
// symmetric with the other case where A is a conjunction and B is a disjunction
return leastCommonAccess(accessB, accessA)
case Disjunction:
// least common access of two disjunctions is their union
// e.g. the least common supertype of (E | F) and (E | G) is (E | F | G)
union := orderedmap.KeySetUnion(setAccessA.Entitlements, setAccessB.Entitlements)
return NewAccessFromEntitlementSet(union, Disjunction)
}
}

panic(errors.NewUnreachableError())
}

func commonSuperTypeOfReferences(types []Type) Type {
var references []*ReferenceType

// check that all the referenced types are equal
// before computing authorization supertype
var prevReferenceType *ReferenceType
for _, typ := range types {
// 'Never' type doesn't affect the supertype.
// Hence, ignore them
if typ == NeverType {
continue
}

referenceType, ok := typ.(*ReferenceType)
if !ok {
panic(errors.NewUnreachableError())
}

references = append(references, referenceType)

if prevReferenceType == nil {
prevReferenceType = referenceType
continue
}

if !referenceType.Type.Equal(prevReferenceType.Type) {
return commonSuperTypeOfHeterogeneousTypes(types)
}
}

if len(references) == 0 {
return commonSuperTypeOfHeterogeneousTypes(types)
}

referencedType := references[0].Type

// compute the least common authorization of the list of references
// the "supertype" operation for access is commutative and associative,
// so we can just apply the operation by reducing the list
var superAuthorization Access
for _, refTyp := range references {
if superAuthorization == nil {
superAuthorization = refTyp.Authorization
continue
}
// this is the "top" access, so if we are already here, just end the loop early
if superAuthorization == UnauthorizedAccess {
break
}
superAuthorization = leastCommonAccess(superAuthorization, refTyp.Authorization)
}

return &ReferenceType{
Type: referencedType,
Authorization: superAuthorization,
}
}

func getSuperTypeOfDerivedTypes(types []Type) Type {
// We reach here if all types belongs to same kind.
// e.g: All are arrays, all are dictionaries, etc.
Expand Down
Loading

0 comments on commit 32e81b0

Please sign in to comment.