From dd2e003e8080f403a74e62db2f72f0b4ffb55a13 Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Sun, 15 Oct 2023 15:27:09 +0530 Subject: [PATCH 01/15] WIP: Introduce HashableStruct --- runtime/convertValues.go | 2 +- runtime/convertValues_test.go | 4 +-- runtime/interpreter/interpreter.go | 5 ++- runtime/interpreter/primitivestatictype.go | 10 ++++-- runtime/sema/check_dictionary_expression.go | 21 +----------- runtime/sema/checker.go | 2 +- runtime/sema/hashablestruct_type.go | 35 ++++++++++++++++++++ runtime/sema/path_type.go | 16 ++++----- runtime/sema/type.go | 29 ++++++++++++++++ runtime/sema/type_tags.go | 28 ++++++++++++++-- runtime/tests/checker/conditional_test.go | 2 +- runtime/tests/checker/events_test.go | 2 +- runtime/tests/checker/storable_test.go | 2 +- runtime/tests/checker/type_inference_test.go | 11 +++--- types.go | 20 +++++++++++ 15 files changed, 144 insertions(+), 45 deletions(-) create mode 100644 runtime/sema/hashablestruct_type.go diff --git a/runtime/convertValues.go b/runtime/convertValues.go index 82a2caa0c7..f10042cfe0 100644 --- a/runtime/convertValues.go +++ b/runtime/convertValues.go @@ -1357,7 +1357,7 @@ func (i valueImporter) importDictionaryValue( keySuperType := sema.LeastCommonSuperType(keyTypes...) valueSuperType := sema.LeastCommonSuperType(valueTypes...) - if !sema.IsValidDictionaryKeyType(keySuperType) { + if !sema.IsSubType(keySuperType, sema.HashableStructType) { return nil, errors.NewDefaultUserError( "cannot import dictionary: keys does not belong to the same type", ) diff --git a/runtime/convertValues_test.go b/runtime/convertValues_test.go index eff3689143..2282c9bc47 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -3891,7 +3891,7 @@ func TestRuntimeImportExportDictionaryValue(t *testing.T) { interpreter.EmptyLocationRange, interpreter.DictionaryStaticType{ KeyType: interpreter.PrimitiveStaticTypeInt8, - ValueType: interpreter.PrimitiveStaticTypeAnyStruct, + ValueType: interpreter.PrimitiveStaticTypeHashableStruct, }, interpreter.NewUnmeteredInt8Value(1), interpreter.NewUnmeteredIntValueFromInt64(100), interpreter.NewUnmeteredInt8Value(2), interpreter.NewUnmeteredStringValue("hello"), @@ -3903,7 +3903,7 @@ func TestRuntimeImportExportDictionaryValue(t *testing.T) { interpreter.EmptyLocationRange, interpreter.DictionaryStaticType{ KeyType: interpreter.PrimitiveStaticTypeSignedInteger, - ValueType: interpreter.PrimitiveStaticTypeAnyStruct, + ValueType: interpreter.PrimitiveStaticTypeHashableStruct, }, interpreter.NewUnmeteredInt8Value(1), interpreter.NewUnmeteredStringValue("foo"), interpreter.NewUnmeteredIntValueFromInt64(2), interpreter.NewUnmeteredIntValueFromInt64(50), diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index de338e7b8d..73ec9c7379 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -3127,7 +3127,10 @@ func init() { // if the given key is not a valid dictionary key, it wouldn't make sense to create this type if keyType == nil || - !sema.IsValidDictionaryKeyType(invocation.Interpreter.MustConvertStaticToSemaType(keyType)) { + !sema.IsSubType( + invocation.Interpreter.MustConvertStaticToSemaType(keyType), + sema.HashableStructType, + ) { return Nil } diff --git a/runtime/interpreter/primitivestatictype.go b/runtime/interpreter/primitivestatictype.go index c31b35ca68..868b70a853 100644 --- a/runtime/interpreter/primitivestatictype.go +++ b/runtime/interpreter/primitivestatictype.go @@ -74,7 +74,7 @@ const ( PrimitiveStaticTypeCharacter PrimitiveStaticTypeMetaType PrimitiveStaticTypeBlock - _ + PrimitiveStaticTypeHashableStruct _ _ _ @@ -200,7 +200,8 @@ func (t PrimitiveStaticType) elementSize() uint { case PrimitiveStaticTypeAnyStruct, PrimitiveStaticTypeAnyResource, - PrimitiveStaticTypeAny: + PrimitiveStaticTypeAny, + PrimitiveStaticTypeHashableStruct: return UnknownElementSize case PrimitiveStaticTypeVoid: return uint(len(cborVoidValue)) @@ -320,6 +321,9 @@ func (t PrimitiveStaticType) SemaType() sema.Type { case PrimitiveStaticTypeAnyStruct: return sema.AnyStructType + case PrimitiveStaticTypeHashableStruct: + return sema.HashableStructType + case PrimitiveStaticTypeAnyResource: return sema.AnyResourceType @@ -573,6 +577,8 @@ func ConvertSemaToPrimitiveStaticType( typ = PrimitiveStaticTypeAny case sema.AnyStructType: typ = PrimitiveStaticTypeAnyStruct + case sema.HashableStructType: + typ = PrimitiveStaticTypeHashableStruct case sema.AnyResourceType: typ = PrimitiveStaticTypeAnyResource case sema.AuthAccountType: diff --git a/runtime/sema/check_dictionary_expression.go b/runtime/sema/check_dictionary_expression.go index 9bc9321139..82e5bae733 100644 --- a/runtime/sema/check_dictionary_expression.go +++ b/runtime/sema/check_dictionary_expression.go @@ -20,7 +20,6 @@ package sema import ( "github.com/onflow/cadence/runtime/ast" - "github.com/onflow/cadence/runtime/common" ) func (checker *Checker) VisitDictionaryExpression(expression *ast.DictionaryExpression) Type { @@ -88,7 +87,7 @@ func (checker *Checker) VisitDictionaryExpression(expression *ast.DictionaryExpr } } - if !IsValidDictionaryKeyType(keyType) { + if !IsSubType(keyType, HashableStructType) { checker.report( &InvalidDictionaryKeyTypeError{ Type: keyType, @@ -112,21 +111,3 @@ func (checker *Checker) VisitDictionaryExpression(expression *ast.DictionaryExpr return dictionaryType } - -func IsValidDictionaryKeyType(keyType Type) bool { - // TODO: implement support for more built-in types here and in interpreter - switch keyType := keyType.(type) { - case *AddressType: - return true - case *CompositeType: - return keyType.Kind == common.CompositeKindEnum - default: - switch keyType { - case NeverType, BoolType, CharacterType, StringType, MetaType: - return true - default: - return IsSameTypeKind(keyType, NumberType) || - IsSameTypeKind(keyType, PathType) - } - } -} diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 8706de2f69..c1b0ff7aee 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -1046,7 +1046,7 @@ func (checker *Checker) convertDictionaryType(t *ast.DictionaryType) Type { keyType := checker.ConvertType(t.KeyType) valueType := checker.ConvertType(t.ValueType) - if !IsValidDictionaryKeyType(keyType) { + if !IsSubType(keyType, HashableStructType) { checker.report( &InvalidDictionaryKeyTypeError{ Type: keyType, diff --git a/runtime/sema/hashablestruct_type.go b/runtime/sema/hashablestruct_type.go new file mode 100644 index 0000000000..9977090f61 --- /dev/null +++ b/runtime/sema/hashablestruct_type.go @@ -0,0 +1,35 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sema + +// HashableStructType represents the type that can be used as a Dictionary key type. +var HashableStructType = &SimpleType{ + Name: "HashableStruct", + QualifiedName: "HashableStruct", + TypeID: "HashableStruct", + tag: HashableStructTypeTag, + IsResource: false, + // The actual storability of a value is checked at run-time + Storable: true, + Equatable: false, + Comparable: false, + Exportable: true, + // The actual importability is checked at runtime + Importable: true, +} diff --git a/runtime/sema/path_type.go b/runtime/sema/path_type.go index 75e0fa35e1..cd06e50443 100644 --- a/runtime/sema/path_type.go +++ b/runtime/sema/path_type.go @@ -30,10 +30,10 @@ var PathType = &SimpleType{ Comparable: false, Exportable: true, Importable: true, - IsSuperTypeOf: func(subType Type) bool { - return IsSubType(subType, StoragePathType) || - IsSubType(subType, CapabilityPathType) - }, + // IsSuperTypeOf: func(subType Type) bool { + // return IsSubType(subType, StoragePathType) || + // IsSubType(subType, CapabilityPathType) + // }, } // StoragePathType @@ -62,10 +62,10 @@ var CapabilityPathType = &SimpleType{ Comparable: false, Exportable: true, Importable: true, - IsSuperTypeOf: func(subType Type) bool { - return IsSubType(subType, PrivatePathType) || - IsSubType(subType, PublicPathType) - }, + // IsSuperTypeOf: func(subType Type) bool { + // return IsSubType(subType, PrivatePathType) || + // IsSubType(subType, PublicPathType) + // }, } // PublicPathType diff --git a/runtime/sema/type.go b/runtime/sema/type.go index ec47eee467..b9ba0bb274 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -3411,6 +3411,7 @@ func init() { HashAlgorithmType, StorageCapabilityControllerType, AccountCapabilityControllerType, + HashableStructType, }, ) @@ -4033,6 +4034,23 @@ func isAttachmentType(t Type) bool { t == AnyStructAttachmentType } +func IsHashableStructType(t Type) bool { + switch typ := t.(type) { + case *AddressType: + return true + case *CompositeType: + return typ.Kind == common.CompositeKindEnum + default: + switch typ { + case NeverType, BoolType, CharacterType, StringType, MetaType, HashableStructType: + return true + default: + return IsSubType(typ, NumberType) || + IsSubType(typ, PathType) + } + } +} + func (t *CompositeType) GetBaseType() Type { return t.baseType } @@ -5704,6 +5722,17 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { case AnyStructAttachmentType: return !subType.IsResourceType() && isAttachmentType(subType) + case HashableStructType: + return !subType.IsResourceType() && IsHashableStructType(subType) + + case PathType: + return IsSubType(subType, StoragePathType) || + IsSubType(subType, CapabilityPathType) + + case CapabilityPathType: + return IsSubType(subType, PrivatePathType) || + IsSubType(subType, PublicPathType) + case NumberType: switch subType { case NumberType, SignedNumberType: diff --git a/runtime/sema/type_tags.go b/runtime/sema/type_tags.go index 436c6f921e..e91a05c551 100644 --- a/runtime/sema/type_tags.go +++ b/runtime/sema/type_tags.go @@ -224,6 +224,8 @@ const ( interfaceTypeMask functionTypeMask + hashableStructMask + invalidTypeMask ) @@ -345,6 +347,16 @@ var ( StorageCapabilityControllerTypeTag = newTypeTagFromUpperMask(storageCapabilityControllerTypeMask) AccountCapabilityControllerTypeTag = newTypeTagFromUpperMask(accountCapabilityControllerTypeMask) + HashableStructTypeTag = newTypeTagFromUpperMask(hashableStructMask). + Or(AddressTypeTag). + Or(NeverTypeTag). + Or(BoolTypeTag). + Or(CharacterTypeTag). + Or(StringTypeTag). + Or(MetaTypeTag). + Or(NumberTypeTag). + Or(PathTypeTag) + // AnyStructTypeTag only includes the types that are pre-known // to belong to AnyStruct type. This is more of an optimization. // Other types (derived types such as collections, etc.) are not possible @@ -368,7 +380,8 @@ var ( Or(CapabilityTypeTag). Or(FunctionTypeTag). Or(StorageCapabilityControllerTypeTag). - Or(AccountCapabilityControllerTypeTag) + Or(AccountCapabilityControllerTypeTag). + Or(HashableStructTypeTag) AnyResourceTypeTag = newTypeTagFromLowerMask(anyResourceTypeMask). Or(AnyResourceAttachmentTypeTag) @@ -673,6 +686,9 @@ func findSuperTypeFromUpperMask(joinedTypeTag TypeTag, types []Type) Type { functionTypeMask: return getSuperTypeOfDerivedTypes(types) + case hashableStructMask: + return HashableStructType + case anyResourceAttachmentMask: return AnyResourceAttachmentType @@ -827,7 +843,7 @@ func commonSuperTypeOfDictionaries(types []Type) Type { return InvalidType } - if !IsValidDictionaryKeyType(keySuperType) { + if !IsSubType(keySuperType, HashableStructType) { return commonSuperTypeOfHeterogeneousTypes(types) } @@ -838,11 +854,13 @@ func commonSuperTypeOfDictionaries(types []Type) Type { } func commonSuperTypeOfHeterogeneousTypes(types []Type) Type { - var hasStructs, hasResources bool + var hasStructs, hasResources, allHashableStructs bool + allHashableStructs = true for _, typ := range types { isResource := typ.IsResourceType() hasResources = hasResources || isResource hasStructs = hasStructs || !isResource + allHashableStructs = allHashableStructs && IsHashableStructType(typ) if hasResources && hasStructs { return AnyType @@ -853,6 +871,10 @@ func commonSuperTypeOfHeterogeneousTypes(types []Type) Type { return AnyResourceType } + if allHashableStructs { + return HashableStructType + } + return AnyStructType } diff --git a/runtime/tests/checker/conditional_test.go b/runtime/tests/checker/conditional_test.go index e314988c89..e44b880188 100644 --- a/runtime/tests/checker/conditional_test.go +++ b/runtime/tests/checker/conditional_test.go @@ -105,7 +105,7 @@ func TestCheckConditionalExpressionTypeInferring(t *testing.T) { require.NoError(t, err) xType := RequireGlobalValue(t, checker.Elaboration, "x") - assert.Equal(t, sema.AnyStructType, xType) + assert.Equal(t, sema.HashableStructType, xType) }) t.Run("optional", func(t *testing.T) { diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index 2dc3b87258..49385d9b23 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -136,7 +136,7 @@ func TestCheckEventDeclaration(t *testing.T) { }, ) - if sema.IsValidDictionaryKeyType(ty) { + if sema.IsSubType(ty, sema.HashableStructType) { tests = append(tests, &sema.DictionaryType{ KeyType: ty, diff --git a/runtime/tests/checker/storable_test.go b/runtime/tests/checker/storable_test.go index 3f90ba4ecf..f14de2c142 100644 --- a/runtime/tests/checker/storable_test.go +++ b/runtime/tests/checker/storable_test.go @@ -84,7 +84,7 @@ func TestCheckStorable(t *testing.T) { ) } - if sema.IsValidDictionaryKeyType(ty) { + if sema.IsSubType(ty, sema.HashableStructType) { nestedTypes = append(nestedTypes, &sema.DictionaryType{ KeyType: ty, diff --git a/runtime/tests/checker/type_inference_test.go b/runtime/tests/checker/type_inference_test.go index af024b4f16..9cd0d3365e 100644 --- a/runtime/tests/checker/type_inference_test.go +++ b/runtime/tests/checker/type_inference_test.go @@ -1094,10 +1094,13 @@ func TestCheckDictionarySupertypeInference(t *testing.T) { }, }, { - name: "no supertype for inner keys", - code: `let x = {0: {10: 1, 20: 2}, 1: {"one": 1, "two": 2}}`, - expectedKeyType: sema.IntType, - expectedValueType: sema.AnyStructType, + name: "no supertype for inner keys", + code: `let x = {0: {10: 1, 20: 2}, 1: {"one": 1, "two": 2}}`, + expectedKeyType: sema.IntType, + expectedValueType: &sema.DictionaryType{ + KeyType: sema.HashableStructType, + ValueType: sema.IntType, + }, }, { name: "no supertype for inner keys with resource values", diff --git a/types.go b/types.go index 8e97823068..d597d9bd27 100644 --- a/types.go +++ b/types.go @@ -2635,6 +2635,26 @@ func (t AccountKeyType) Equal(other Type) bool { return t == other } +// HashableStructType +type HashableStructType struct{} + +var TheHashableStructType = HashableStructType{} +var _ Type = &HashableStructType{} + +func NewTheHashableStruct() HashableStructType { + return TheHashableStructType +} + +func (HashableStructType) isType() {} + +func (HashableStructType) ID() string { + return "HashableStruct" +} + +func (t HashableStructType) Equal(other Type) bool { + return t == other +} + // TypeWithCachedTypeID recursively caches type ID of type t. // This is needed because each type ID is lazily cached on // its first use in ID() to avoid performance penalty. From 531e40d06d4db0305e0be08e8767264ce026d485 Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Sun, 15 Oct 2023 15:28:44 +0530 Subject: [PATCH 02/15] remove commented code --- runtime/sema/path_type.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/runtime/sema/path_type.go b/runtime/sema/path_type.go index cd06e50443..0c1bb60279 100644 --- a/runtime/sema/path_type.go +++ b/runtime/sema/path_type.go @@ -30,10 +30,6 @@ var PathType = &SimpleType{ Comparable: false, Exportable: true, Importable: true, - // IsSuperTypeOf: func(subType Type) bool { - // return IsSubType(subType, StoragePathType) || - // IsSubType(subType, CapabilityPathType) - // }, } // StoragePathType @@ -62,10 +58,6 @@ var CapabilityPathType = &SimpleType{ Comparable: false, Exportable: true, Importable: true, - // IsSuperTypeOf: func(subType Type) bool { - // return IsSubType(subType, PrivatePathType) || - // IsSubType(subType, PublicPathType) - // }, } // PublicPathType From d6298f5e02bd4eb62c5c01f1a72bb8e88a1d991b Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Thu, 19 Oct 2023 00:20:38 +0530 Subject: [PATCH 03/15] Fix all existing tests --- runtime/convertValues_test.go | 77 +++++++++---------- runtime/sema/type_test.go | 19 +++-- .../tests/checker/arrays_dictionaries_test.go | 6 +- runtime/tests/checker/type_inference_test.go | 52 +++++-------- 4 files changed, 72 insertions(+), 82 deletions(-) diff --git a/runtime/convertValues_test.go b/runtime/convertValues_test.go index 2282c9bc47..a790573036 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -3881,7 +3881,7 @@ func TestRuntimeImportExportDictionaryValue(t *testing.T) { KeyType: interpreter.PrimitiveStaticTypeString, ValueType: interpreter.DictionaryStaticType{ KeyType: interpreter.PrimitiveStaticTypeSignedInteger, - ValueType: interpreter.PrimitiveStaticTypeAnyStruct, + ValueType: interpreter.PrimitiveStaticTypeHashableStruct, }, }, @@ -3916,52 +3916,47 @@ func TestRuntimeImportExportDictionaryValue(t *testing.T) { t.Run("import dictionary with heterogeneous keys", func(t *testing.T) { t.Parallel() - script := - `pub fun main(arg: Foo) { - } + dictionaryWithHeterogenousKeys := cadence.NewDictionary([]cadence.KeyValuePair{ + { + Key: cadence.String("foo"), + Value: cadence.String("value1"), + }, + { + Key: cadence.NewInt(5), + Value: cadence.String("value2"), + }, + }) - pub struct Foo { - pub var a: AnyStruct + inter := newTestInterpreter(t) - init() { - self.a = nil - } - }` + actual, err := ImportValue( + inter, + interpreter.EmptyLocationRange, + nil, + dictionaryWithHeterogenousKeys, + sema.AnyStructType, + ) + require.NoError(t, err) - // Struct with nested malformed dictionary value - malformedStruct := cadence.Struct{ - StructType: &cadence.StructType{ - Location: common.ScriptLocation{}, - QualifiedIdentifier: "Foo", - Fields: []cadence.Field{ - { - Identifier: "a", - Type: cadence.AnyStructType{}, - }, + AssertValuesEqual( + t, + inter, + interpreter.NewDictionaryValue( + inter, + interpreter.EmptyLocationRange, + interpreter.DictionaryStaticType{ + KeyType: interpreter.PrimitiveStaticTypeHashableStruct, + ValueType: interpreter.PrimitiveStaticTypeString, }, - }, - Fields: []cadence.Value{ - cadence.NewDictionary([]cadence.KeyValuePair{ - { - Key: cadence.String("foo"), - Value: cadence.String("value1"), - }, - { - Key: cadence.NewInt(5), - Value: cadence.String("value2"), - }, - }), - }, - } - - _, err := executeTestScript(t, script, malformedStruct) - RequireError(t, err) - assertUserError(t, err) - var argErr *InvalidEntryPointArgumentError - require.ErrorAs(t, err, &argErr) + interpreter.NewUnmeteredStringValue("foo"), + interpreter.NewUnmeteredStringValue("value1"), - assert.Contains(t, argErr.Error(), "cannot import dictionary: keys does not belong to the same type") + interpreter.NewIntValueFromInt64(nil, 5), + interpreter.NewUnmeteredStringValue("value2"), + ), + actual, + ) }) t.Run("nested dictionary with mismatching element", func(t *testing.T) { diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index 3233d9c323..f4ec6bc233 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -778,10 +778,11 @@ func TestCommonSuperType(t *testing.T) { testLeastCommonSuperType := func(t *testing.T, tests []testCase) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + tt := LeastCommonSuperType(test.types...) assert.Equal( t, test.expectedSuperType, - LeastCommonSuperType(test.types...), + tt, ) }) } @@ -901,7 +902,7 @@ func TestCommonSuperType(t *testing.T) { StringType, Int8Type, }, - expectedSuperType: AnyStructType, + expectedSuperType: HashableStructType, }, { name: "all nil", @@ -1115,7 +1116,7 @@ func TestCommonSuperType(t *testing.T) { stringArray, &VariableSizedType{Type: BoolType}, }, - expectedSuperType: &VariableSizedType{Type: AnyStructType}, + expectedSuperType: &VariableSizedType{Type: HashableStructType}, }, { name: "simple-typed array & resource array", @@ -1239,7 +1240,7 @@ func TestCommonSuperType(t *testing.T) { }, expectedSuperType: &DictionaryType{ KeyType: StringType, - ValueType: AnyStructType, + ValueType: HashableStructType, }, }, { @@ -1404,7 +1405,7 @@ func TestCommonSuperType(t *testing.T) { StoragePathType, StringType, }, - expectedSuperType: AnyStructType, + expectedSuperType: HashableStructType, }, } @@ -1658,7 +1659,9 @@ func TestCommonSuperType(t *testing.T) { Int8Type, StringType, }, - expectedSuperType: AnyStructType, + expectedSuperType: &OptionalType{ + Type: HashableStructType, + }, }, { name: "nil with simple type", @@ -1677,7 +1680,9 @@ func TestCommonSuperType(t *testing.T) { Int8Type, StringType, }, - expectedSuperType: AnyStructType, + expectedSuperType: &OptionalType{ + Type: HashableStructType, + }, }, { name: "multi-level simple optional types", diff --git a/runtime/tests/checker/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index 09cab5a4ca..700f49df18 100644 --- a/runtime/tests/checker/arrays_dictionaries_test.go +++ b/runtime/tests/checker/arrays_dictionaries_test.go @@ -133,7 +133,11 @@ func TestCheckInvalidDictionaryKeys(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - let z = {"a": 1, true: 2} + let f = fun (_ x: Int): Int { + return x + 10 + } + + let z = {f: 1, true: 2} `) errs := RequireCheckerErrors(t, err, 1) diff --git a/runtime/tests/checker/type_inference_test.go b/runtime/tests/checker/type_inference_test.go index 9cd0d3365e..baa3a85f9e 100644 --- a/runtime/tests/checker/type_inference_test.go +++ b/runtime/tests/checker/type_inference_test.go @@ -787,7 +787,7 @@ func TestCheckArraySupertypeInference(t *testing.T) { { name: "mixed simple values", code: `let x = [0, true]`, - expectedElementType: sema.AnyStructType, + expectedElementType: sema.HashableStructType, }, { name: "signed integer values", @@ -893,7 +893,7 @@ func TestCheckArraySupertypeInference(t *testing.T) { code: `let x = [[[1, 2]], [["foo", "bar"]], [[5.3, 6.4]]]`, expectedElementType: &sema.VariableSizedType{ Type: &sema.VariableSizedType{ - Type: sema.AnyStructType, + Type: sema.HashableStructType, }, }, }, @@ -902,7 +902,7 @@ func TestCheckArraySupertypeInference(t *testing.T) { code: `let x = [[[1, 2] as [Int; 2]], [["foo", "bar"] as [String; 2]], [[5.3, 6.4] as [Fix64; 2]]]`, expectedElementType: &sema.VariableSizedType{ Type: &sema.ConstantSizedType{ - Type: sema.AnyStructType, + Type: sema.HashableStructType, Size: 2, }, }, @@ -977,7 +977,7 @@ func TestCheckDictionarySupertypeInference(t *testing.T) { name: "mixed simple values", code: `let x = {0: 0, 1: true}`, expectedKeyType: sema.IntType, - expectedValueType: sema.AnyStructType, + expectedValueType: sema.HashableStructType, }, { name: "signed integer values", @@ -1011,6 +1011,12 @@ func TestCheckDictionarySupertypeInference(t *testing.T) { Type: sema.StringType, }, }, + { + name: "int and string keys", + code: `let x = {0: 1, "hello": 2}`, + expectedKeyType: sema.HashableStructType, + expectedValueType: sema.IntType, + }, { name: "common interfaced values", code: ` @@ -1109,8 +1115,15 @@ func TestCheckDictionarySupertypeInference(t *testing.T) { pub resource Foo {} `, - expectedKeyType: sema.IntType, - expectedValueType: sema.AnyResourceType, + expectedKeyType: sema.IntType, + expectedValueType: &sema.DictionaryType{ + KeyType: sema.HashableStructType, + ValueType: &sema.InterfaceType{ + Location: common.StringLocation("test"), + Identifier: "Foo", + CompositeKind: common.CompositeKindStructure, + }, + }, }, } @@ -1146,33 +1159,6 @@ func TestCheckDictionarySupertypeInference(t *testing.T) { assert.IsType(t, &sema.TypeAnnotationRequiredError{}, errs[0]) }) - t.Run("no supertype for keys", func(t *testing.T) { - t.Parallel() - - code := ` - let x = {1: 1, "two": 2} - ` - _, err := ParseAndCheck(t, code) - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.InvalidDictionaryKeyTypeError{}, errs[0]) - }) - - t.Run("unsupported supertype for keys", func(t *testing.T) { - t.Parallel() - - code := ` - let x = {0: 1, "hello": 2} - ` - _, err := ParseAndCheck(t, code) - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidDictionaryKeyTypeError{}, errs[0]) - invalidKeyError := errs[0].(*sema.InvalidDictionaryKeyTypeError) - - assert.Equal(t, sema.AnyStructType, invalidKeyError.Type) - }) - t.Run("empty dictionary", func(t *testing.T) { t.Parallel() From 047939dc65548aa8e38758aa4da359002308dfe5 Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Thu, 19 Oct 2023 00:30:50 +0530 Subject: [PATCH 04/15] Add import/export for HashableStruct --- encoding/ccf/decode_type.go | 3 +++ encoding/ccf/simple_type_utils.go | 4 ++++ encoding/json/decode.go | 2 ++ encoding/json/encode.go | 1 + encoding/json/encoding_test.go | 1 + runtime/convertTypes.go | 4 ++++ types.go | 2 +- 7 files changed, 16 insertions(+), 1 deletion(-) diff --git a/encoding/ccf/decode_type.go b/encoding/ccf/decode_type.go index d461d40a24..40eb9f945e 100644 --- a/encoding/ccf/decode_type.go +++ b/encoding/ccf/decode_type.go @@ -236,6 +236,9 @@ func (d *Decoder) decodeSimpleTypeID() (cadence.Type, error) { case TypeAnyStruct: return cadence.TheAnyStructType, nil + case TypeHashableStruct: + return cadence.TheHashableStructType, nil + case TypeAnyResource: return cadence.TheAnyResourceType, nil diff --git a/encoding/ccf/simple_type_utils.go b/encoding/ccf/simple_type_utils.go index 1f28866fd5..aec556449a 100644 --- a/encoding/ccf/simple_type_utils.go +++ b/encoding/ccf/simple_type_utils.go @@ -85,6 +85,7 @@ const ( // Cadence simple type IDs TypeWord256 TypeAnyStructAttachmentType TypeAnyResourceAttachmentType + TypeHashableStruct ) // NOTE: cadence.FunctionType isn't included in simpleTypeIDByType @@ -99,6 +100,9 @@ func simpleTypeIDByType(typ cadence.Type) (uint64, bool) { case cadence.AnyStructType: return TypeAnyStruct, true + case cadence.HashableStructType: + return TypeHashableStruct, true + case cadence.AnyResourceType: return TypeAnyResource, true diff --git a/encoding/json/decode.go b/encoding/json/decode.go index 1c7d25997c..f09086572f 100644 --- a/encoding/json/decode.go +++ b/encoding/json/decode.go @@ -1203,6 +1203,8 @@ func (d *Decoder) decodeType(valueJSON any, results typeDecodingResults) cadence return cadence.TheAnyType case "AnyStruct": return cadence.TheAnyStructType + case "HashableStruct": + return cadence.TheHashableStructType case "AnyStructAttachment": return cadence.TheAnyStructAttachmentType case "AnyResource": diff --git a/encoding/json/encode.go b/encoding/json/encode.go index 4fc7cb49c0..5a8cbe0861 100644 --- a/encoding/json/encode.go +++ b/encoding/json/encode.go @@ -763,6 +763,7 @@ func prepareType(typ cadence.Type, results typePreparationResults) jsonValue { switch typ := typ.(type) { case cadence.AnyType, cadence.AnyStructType, + cadence.HashableStructType, cadence.AnyStructAttachmentType, cadence.AnyResourceType, cadence.AnyResourceAttachmentType, diff --git a/encoding/json/encoding_test.go b/encoding/json/encoding_test.go index 65afb32b94..3127ed9746 100644 --- a/encoding/json/encoding_test.go +++ b/encoding/json/encoding_test.go @@ -1753,6 +1753,7 @@ func TestEncodeSimpleTypes(t *testing.T) { for _, ty := range []cadence.Type{ cadence.AnyType{}, cadence.AnyStructType{}, + cadence.HashableStructType{}, cadence.AnyStructAttachmentType{}, cadence.AnyResourceType{}, cadence.AnyResourceAttachmentType{}, diff --git a/runtime/convertTypes.go b/runtime/convertTypes.go index 886839dda7..79acb2d726 100644 --- a/runtime/convertTypes.go +++ b/runtime/convertTypes.go @@ -157,6 +157,8 @@ func ExportMeteredType( return cadence.TheAnyType case sema.AnyStructType: return cadence.TheAnyStructType + case sema.HashableStructType: + return cadence.TheHashableStructType case sema.AnyResourceType: return cadence.TheAnyResourceType case sema.BlockType: @@ -539,6 +541,8 @@ func ImportType(memoryGauge common.MemoryGauge, t cadence.Type) interpreter.Stat return interpreter.NewPrimitiveStaticType(memoryGauge, interpreter.PrimitiveStaticTypeAny) case cadence.AnyStructType: return interpreter.NewPrimitiveStaticType(memoryGauge, interpreter.PrimitiveStaticTypeAnyStruct) + case cadence.HashableStructType: + return interpreter.NewPrimitiveStaticType(memoryGauge, interpreter.PrimitiveStaticTypeHashableStruct) case cadence.AnyResourceType: return interpreter.NewPrimitiveStaticType(memoryGauge, interpreter.PrimitiveStaticTypeAnyResource) case *cadence.OptionalType: diff --git a/types.go b/types.go index d597d9bd27..80b2388cad 100644 --- a/types.go +++ b/types.go @@ -2641,7 +2641,7 @@ type HashableStructType struct{} var TheHashableStructType = HashableStructType{} var _ Type = &HashableStructType{} -func NewTheHashableStruct() HashableStructType { +func NewHashableStruct() HashableStructType { return TheHashableStructType } From c94d3ffde4fae294b8f02960ffdb05bcca793506 Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Thu, 19 Oct 2023 22:28:04 +0530 Subject: [PATCH 05/15] Add test cases --- encoding/ccf/ccf_test.go | 68 ++++++++++++++++ runtime/convertValues_test.go | 144 ++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+) diff --git a/encoding/ccf/ccf_test.go b/encoding/ccf/ccf_test.go index 29c145678d..5970047e31 100644 --- a/encoding/ccf/ccf_test.go +++ b/encoding/ccf/ccf_test.go @@ -9319,6 +9319,74 @@ func TestEncodeType(t *testing.T) { 0x04, }, ) + + testEncodeAndDecode( + t, + cadence.TypeValue{ + StaticType: &cadence.FunctionType{ + TypeParameters: []cadence.TypeParameter{ + {Name: "T", TypeBound: cadence.HashableStructType{}}, + }, + Parameters: []cadence.Parameter{ + {Label: "qux", Identifier: "baz", Type: cadence.StringType{}}, + }, + ReturnType: cadence.IntType{}, + }, + }, + []byte{ + // language=json, format=json-cdc + // {"value":{"staticType":{"kind":"Function","typeParameters":[{"name":"T","typeBound":{"kind":"HashableStruct"}}],"parameters":[{"type":{"kind":"String"},"label":"qux","id":"baz"}],"return":{"kind":"Int"}}},"type":"Type"} + // + // language=edn, format=ccf + // 130([137(41), 193([[["T", 185(56)]], [["qux", "baz", 185(1)]], 185(4)])]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 elements follow + 0x82, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Meta type ID (41) + 0x18, 0x29, + // tag + 0xd8, ccf.CBORTagFunctionTypeValue, + // array, 3 elements follow + 0x83, + // array, 1 elements follow + 0x81, + // array, 2 elements follow + 0x82, + // string, 1 byte follows + 0x61, + // "T" + 0x54, + // tag + 0xd8, ccf.CBORTagSimpleTypeValue, + // HashableStruct type (56) + 0x18, 0x38, + // array, 1 elements follow + 0x81, + // array, 3 elements follow + 0x83, + // string, 3 bytes follow + 0x63, + // qux + 0x71, 0x75, 0x78, + // string, 3 bytes follow + 0x63, + // bax + 0x62, 0x61, 0x7a, + // tag + 0xd8, ccf.CBORTagSimpleTypeValue, + // String type (1) + 0x01, + // tag + 0xd8, ccf.CBORTagSimpleTypeValue, + // Int type ID (4) + 0x04, + }, + ) }) t.Run("with static function nil type bound", func(t *testing.T) { diff --git a/runtime/convertValues_test.go b/runtime/convertValues_test.go index a790573036..c72da0963b 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -217,6 +217,27 @@ func TestExportValue(t *testing.T) { ElementType: cadence.AnyStructType{}, }), }, + { + label: "Array (non-empty) with HashableStruct", + valueFactory: func(inter *interpreter.Interpreter) interpreter.Value { + return interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeHashableStruct, + }, + common.ZeroAddress, + interpreter.NewUnmeteredIntValueFromInt64(42), + interpreter.NewUnmeteredStringValue("foo"), + ) + }, + expected: cadence.NewArray([]cadence.Value{ + cadence.NewInt(42), + cadence.String("foo"), + }).WithType(&cadence.VariableSizedArrayType{ + ElementType: cadence.HashableStructType{}, + }), + }, { label: "Dictionary", valueFactory: func(inter *interpreter.Interpreter) interpreter.Value { @@ -930,6 +951,11 @@ func TestImportRuntimeType(t *testing.T) { actual: cadence.AnyStructType{}, expected: interpreter.PrimitiveStaticTypeAnyStruct, }, + { + label: "HashableStruct", + actual: cadence.HashableStructType{}, + expected: interpreter.PrimitiveStaticTypeHashableStruct, + }, { label: "AnyResource", actual: cadence.AnyResourceType{}, @@ -2954,6 +2980,10 @@ func TestRuntimeComplexStructArgumentPassing(t *testing.T) { Identifier: "j", Type: cadence.AnyStructType{}, }, + { + Identifier: "k", + Type: cadence.HashableStructType{}, + }, }, }, @@ -2998,6 +3028,7 @@ func TestRuntimeComplexStructArgumentPassing(t *testing.T) { Identifier: "foo", }, cadence.String("foo"), + cadence.String("foo"), }, } @@ -3023,6 +3054,7 @@ func TestRuntimeComplexStructArgumentPassing(t *testing.T) { pub var h: PublicPath pub var i: PrivatePath pub var j: AnyStruct + pub var k: HashableStruct init() { self.a = "Hello" @@ -3035,6 +3067,7 @@ func TestRuntimeComplexStructArgumentPassing(t *testing.T) { self.h = /public/foo self.i = /private/foo self.j = nil + self.k = "hashable_struct_value" } } `, @@ -3160,6 +3193,117 @@ func TestRuntimeComplexStructWithAnyStructFields(t *testing.T) { assert.Equal(t, expected, actual) } +func TestRuntimeComplexStructWithHashableStructFields(t *testing.T) { + + t.Parallel() + + // Complex struct value + complexStructValue := cadence.Struct{ + StructType: &cadence.StructType{ + Location: common.ScriptLocation{}, + QualifiedIdentifier: "Foo", + Fields: []cadence.Field{ + { + Identifier: "a", + Type: &cadence.OptionalType{ + Type: cadence.HashableStructType{}, + }, + }, + { + Identifier: "b", + Type: &cadence.DictionaryType{ + KeyType: cadence.StringType{}, + ElementType: cadence.HashableStructType{}, + }, + }, + { + Identifier: "c", + Type: &cadence.VariableSizedArrayType{ + ElementType: cadence.HashableStructType{}, + }, + }, + { + Identifier: "d", + Type: &cadence.ConstantSizedArrayType{ + ElementType: cadence.HashableStructType{}, + Size: 2, + }, + }, + { + Identifier: "e", + Type: cadence.HashableStructType{}, + }, + }, + }, + + Fields: []cadence.Value{ + cadence.NewOptional(cadence.String("John")), + cadence.NewDictionary([]cadence.KeyValuePair{ + { + Key: cadence.String("name"), + Value: cadence.String("Doe"), + }, + }).WithType(&cadence.DictionaryType{ + KeyType: cadence.StringType{}, + ElementType: cadence.HashableStructType{}, + }), + cadence.NewArray([]cadence.Value{ + cadence.String("foo"), + cadence.String("bar"), + }).WithType(&cadence.VariableSizedArrayType{ + ElementType: cadence.HashableStructType{}, + }), + cadence.NewArray([]cadence.Value{ + cadence.String("foo"), + cadence.String("bar"), + }).WithType(&cadence.ConstantSizedArrayType{ + ElementType: cadence.HashableStructType{}, + Size: 2, + }), + cadence.Path{ + Domain: common.PathDomainStorage, + Identifier: "foo", + }, + }, + } + + script := fmt.Sprintf( + ` + pub fun main(arg: %[1]s): %[1]s { + + if !arg.isInstance(Type<%[1]s>()) { + panic("Not a %[1]s value") + } + + return arg + } + + pub struct Foo { + pub var a: HashableStruct? + pub var b: {String: HashableStruct} + pub var c: [HashableStruct] + pub var d: [HashableStruct; 2] + pub var e: HashableStruct + + init() { + self.a = "Hello" + self.b = {} + self.c = [] + self.d = ["foo", "bar"] + self.e = /storage/foo + } + } + `, + "Foo", + ) + + actual, err := executeTestScript(t, script, complexStructValue) + require.NoError(t, err) + + expected := cadence.ValueWithCachedTypeID(complexStructValue) + assert.Equal(t, expected, actual) +} + func TestRuntimeMalformedArgumentPassing(t *testing.T) { t.Parallel() From 2e07531c687691f68946cc67403348c60dbb470f Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Thu, 19 Oct 2023 22:56:01 +0530 Subject: [PATCH 06/15] regenerate primitivestatictype --- .../interpreter/primitivestatictype_string.go | 104 +++++++++--------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/runtime/interpreter/primitivestatictype_string.go b/runtime/interpreter/primitivestatictype_string.go index 639e2cff9d..3300ba907b 100644 --- a/runtime/interpreter/primitivestatictype_string.go +++ b/runtime/interpreter/primitivestatictype_string.go @@ -20,6 +20,7 @@ func _() { _ = x[PrimitiveStaticTypeCharacter-9] _ = x[PrimitiveStaticTypeMetaType-10] _ = x[PrimitiveStaticTypeBlock-11] + _ = x[PrimitiveStaticTypeHashableStruct-12] _ = x[PrimitiveStaticTypeNumber-18] _ = x[PrimitiveStaticTypeSignedNumber-19] _ = x[PrimitiveStaticTypeInteger-24] @@ -72,7 +73,7 @@ func _() { _ = x[PrimitiveStaticType_Count-105] } -const _PrimitiveStaticType_name = "UnknownVoidAnyNeverAnyStructAnyResourceBoolAddressStringCharacterMetaTypeBlockNumberSignedNumberIntegerSignedIntegerFixedPointSignedFixedPointIntInt8Int16Int32Int64Int128Int256UIntUInt8UInt16UInt32UInt64UInt128UInt256Word8Word16Word32Word64Word128Word256Fix64UFix64PathCapabilityStoragePathCapabilityPathPublicPathPrivatePathAuthAccountPublicAccountDeployedContractAuthAccountContractsPublicAccountContractsAuthAccountKeysPublicAccountKeysAccountKeyAuthAccountInboxStorageCapabilityControllerAccountCapabilityControllerAuthAccountStorageCapabilitiesAuthAccountAccountCapabilitiesAuthAccountCapabilitiesPublicAccountCapabilities_Count" +const _PrimitiveStaticType_name = "UnknownVoidAnyNeverAnyStructAnyResourceBoolAddressStringCharacterMetaTypeBlockHashableStructNumberSignedNumberIntegerSignedIntegerFixedPointSignedFixedPointIntInt8Int16Int32Int64Int128Int256UIntUInt8UInt16UInt32UInt64UInt128UInt256Word8Word16Word32Word64Word128Word256Fix64UFix64PathCapabilityStoragePathCapabilityPathPublicPathPrivatePathAuthAccountPublicAccountDeployedContractAuthAccountContractsPublicAccountContractsAuthAccountKeysPublicAccountKeysAccountKeyAuthAccountInboxStorageCapabilityControllerAccountCapabilityControllerAuthAccountStorageCapabilitiesAuthAccountAccountCapabilitiesAuthAccountCapabilitiesPublicAccountCapabilities_Count" var _PrimitiveStaticType_map = map[PrimitiveStaticType]string{ 0: _PrimitiveStaticType_name[0:7], @@ -87,56 +88,57 @@ var _PrimitiveStaticType_map = map[PrimitiveStaticType]string{ 9: _PrimitiveStaticType_name[56:65], 10: _PrimitiveStaticType_name[65:73], 11: _PrimitiveStaticType_name[73:78], - 18: _PrimitiveStaticType_name[78:84], - 19: _PrimitiveStaticType_name[84:96], - 24: _PrimitiveStaticType_name[96:103], - 25: _PrimitiveStaticType_name[103:116], - 30: _PrimitiveStaticType_name[116:126], - 31: _PrimitiveStaticType_name[126:142], - 36: _PrimitiveStaticType_name[142:145], - 37: _PrimitiveStaticType_name[145:149], - 38: _PrimitiveStaticType_name[149:154], - 39: _PrimitiveStaticType_name[154:159], - 40: _PrimitiveStaticType_name[159:164], - 41: _PrimitiveStaticType_name[164:170], - 42: _PrimitiveStaticType_name[170:176], - 44: _PrimitiveStaticType_name[176:180], - 45: _PrimitiveStaticType_name[180:185], - 46: _PrimitiveStaticType_name[185:191], - 47: _PrimitiveStaticType_name[191:197], - 48: _PrimitiveStaticType_name[197:203], - 49: _PrimitiveStaticType_name[203:210], - 50: _PrimitiveStaticType_name[210:217], - 53: _PrimitiveStaticType_name[217:222], - 54: _PrimitiveStaticType_name[222:228], - 55: _PrimitiveStaticType_name[228:234], - 56: _PrimitiveStaticType_name[234:240], - 57: _PrimitiveStaticType_name[240:247], - 58: _PrimitiveStaticType_name[247:254], - 64: _PrimitiveStaticType_name[254:259], - 72: _PrimitiveStaticType_name[259:265], - 76: _PrimitiveStaticType_name[265:269], - 77: _PrimitiveStaticType_name[269:279], - 78: _PrimitiveStaticType_name[279:290], - 79: _PrimitiveStaticType_name[290:304], - 80: _PrimitiveStaticType_name[304:314], - 81: _PrimitiveStaticType_name[314:325], - 90: _PrimitiveStaticType_name[325:336], - 91: _PrimitiveStaticType_name[336:349], - 92: _PrimitiveStaticType_name[349:365], - 93: _PrimitiveStaticType_name[365:385], - 94: _PrimitiveStaticType_name[385:407], - 95: _PrimitiveStaticType_name[407:422], - 96: _PrimitiveStaticType_name[422:439], - 97: _PrimitiveStaticType_name[439:449], - 98: _PrimitiveStaticType_name[449:465], - 99: _PrimitiveStaticType_name[465:492], - 100: _PrimitiveStaticType_name[492:519], - 101: _PrimitiveStaticType_name[519:549], - 102: _PrimitiveStaticType_name[549:579], - 103: _PrimitiveStaticType_name[579:602], - 104: _PrimitiveStaticType_name[602:627], - 105: _PrimitiveStaticType_name[627:633], + 12: _PrimitiveStaticType_name[78:92], + 18: _PrimitiveStaticType_name[92:98], + 19: _PrimitiveStaticType_name[98:110], + 24: _PrimitiveStaticType_name[110:117], + 25: _PrimitiveStaticType_name[117:130], + 30: _PrimitiveStaticType_name[130:140], + 31: _PrimitiveStaticType_name[140:156], + 36: _PrimitiveStaticType_name[156:159], + 37: _PrimitiveStaticType_name[159:163], + 38: _PrimitiveStaticType_name[163:168], + 39: _PrimitiveStaticType_name[168:173], + 40: _PrimitiveStaticType_name[173:178], + 41: _PrimitiveStaticType_name[178:184], + 42: _PrimitiveStaticType_name[184:190], + 44: _PrimitiveStaticType_name[190:194], + 45: _PrimitiveStaticType_name[194:199], + 46: _PrimitiveStaticType_name[199:205], + 47: _PrimitiveStaticType_name[205:211], + 48: _PrimitiveStaticType_name[211:217], + 49: _PrimitiveStaticType_name[217:224], + 50: _PrimitiveStaticType_name[224:231], + 53: _PrimitiveStaticType_name[231:236], + 54: _PrimitiveStaticType_name[236:242], + 55: _PrimitiveStaticType_name[242:248], + 56: _PrimitiveStaticType_name[248:254], + 57: _PrimitiveStaticType_name[254:261], + 58: _PrimitiveStaticType_name[261:268], + 64: _PrimitiveStaticType_name[268:273], + 72: _PrimitiveStaticType_name[273:279], + 76: _PrimitiveStaticType_name[279:283], + 77: _PrimitiveStaticType_name[283:293], + 78: _PrimitiveStaticType_name[293:304], + 79: _PrimitiveStaticType_name[304:318], + 80: _PrimitiveStaticType_name[318:328], + 81: _PrimitiveStaticType_name[328:339], + 90: _PrimitiveStaticType_name[339:350], + 91: _PrimitiveStaticType_name[350:363], + 92: _PrimitiveStaticType_name[363:379], + 93: _PrimitiveStaticType_name[379:399], + 94: _PrimitiveStaticType_name[399:421], + 95: _PrimitiveStaticType_name[421:436], + 96: _PrimitiveStaticType_name[436:453], + 97: _PrimitiveStaticType_name[453:463], + 98: _PrimitiveStaticType_name[463:479], + 99: _PrimitiveStaticType_name[479:506], + 100: _PrimitiveStaticType_name[506:533], + 101: _PrimitiveStaticType_name[533:563], + 102: _PrimitiveStaticType_name[563:593], + 103: _PrimitiveStaticType_name[593:616], + 104: _PrimitiveStaticType_name[616:641], + 105: _PrimitiveStaticType_name[641:647], } func (i PrimitiveStaticType) String() string { From c305cae04e775c24a0c824963a8959c316ca5303 Mon Sep 17 00:00:00 2001 From: darkdrag00nv2 <122124396+darkdrag00nv2@users.noreply.github.com> Date: Thu, 26 Oct 2023 23:04:23 +0530 Subject: [PATCH 07/15] Remove redundant IsResourceType() check Co-authored-by: Supun Setunga --- runtime/sema/type.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/sema/type.go b/runtime/sema/type.go index b9ba0bb274..03e673b224 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -5723,7 +5723,7 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { return !subType.IsResourceType() && isAttachmentType(subType) case HashableStructType: - return !subType.IsResourceType() && IsHashableStructType(subType) + return IsHashableStructType(subType) case PathType: return IsSubType(subType, StoragePathType) || From 464b4ef943a75690ed7e3dcecff15732ebf23c3d Mon Sep 17 00:00:00 2001 From: darkdrag00nv2 <122124396+darkdrag00nv2@users.noreply.github.com> Date: Thu, 26 Oct 2023 23:04:46 +0530 Subject: [PATCH 08/15] Fix identation Co-authored-by: Supun Setunga --- runtime/convertValues_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/convertValues_test.go b/runtime/convertValues_test.go index c72da0963b..0c30181a76 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -3054,7 +3054,7 @@ func TestRuntimeComplexStructArgumentPassing(t *testing.T) { pub var h: PublicPath pub var i: PrivatePath pub var j: AnyStruct - pub var k: HashableStruct + pub var k: HashableStruct init() { self.a = "Hello" From 65c649910ca99383b8b241e4e832613b3e0ab7b6 Mon Sep 17 00:00:00 2001 From: darkdrag00nv2 <122124396+darkdrag00nv2@users.noreply.github.com> Date: Thu, 26 Oct 2023 23:05:03 +0530 Subject: [PATCH 09/15] Fix indentation Co-authored-by: Supun Setunga --- runtime/convertValues_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/convertValues_test.go b/runtime/convertValues_test.go index 0c30181a76..718fa9cf73 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -3067,7 +3067,7 @@ func TestRuntimeComplexStructArgumentPassing(t *testing.T) { self.h = /public/foo self.i = /private/foo self.j = nil - self.k = "hashable_struct_value" + self.k = "hashable_struct_value" } } `, From b619887b81578dc4cb9a52d458b11e3797a46904 Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Thu, 26 Oct 2023 23:05:30 +0530 Subject: [PATCH 10/15] Remove redundant change --- runtime/sema/type_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index f4ec6bc233..0639b2c5c9 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -778,11 +778,10 @@ func TestCommonSuperType(t *testing.T) { testLeastCommonSuperType := func(t *testing.T, tests []testCase) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - tt := LeastCommonSuperType(test.types...) assert.Equal( t, test.expectedSuperType, - tt, + LeastCommonSuperType(test.types...), ) }) } From 4d6442b9c9e9497c5a122895a0708115fa8e0229 Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Thu, 26 Oct 2023 23:09:29 +0530 Subject: [PATCH 11/15] Define HashableStruct in Cadence and auto generate --- runtime/sema/hashable_struct.cdc | 4 ++++ ...estruct_type.go => hashable_struct.gen.go} | 22 +++++++++---------- runtime/sema/hashable_struct.go | 21 ++++++++++++++++++ 3 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 runtime/sema/hashable_struct.cdc rename runtime/sema/{hashablestruct_type.go => hashable_struct.gen.go} (66%) create mode 100644 runtime/sema/hashable_struct.go diff --git a/runtime/sema/hashable_struct.cdc b/runtime/sema/hashable_struct.cdc new file mode 100644 index 0000000000..486041fffd --- /dev/null +++ b/runtime/sema/hashable_struct.cdc @@ -0,0 +1,4 @@ + +/// HashableStructType represents the type that can be used as a Dictionary key type. +pub struct HashableStruct: Storable, Exportable, Importable { +} diff --git a/runtime/sema/hashablestruct_type.go b/runtime/sema/hashable_struct.gen.go similarity index 66% rename from runtime/sema/hashablestruct_type.go rename to runtime/sema/hashable_struct.gen.go index 9977090f61..48da711c33 100644 --- a/runtime/sema/hashablestruct_type.go +++ b/runtime/sema/hashable_struct.gen.go @@ -1,3 +1,4 @@ +// Code generated from hashable_struct.cdc. DO NOT EDIT. /* * Cadence - The resource-oriented smart contract programming language * @@ -18,18 +19,17 @@ package sema -// HashableStructType represents the type that can be used as a Dictionary key type. +const HashableStructTypeName = "HashableStruct" + var HashableStructType = &SimpleType{ - Name: "HashableStruct", - QualifiedName: "HashableStruct", - TypeID: "HashableStruct", + Name: HashableStructTypeName, + QualifiedName: HashableStructTypeName, + TypeID: HashableStructTypeName, tag: HashableStructTypeTag, IsResource: false, - // The actual storability of a value is checked at run-time - Storable: true, - Equatable: false, - Comparable: false, - Exportable: true, - // The actual importability is checked at runtime - Importable: true, + Storable: true, + Equatable: false, + Comparable: false, + Exportable: true, + Importable: true, } diff --git a/runtime/sema/hashable_struct.go b/runtime/sema/hashable_struct.go new file mode 100644 index 0000000000..bdb7b05ebe --- /dev/null +++ b/runtime/sema/hashable_struct.go @@ -0,0 +1,21 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sema + +//go:generate go run ./gen hashable_struct.cdc hashable_struct.gen.go From 9ea3e618ebdf3659d5c2aa9ddf9dc4d1ec3a4315 Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Thu, 26 Oct 2023 23:39:27 +0530 Subject: [PATCH 12/15] Add hashable_struct_test in sema --- runtime/convertValues_test.go | 32 +++++----- runtime/tests/checker/hashable_struct_test.go | 58 +++++++++++++++++++ 2 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 runtime/tests/checker/hashable_struct_test.go diff --git a/runtime/convertValues_test.go b/runtime/convertValues_test.go index 718fa9cf73..6e2bc17784 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -3269,30 +3269,28 @@ func TestRuntimeComplexStructWithHashableStructFields(t *testing.T) { script := fmt.Sprintf( ` - pub fun main(arg: %[1]s): %[1]s { - + access(all) fun main(arg: %[1]s): %[1]s { if !arg.isInstance(Type<%[1]s>()) { panic("Not a %[1]s value") } - return arg } - pub struct Foo { - pub var a: HashableStruct? - pub var b: {String: HashableStruct} - pub var c: [HashableStruct] - pub var d: [HashableStruct; 2] - pub var e: HashableStruct + access(all) struct Foo { + access(all) var a: HashableStruct? + access(all) var b: {String: HashableStruct} + access(all) var c: [HashableStruct] + access(all) var d: [HashableStruct; 2] + access(all) var e: HashableStruct - init() { - self.a = "Hello" - self.b = {} - self.c = [] - self.d = ["foo", "bar"] - self.e = /storage/foo - } - } + init() { + self.a = "Hello" + self.b = {} + self.c = [] + self.d = ["foo", "bar"] + self.e = /storage/foo + } + } `, "Foo", ) diff --git a/runtime/tests/checker/hashable_struct_test.go b/runtime/tests/checker/hashable_struct_test.go new file mode 100644 index 0000000000..954997a4b4 --- /dev/null +++ b/runtime/tests/checker/hashable_struct_test.go @@ -0,0 +1,58 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package checker + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/onflow/cadence/runtime/sema" +) + +func TestCheckHashableStruct(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + let a: HashableStruct = 1 + let b: HashableStruct = true + `) + + assert.NoError(t, err) +} + +func TestCheckInvalidHashableStruct(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R {} + + let a: HashableStruct = <-create R() + let b: HashableStruct = [<-create R()] + let c: HashableStruct = {1: true} + `) + + errs := RequireCheckerErrors(t, err, 3) + + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) + assert.IsType(t, &sema.TypeMismatchError{}, errs[2]) +} From a067ed60cd6cf405929039b6cd3d56945923155a Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Fri, 27 Oct 2023 00:05:18 +0530 Subject: [PATCH 13/15] Update ccf test for HashableStruct --- encoding/ccf/ccf_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/encoding/ccf/ccf_test.go b/encoding/ccf/ccf_test.go index 5970047e31..03fab9d6f5 100644 --- a/encoding/ccf/ccf_test.go +++ b/encoding/ccf/ccf_test.go @@ -9249,6 +9249,38 @@ func TestEncodeType(t *testing.T) { }) + t.Run("with static HashableStruct", func(t *testing.T) { + t.Parallel() + + testEncodeAndDecode( + t, + cadence.TypeValue{ + StaticType: cadence.HashableStructType{}, + }, + []byte{ + // language=json, format=json-cdc + // {"type":"Type","value":{"staticType":{"kind":"Struct", "type" : {"kind" : "HashableStruct"}}}} + // + // language=edn, format=ccf + // 130([137(41), 185(56)]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 elements follow + 0x82, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Meta type ID (41) + 0x18, 0x29, + // tag + 0xd8, ccf.CBORTagSimpleTypeValue, + // HashableStruct type (56) + 0x18, 0x38, + }, + ) + }) + t.Run("with static function", func(t *testing.T) { t.Parallel() From 3e3f169e844a8f82805174ed3481e77261cee311 Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Fri, 27 Oct 2023 00:06:02 +0530 Subject: [PATCH 14/15] Remove test for HashableStruct inside a static function --- encoding/ccf/ccf_test.go | 68 ---------------------------------------- 1 file changed, 68 deletions(-) diff --git a/encoding/ccf/ccf_test.go b/encoding/ccf/ccf_test.go index 03fab9d6f5..d2493ea578 100644 --- a/encoding/ccf/ccf_test.go +++ b/encoding/ccf/ccf_test.go @@ -9351,74 +9351,6 @@ func TestEncodeType(t *testing.T) { 0x04, }, ) - - testEncodeAndDecode( - t, - cadence.TypeValue{ - StaticType: &cadence.FunctionType{ - TypeParameters: []cadence.TypeParameter{ - {Name: "T", TypeBound: cadence.HashableStructType{}}, - }, - Parameters: []cadence.Parameter{ - {Label: "qux", Identifier: "baz", Type: cadence.StringType{}}, - }, - ReturnType: cadence.IntType{}, - }, - }, - []byte{ - // language=json, format=json-cdc - // {"value":{"staticType":{"kind":"Function","typeParameters":[{"name":"T","typeBound":{"kind":"HashableStruct"}}],"parameters":[{"type":{"kind":"String"},"label":"qux","id":"baz"}],"return":{"kind":"Int"}}},"type":"Type"} - // - // language=edn, format=ccf - // 130([137(41), 193([[["T", 185(56)]], [["qux", "baz", 185(1)]], 185(4)])]) - // - // language=cbor, format=ccf - // tag - 0xd8, ccf.CBORTagTypeAndValue, - // array, 2 elements follow - 0x82, - // tag - 0xd8, ccf.CBORTagSimpleType, - // Meta type ID (41) - 0x18, 0x29, - // tag - 0xd8, ccf.CBORTagFunctionTypeValue, - // array, 3 elements follow - 0x83, - // array, 1 elements follow - 0x81, - // array, 2 elements follow - 0x82, - // string, 1 byte follows - 0x61, - // "T" - 0x54, - // tag - 0xd8, ccf.CBORTagSimpleTypeValue, - // HashableStruct type (56) - 0x18, 0x38, - // array, 1 elements follow - 0x81, - // array, 3 elements follow - 0x83, - // string, 3 bytes follow - 0x63, - // qux - 0x71, 0x75, 0x78, - // string, 3 bytes follow - 0x63, - // bax - 0x62, 0x61, 0x7a, - // tag - 0xd8, ccf.CBORTagSimpleTypeValue, - // String type (1) - 0x01, - // tag - 0xd8, ccf.CBORTagSimpleTypeValue, - // Int type ID (4) - 0x04, - }, - ) }) t.Run("with static function nil type bound", func(t *testing.T) { From 8a16ed2b3fc63904441e9e997611b291b1214abc Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Fri, 27 Oct 2023 21:42:28 +0530 Subject: [PATCH 15/15] Remove IsSuperTypeOf from simple type --- runtime/sema/simple_type.go | 1 - runtime/sema/storable_type.go | 4 ---- runtime/sema/type.go | 10 ++++------ 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/runtime/sema/simple_type.go b/runtime/sema/simple_type.go index 1e8b30b0df..5979d8f6de 100644 --- a/runtime/sema/simple_type.go +++ b/runtime/sema/simple_type.go @@ -35,7 +35,6 @@ type ValueIndexingInfo struct { // SimpleType represents a simple nominal type. type SimpleType struct { ValueIndexingInfo ValueIndexingInfo - IsSuperTypeOf func(subType Type) bool NestedTypes *StringTypeOrderedMap memberResolvers map[string]MemberResolver Members func(*SimpleType) map[string]MemberResolver diff --git a/runtime/sema/storable_type.go b/runtime/sema/storable_type.go index b3607b65a3..0c13b0e441 100644 --- a/runtime/sema/storable_type.go +++ b/runtime/sema/storable_type.go @@ -39,8 +39,4 @@ var StorableType = &SimpleType{ Comparable: false, Exportable: false, Importable: false, - IsSuperTypeOf: func(subType Type) bool { - storableResults := map[*Member]bool{} - return subType.IsStorable(storableResults) - }, } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 03e673b224..ac18ca933e 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -5729,6 +5729,10 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { return IsSubType(subType, StoragePathType) || IsSubType(subType, CapabilityPathType) + case StorableType: + storableResults := map[*Member]bool{} + return subType.IsStorable(storableResults) + case CapabilityPathType: return IsSubType(subType, PrivatePathType) || IsSubType(subType, PublicPathType) @@ -6333,12 +6337,6 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { } } } - - case *SimpleType: - if typedSuperType.IsSuperTypeOf == nil { - return false - } - return typedSuperType.IsSuperTypeOf(subType) } // TODO: enforce type arguments, remove this rule