Skip to content

Commit

Permalink
Merge pull request #1043 from onflow/bastian/static-types-in-dynamic-…
Browse files Browse the repository at this point in the history
…container-types

Include and consider static types of arrays and dictionaries
  • Loading branch information
SupunS authored Jul 2, 2021
2 parents b561c08 + b88f083 commit 3cfe0f1
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 27 deletions.
9 changes: 8 additions & 1 deletion runtime/interpreter/dynamictype.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func (BoolDynamicType) IsImportable() bool {

type ArrayDynamicType struct {
ElementTypes []DynamicType
StaticType ArrayStaticType
}

func (ArrayDynamicType) IsDynamicType() {}
Expand Down Expand Up @@ -119,8 +120,14 @@ func (t CompositeDynamicType) IsImportable() bool {

// DictionaryDynamicType

type DictionaryStaticTypeEntry struct {
KeyType DynamicType
ValueType DynamicType
}

type DictionaryDynamicType struct {
EntryTypes []struct{ KeyType, ValueType DynamicType }
EntryTypes []DictionaryStaticTypeEntry
StaticType DictionaryStaticType
}

func (DictionaryDynamicType) IsDynamicType() {}
Expand Down
34 changes: 25 additions & 9 deletions runtime/interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -1769,7 +1769,7 @@ func (interpreter *Interpreter) checkValueTransferTargetType(value Value, target
dynamicTypeResults := DynamicTypeResults{}

valueDynamicType := value.DynamicType(interpreter, dynamicTypeResults)
if IsSubType(valueDynamicType, targetType) {
if interpreter.IsSubType(valueDynamicType, targetType) {
return true
}

Expand Down Expand Up @@ -2635,7 +2635,7 @@ func defineStringFunction(activation *VariableActivation) {
// - Character
// - Block

func IsSubType(subType DynamicType, superType sema.Type) bool {
func (interpreter *Interpreter) IsSubType(subType DynamicType, superType sema.Type) bool {
switch typedSubType := subType.(type) {
case MetaTypeDynamicType:
switch superType {
Expand Down Expand Up @@ -2684,9 +2684,19 @@ func IsSubType(subType DynamicType, superType sema.Type) bool {
case *sema.VariableSizedType:
superTypeElementType = typedSuperType.Type

subTypeStaticType := interpreter.ConvertStaticToSemaType(typedSubType.StaticType)
if !sema.IsSubType(subTypeStaticType, typedSuperType) {
return false
}

case *sema.ConstantSizedType:
superTypeElementType = typedSuperType.Type

subTypeStaticType := interpreter.ConvertStaticToSemaType(typedSubType.StaticType)
if !sema.IsSubType(subTypeStaticType, typedSuperType) {
return false
}

default:
switch superType {
case sema.AnyStructType, sema.AnyResourceType:
Expand All @@ -2697,7 +2707,7 @@ func IsSubType(subType DynamicType, superType sema.Type) bool {
}

for _, elementType := range typedSubType.ElementTypes {
if !IsSubType(elementType, superTypeElementType) {
if !interpreter.IsSubType(elementType, superTypeElementType) {
return false
}
}
Expand All @@ -2707,9 +2717,15 @@ func IsSubType(subType DynamicType, superType sema.Type) bool {
case DictionaryDynamicType:

if typedSuperType, ok := superType.(*sema.DictionaryType); ok {

subTypeStaticType := interpreter.ConvertStaticToSemaType(typedSubType.StaticType)
if !sema.IsSubType(subTypeStaticType, typedSuperType) {
return false
}

for _, entryTypes := range typedSubType.EntryTypes {
if !IsSubType(entryTypes.KeyType, typedSuperType.KeyType) ||
!IsSubType(entryTypes.ValueType, typedSuperType.ValueType) {
if !interpreter.IsSubType(entryTypes.KeyType, typedSuperType.KeyType) ||
!interpreter.IsSubType(entryTypes.ValueType, typedSuperType.ValueType) {

return false
}
Expand All @@ -2735,7 +2751,7 @@ func IsSubType(subType DynamicType, superType sema.Type) bool {

case SomeDynamicType:
if typedSuperType, ok := superType.(*sema.OptionalType); ok {
return IsSubType(typedSubType.InnerType, typedSuperType.Type)
return interpreter.IsSubType(typedSubType.InnerType, typedSuperType.Type)
}

switch superType {
Expand All @@ -2749,7 +2765,7 @@ func IsSubType(subType DynamicType, superType sema.Type) bool {
// First, check that the dynamic type of the referenced value
// is a subtype of the super type

if !IsSubType(typedSubType.InnerType(), typedSuperType.Type) {
if !interpreter.IsSubType(typedSubType.InnerType(), typedSuperType.Type) {
return false
}

Expand Down Expand Up @@ -2919,7 +2935,7 @@ func (interpreter *Interpreter) authAccountReadFunction(addressValue AddressValu
dynamicTypeResults := DynamicTypeResults{}

dynamicType := value.Value.DynamicType(interpreter, dynamicTypeResults)
if !IsSubType(dynamicType, ty) {
if !interpreter.IsSubType(dynamicType, ty) {
return NilValue{}
}

Expand Down Expand Up @@ -3412,7 +3428,7 @@ func (interpreter *Interpreter) isInstanceFunction(self Value) HostFunctionValue
// NOTE: not invocation.Self, as that is only set for composite values
dynamicTypeResults := DynamicTypeResults{}
dynamicType := self.DynamicType(interpreter, dynamicTypeResults)
result := IsSubType(dynamicType, semaType)
result := interpreter.IsSubType(dynamicType, semaType)
return BoolValue(result)
},
)
Expand Down
2 changes: 1 addition & 1 deletion runtime/interpreter/interpreter_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ func (interpreter *Interpreter) VisitCastingExpression(expression *ast.CastingEx
case ast.OperationFailableCast, ast.OperationForceCast:
dynamicTypeResults := DynamicTypeResults{}
dynamicType := value.DynamicType(interpreter, dynamicTypeResults)
isSubType := IsSubType(dynamicType, expectedType)
isSubType := interpreter.IsSubType(dynamicType, expectedType)

switch expression.Operation {
case ast.OperationFailableCast:
Expand Down
26 changes: 21 additions & 5 deletions runtime/interpreter/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,7 @@ func (v *ArrayValue) DynamicType(interpreter *Interpreter, results DynamicTypeRe

return ArrayDynamicType{
ElementTypes: elementTypes,
StaticType: v.Type,
}
}

Expand Down Expand Up @@ -1101,6 +1102,16 @@ func (v *ArrayValue) Equal(other Value, interpreter *Interpreter, loadDeferred b
return false
}

if v.Type == nil {
if otherArray.Type != nil {
return false
}
} else if otherArray.Type == nil ||
!v.Type.Equal(otherArray.Type) {

return false
}

for i, value := range elements {
otherValue := otherElements[i]

Expand Down Expand Up @@ -7160,7 +7171,7 @@ func (v *CompositeValue) ConformsToDynamicType(
dynamicTypeResults := DynamicTypeResults{}
fieldDynamicType := field.DynamicType(interpreter, dynamicTypeResults)

if !IsSubType(fieldDynamicType, member.TypeAnnotation.Type) {
if !interpreter.IsSubType(fieldDynamicType, member.TypeAnnotation.Type) {
return false
}

Expand Down Expand Up @@ -7459,21 +7470,22 @@ func (v *DictionaryValue) Walk(walkChild func(Value)) {

func (v *DictionaryValue) DynamicType(interpreter *Interpreter, results DynamicTypeResults) DynamicType {
keys := v.Keys().Elements()
entryTypes := make([]struct{ KeyType, ValueType DynamicType }, len(keys))
entryTypes := make([]DictionaryStaticTypeEntry, len(keys))

for i, key := range keys {
// NOTE: Force unwrap, otherwise dynamic type check is for optional type.
// This is safe because we are iterating over the keys.
value := v.Get(interpreter, ReturnEmptyLocationRange, key).(*SomeValue).Value
entryTypes[i] =
struct{ KeyType, ValueType DynamicType }{
DictionaryStaticTypeEntry{
KeyType: key.DynamicType(interpreter, results),
ValueType: value.DynamicType(interpreter, results),
}
}

return DictionaryDynamicType{
EntryTypes: entryTypes,
StaticType: v.Type,
}
}

Expand Down Expand Up @@ -7966,6 +7978,10 @@ func (v *DictionaryValue) Equal(other Value, interpreter *Interpreter, loadDefer
v.ensureLoaded()
otherDictionary.ensureLoaded()

if !v.Type.Equal(otherDictionary.Type) {
return false
}

if !v.keys.Equal(otherDictionary.keys, interpreter, loadDeferred) {
return false
}
Expand Down Expand Up @@ -8388,8 +8404,8 @@ func (v *StorageReferenceValue) ReferencedValue(interpreter *Interpreter) *Value
if v.BorrowedType != nil {
dynamicTypeResults := DynamicTypeResults{}
dynamicType := value.DynamicType(interpreter, dynamicTypeResults)
if !IsSubType(dynamicType, v.BorrowedType) {
IsSubType(dynamicType, v.BorrowedType)
if !interpreter.IsSubType(dynamicType, v.BorrowedType) {
interpreter.IsSubType(dynamicType, v.BorrowedType)
return nil
}
}
Expand Down
94 changes: 94 additions & 0 deletions runtime/interpreter/value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,78 @@ func TestArrayValue_Equal(t *testing.T) {
)
})

t.Run("different types", func(t *testing.T) {

t.Parallel()

uint16ArrayStaticType := VariableSizedStaticType{
Type: PrimitiveStaticTypeUInt16,
}

require.False(t,
NewArrayValueUnownedNonCopying(
uint8ArrayStaticType,
).Equal(
NewArrayValueUnownedNonCopying(
uint16ArrayStaticType,
),
nil,
true,
),
)
})

t.Run("no type, type", func(t *testing.T) {

t.Parallel()

require.False(t,
NewArrayValueUnownedNonCopying(
nil,
).Equal(
NewArrayValueUnownedNonCopying(
uint8ArrayStaticType,
),
nil,
true,
),
)
})

t.Run("type, no type", func(t *testing.T) {

t.Parallel()

require.False(t,
NewArrayValueUnownedNonCopying(
uint8ArrayStaticType,
).Equal(
NewArrayValueUnownedNonCopying(
nil,
),
nil,
true,
),
)
})

t.Run("no types", func(t *testing.T) {

t.Parallel()

require.True(t,
NewArrayValueUnownedNonCopying(
nil,
).Equal(
NewArrayValueUnownedNonCopying(
nil,
),
nil,
true,
),
)
})

t.Run("different kind", func(t *testing.T) {

t.Parallel()
Expand Down Expand Up @@ -1871,6 +1943,28 @@ func TestDictionaryValue_Equal(t *testing.T) {
)
})

t.Run("different types", func(t *testing.T) {

t.Parallel()

stringByteDictionaryStaticType := DictionaryStaticType{
KeyType: PrimitiveStaticTypeString,
ValueType: PrimitiveStaticTypeUInt8,
}

require.False(t,
NewDictionaryValueUnownedNonCopying(
byteStringDictionaryType,
).Equal(
NewDictionaryValueUnownedNonCopying(
stringByteDictionaryStaticType,
),
nil,
true,
),
)
})

t.Run("different kind", func(t *testing.T) {

t.Parallel()
Expand Down
2 changes: 1 addition & 1 deletion runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,7 @@ func validateArgumentParams(
}

// Check that decoded value is a subtype of static parameter type
if !interpreter.IsSubType(dynamicType, parameterType) {
if !inter.IsSubType(dynamicType, parameterType) {
return nil, &InvalidEntryPointArgumentError{
Index: i,
Err: &InvalidValueTypeError{
Expand Down
Loading

0 comments on commit 3cfe0f1

Please sign in to comment.