From 91005989eedd43a044d7d7f9df32c7e932a97166 Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Mon, 9 Sep 2024 15:01:31 -0400 Subject: [PATCH 1/8] Add tests for unwrapping optionals with dynamic casts --- .../tests/interpreter/dynamic_casting_test.go | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/runtime/tests/interpreter/dynamic_casting_test.go b/runtime/tests/interpreter/dynamic_casting_test.go index 933e4f12f8..c012f395b3 100644 --- a/runtime/tests/interpreter/dynamic_casting_test.go +++ b/runtime/tests/interpreter/dynamic_casting_test.go @@ -3870,3 +3870,43 @@ func TestInterpretDynamicCastingReferenceCasting(t *testing.T) { } }) } +func TestInterpretDynamicCastingOptionalUnwrapping(t *testing.T) { + + t.Parallel() + + t.Run("as?", func(t *testing.T) { + inter := parseCheckAndInterpret(t, + fmt.Sprintf(` + let x: Int? = 42 + let y: Int? = x %[1]s Int + `, + "as?", + ), + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredIntValueFromInt64(42), + inter.Globals.Get("y").GetValue(inter), + ) + }) + + t.Run("as!", func(t *testing.T) { + inter := parseCheckAndInterpret(t, + fmt.Sprintf(` + let x: Int? = 42 + let y: Int = x %[1]s Int + `, + "as!", + ), + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredIntValueFromInt64(42), + inter.Globals.Get("y").GetValue(inter), + ) + }) +} From 352c9eb21bf7f4211d75b3db0b887af8ab9f97fd Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Tue, 10 Sep 2024 13:41:24 -0400 Subject: [PATCH 2/8] Add more tests. --- .../tests/interpreter/dynamic_casting_test.go | 144 ++++++++++++++---- 1 file changed, 118 insertions(+), 26 deletions(-) diff --git a/runtime/tests/interpreter/dynamic_casting_test.go b/runtime/tests/interpreter/dynamic_casting_test.go index c012f395b3..74e07d4767 100644 --- a/runtime/tests/interpreter/dynamic_casting_test.go +++ b/runtime/tests/interpreter/dynamic_casting_test.go @@ -1079,17 +1079,19 @@ func TestInterpretDynamicCastingSome(t *testing.T) { t.Run(fmt.Sprintf("valid: from %s to %s", fromType, targetType), func(t *testing.T) { + code := fmt.Sprintf( + ` + let x: Int? = 42 + let y: %[1]s = x + let z: %[2]s? = y %[3]s %[2]s + `, + fromType, + targetType, + operation.Symbol(), + ) + inter := parseCheckAndInterpret(t, - fmt.Sprintf( - ` - let x: Int? = 42 - let y: %[1]s = x - let z: %[2]s? = y %[3]s %[2]s - `, - fromType, - targetType, - operation.Symbol(), - ), + code, ) expectedValue := interpreter.NewUnmeteredSomeValueNonCopying( @@ -3875,32 +3877,50 @@ func TestInterpretDynamicCastingOptionalUnwrapping(t *testing.T) { t.Parallel() t.Run("as?", func(t *testing.T) { - inter := parseCheckAndInterpret(t, - fmt.Sprintf(` - let x: Int? = 42 - let y: Int? = x %[1]s Int - `, - "as?", - ), - ) + t.Parallel() + + code := ` + let x: Int? = 42 + let y: Int? = x as? Int + ` + + inter := parseCheckAndInterpret(t, code) AssertValuesEqual( t, inter, - interpreter.NewUnmeteredIntValueFromInt64(42), + interpreter.NewUnmeteredSomeValueNonCopying(interpreter.NewUnmeteredIntValueFromInt64(42)), inter.Globals.Get("y").GetValue(inter), ) }) t.Run("as!", func(t *testing.T) { - inter := parseCheckAndInterpret(t, - fmt.Sprintf(` - let x: Int? = 42 - let y: Int = x %[1]s Int - `, - "as!", - ), + t.Parallel() + + code := ` + let x: Int? = 42 + let y: Int = x as! Int + ` + + inter := parseCheckAndInterpret(t, code) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredIntValueFromInt64(42), + inter.Globals.Get("y").GetValue(inter), ) + }) + + t.Run("multi optional as!", func(t *testing.T) { + t.Parallel() + + code := ` + let x: Int??? = 42 + let y: Int = x as! Int + ` + + inter := parseCheckAndInterpret(t, code) AssertValuesEqual( t, @@ -3909,4 +3929,76 @@ func TestInterpretDynamicCastingOptionalUnwrapping(t *testing.T) { inter.Globals.Get("y").GetValue(inter), ) }) + + t.Run("multi optional as?", func(t *testing.T) { + t.Parallel() + + code := ` + let x: Int??? = 42 + let y: Int? = x as? Int + ` + + inter := parseCheckAndInterpret(t, code) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredSomeValueNonCopying(interpreter.NewUnmeteredIntValueFromInt64(42)), + inter.Globals.Get("y").GetValue(inter), + ) + }) + + t.Run("nil as?", func(t *testing.T) { + t.Parallel() + + code := ` + let x: Int? = nil + let y: Int? = x as? Int + ` + + inter := parseCheckAndInterpret(t, code) + + AssertValuesEqual( + t, + inter, + interpreter.Nil, + inter.Globals.Get("y").GetValue(inter), + ) + }) + + t.Run("nil as!", func(t *testing.T) { + t.Parallel() + + code := ` + fun test() { + let x: Int? = nil + let y: Int = x as! Int + } + ` + + inter := parseCheckAndInterpret(t, code) + + _, err := inter.Invoke("test") + RequireError(t, err) + + assert.ErrorAs(t, err, &interpreter.ValueTransferTypeError{}) + }) + + t.Run("string as!", func(t *testing.T) { + t.Parallel() + + code := ` + fun test(): String { + let hello: String???? = "hello" + let something: AnyStruct = hello + return something as! String + } + ` + + inter := parseCheckAndInterpret(t, code) + + result, err := inter.Invoke("test") + require.NoError(t, err) + require.Equal(t, interpreter.NewUnmeteredStringValue("hello"), result) + }) } From 6a69bb1bc9a81211a91f0f24ff6aad7015fa62dc Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Tue, 10 Sep 2024 13:44:14 -0400 Subject: [PATCH 3/8] Conditionally unwrap optionals on dynamic cast --- runtime/interpreter/interpreter.go | 11 +++++++++++ runtime/interpreter/interpreter_expression.go | 10 ++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index a99f854410..806b65326e 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -4025,6 +4025,17 @@ func (interpreter *Interpreter) IsSubTypeOfSemaType(staticSubType StaticType, su return sema.IsSubType(semaSubType, superType) } +func (interpreter *Interpreter) IsUnwrappable(staticSubType StaticType, superType sema.Type) bool { + // this is a specific check for if a subtype (which must be an optional) is unwrappable to supertype + // which is essentially just unwrapping the subtype first before checking + switch staticSubType := staticSubType.(type) { + case *OptionalStaticType: + return interpreter.IsUnwrappable(staticSubType.Type, superType) + } + + return interpreter.IsSubTypeOfSemaType(staticSubType, superType) +} + func (interpreter *Interpreter) domainPaths(address common.Address, domain common.PathDomain) []Value { storageMap := interpreter.Storage().GetStorageMap(address, domain.Identifier(), false) if storageMap == nil { diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 330610d2da..8c416b7d2c 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1337,16 +1337,17 @@ func (interpreter *Interpreter) VisitCastingExpression(expression *ast.CastingEx // so we don't substitute them. valueSemaType := interpreter.SubstituteMappedEntitlements(interpreter.MustSemaTypeOfValue(value)) valueStaticType := ConvertSemaToStaticType(interpreter, valueSemaType) + isUnwrappable := interpreter.IsUnwrappable(valueStaticType, expectedType) isSubType := interpreter.IsSubTypeOfSemaType(valueStaticType, expectedType) switch expression.Operation { case ast.OperationFailableCast: - if !isSubType { + if !isSubType && !isUnwrappable { return Nil } case ast.OperationForceCast: - if !isSubType { + if !isSubType && !isUnwrappable { locationRange := LocationRange{ Location: interpreter.Location, HasPosition: expression.Expression, @@ -1363,6 +1364,11 @@ func (interpreter *Interpreter) VisitCastingExpression(expression *ast.CastingEx panic(errors.NewUnreachableError()) } + if isUnwrappable { + // dynamic cast now unboxes optionals + value = interpreter.Unbox(locationRange, value) + } + // The failable cast may upcast to an optional type, e.g. `1 as? Int?`, so box value = interpreter.ConvertAndBox(locationRange, value, valueSemaType, expectedType) From e14a7bb880c8c62f89eb502330f34879a1d339eb Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Tue, 10 Sep 2024 15:07:38 -0400 Subject: [PATCH 4/8] Fix handling of nil and ambiguous tests --- runtime/interpreter/interpreter_expression.go | 6 ++++++ runtime/tests/interpreter/dynamic_casting_test.go | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 8c416b7d2c..a1c6ef0cf1 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1367,6 +1367,12 @@ func (interpreter *Interpreter) VisitCastingExpression(expression *ast.CastingEx if isUnwrappable { // dynamic cast now unboxes optionals value = interpreter.Unbox(locationRange, value) + + // we should not process nil further + switch value.(type) { + case NilValue: + return Nil + } } // The failable cast may upcast to an optional type, e.g. `1 as? Int?`, so box diff --git a/runtime/tests/interpreter/dynamic_casting_test.go b/runtime/tests/interpreter/dynamic_casting_test.go index 74e07d4767..44db1a4514 100644 --- a/runtime/tests/interpreter/dynamic_casting_test.go +++ b/runtime/tests/interpreter/dynamic_casting_test.go @@ -1105,8 +1105,7 @@ func TestInterpretDynamicCastingSome(t *testing.T) { inter.Globals.Get("y").GetValue(inter), ) - if targetType == sema.AnyStructType && !returnsOptional { - + if _, ok := targetType.(*sema.OptionalType); !ok { AssertValuesEqual( t, inter, From 0b731acae1480ce6282b55ff6fa9cfb5a228d356 Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Tue, 10 Sep 2024 15:24:08 -0400 Subject: [PATCH 5/8] Replace single statement switches with if --- runtime/interpreter/interpreter.go | 3 +-- runtime/interpreter/interpreter_expression.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 806b65326e..e5b1e9245f 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -4028,8 +4028,7 @@ func (interpreter *Interpreter) IsSubTypeOfSemaType(staticSubType StaticType, su func (interpreter *Interpreter) IsUnwrappable(staticSubType StaticType, superType sema.Type) bool { // this is a specific check for if a subtype (which must be an optional) is unwrappable to supertype // which is essentially just unwrapping the subtype first before checking - switch staticSubType := staticSubType.(type) { - case *OptionalStaticType: + if staticSubType, ok := staticSubType.(*OptionalStaticType); ok { return interpreter.IsUnwrappable(staticSubType.Type, superType) } diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index a1c6ef0cf1..4417560925 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1369,8 +1369,7 @@ func (interpreter *Interpreter) VisitCastingExpression(expression *ast.CastingEx value = interpreter.Unbox(locationRange, value) // we should not process nil further - switch value.(type) { - case NilValue: + if _, ok := value.(NilValue); ok { return Nil } } From d4369cf36d56fefb303013ce0fbdb10ec597278a Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Wed, 11 Sep 2024 15:14:19 -0400 Subject: [PATCH 6/8] Revert breaking change, clean up code Preserve optionals in anystruct, move value unboxing earlier in casting function. --- runtime/interpreter/interpreter.go | 10 --- runtime/interpreter/interpreter_expression.go | 21 +++--- .../tests/interpreter/dynamic_casting_test.go | 71 +++++++++++++++---- 3 files changed, 65 insertions(+), 37 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index e5b1e9245f..a99f854410 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -4025,16 +4025,6 @@ func (interpreter *Interpreter) IsSubTypeOfSemaType(staticSubType StaticType, su return sema.IsSubType(semaSubType, superType) } -func (interpreter *Interpreter) IsUnwrappable(staticSubType StaticType, superType sema.Type) bool { - // this is a specific check for if a subtype (which must be an optional) is unwrappable to supertype - // which is essentially just unwrapping the subtype first before checking - if staticSubType, ok := staticSubType.(*OptionalStaticType); ok { - return interpreter.IsUnwrappable(staticSubType.Type, superType) - } - - return interpreter.IsSubTypeOfSemaType(staticSubType, superType) -} - func (interpreter *Interpreter) domainPaths(address common.Address, domain common.PathDomain) []Value { storageMap := interpreter.Storage().GetStorageMap(address, domain.Identifier(), false) if storageMap == nil { diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 4417560925..9b5961d048 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1335,19 +1335,24 @@ func (interpreter *Interpreter) VisitCastingExpression(expression *ast.CastingEx // thus this is the only place where it becomes necessary to "instantiate" the result of a map to its // concrete outputs. In other places (e.g. interface conformance checks) we want to leave maps generic, // so we don't substitute them. + + // if the target is anystruct or anyresource we want to preserve optionals + if !(expectedType == sema.AnyStructType || expectedType == sema.AnyResourceType) { + // otherwise dynamic cast now always unboxes optionals + value = interpreter.Unbox(locationRange, value) + } valueSemaType := interpreter.SubstituteMappedEntitlements(interpreter.MustSemaTypeOfValue(value)) valueStaticType := ConvertSemaToStaticType(interpreter, valueSemaType) - isUnwrappable := interpreter.IsUnwrappable(valueStaticType, expectedType) isSubType := interpreter.IsSubTypeOfSemaType(valueStaticType, expectedType) switch expression.Operation { case ast.OperationFailableCast: - if !isSubType && !isUnwrappable { + if !isSubType { return Nil } case ast.OperationForceCast: - if !isSubType && !isUnwrappable { + if !isSubType { locationRange := LocationRange{ Location: interpreter.Location, HasPosition: expression.Expression, @@ -1364,16 +1369,6 @@ func (interpreter *Interpreter) VisitCastingExpression(expression *ast.CastingEx panic(errors.NewUnreachableError()) } - if isUnwrappable { - // dynamic cast now unboxes optionals - value = interpreter.Unbox(locationRange, value) - - // we should not process nil further - if _, ok := value.(NilValue); ok { - return Nil - } - } - // The failable cast may upcast to an optional type, e.g. `1 as? Int?`, so box value = interpreter.ConvertAndBox(locationRange, value, valueSemaType, expectedType) diff --git a/runtime/tests/interpreter/dynamic_casting_test.go b/runtime/tests/interpreter/dynamic_casting_test.go index 44db1a4514..30b323dee4 100644 --- a/runtime/tests/interpreter/dynamic_casting_test.go +++ b/runtime/tests/interpreter/dynamic_casting_test.go @@ -1079,19 +1079,17 @@ func TestInterpretDynamicCastingSome(t *testing.T) { t.Run(fmt.Sprintf("valid: from %s to %s", fromType, targetType), func(t *testing.T) { - code := fmt.Sprintf( - ` - let x: Int? = 42 - let y: %[1]s = x - let z: %[2]s? = y %[3]s %[2]s - `, - fromType, - targetType, - operation.Symbol(), - ) - inter := parseCheckAndInterpret(t, - code, + fmt.Sprintf( + ` + let x: Int? = 42 + let y: %[1]s = x + let z: %[2]s? = y %[3]s %[2]s + `, + fromType, + targetType, + operation.Symbol(), + ), ) expectedValue := interpreter.NewUnmeteredSomeValueNonCopying( @@ -1105,7 +1103,8 @@ func TestInterpretDynamicCastingSome(t *testing.T) { inter.Globals.Get("y").GetValue(inter), ) - if _, ok := targetType.(*sema.OptionalType); !ok { + if targetType == sema.AnyStructType && !returnsOptional { + AssertValuesEqual( t, inter, @@ -3980,7 +3979,7 @@ func TestInterpretDynamicCastingOptionalUnwrapping(t *testing.T) { _, err := inter.Invoke("test") RequireError(t, err) - assert.ErrorAs(t, err, &interpreter.ValueTransferTypeError{}) + assert.ErrorAs(t, err, &interpreter.ForceCastTypeMismatchError{}) }) t.Run("string as!", func(t *testing.T) { @@ -4000,4 +3999,48 @@ func TestInterpretDynamicCastingOptionalUnwrapping(t *testing.T) { require.NoError(t, err) require.Equal(t, interpreter.NewUnmeteredStringValue("hello"), result) }) + + t.Run("return nested optional as?", func(t *testing.T) { + t.Parallel() + + code := ` + let x: Int??? = 42 + let y: Int?? = x as? Int? + ` + + inter := parseCheckAndInterpret(t, code) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredIntValueFromInt64(42), + ), + ), + inter.Globals.Get("y").GetValue(inter), + ) + }) + + t.Run("return nested optional as!", func(t *testing.T) { + t.Parallel() + + code := ` + let x: Int??? = 42 + let y: Int?? = x as! Int?? + ` + + inter := parseCheckAndInterpret(t, code) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredIntValueFromInt64(42), + ), + ), + inter.Globals.Get("y").GetValue(inter), + ) + }) } From adc34040d9b341e36a48739632876b26994d1d0c Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Thu, 12 Sep 2024 15:53:46 -0400 Subject: [PATCH 7/8] Add tests for resources, unwrap expected type --- runtime/interpreter/interpreter_expression.go | 3 +- .../tests/interpreter/dynamic_casting_test.go | 188 ++++++++++++++++++ 2 files changed, 190 insertions(+), 1 deletion(-) diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 9b5961d048..d2894749bf 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1337,7 +1337,8 @@ func (interpreter *Interpreter) VisitCastingExpression(expression *ast.CastingEx // so we don't substitute them. // if the target is anystruct or anyresource we want to preserve optionals - if !(expectedType == sema.AnyStructType || expectedType == sema.AnyResourceType) { + unboxedExpectedType := sema.UnwrapOptionalType(expectedType) + if !(unboxedExpectedType == sema.AnyStructType || unboxedExpectedType == sema.AnyResourceType) { // otherwise dynamic cast now always unboxes optionals value = interpreter.Unbox(locationRange, value) } diff --git a/runtime/tests/interpreter/dynamic_casting_test.go b/runtime/tests/interpreter/dynamic_casting_test.go index 30b323dee4..b2bbf82829 100644 --- a/runtime/tests/interpreter/dynamic_casting_test.go +++ b/runtime/tests/interpreter/dynamic_casting_test.go @@ -4043,4 +4043,192 @@ func TestInterpretDynamicCastingOptionalUnwrapping(t *testing.T) { inter.Globals.Get("y").GetValue(inter), ) }) + + t.Run("AnyResource as!", func(t *testing.T) { + t.Parallel() + + code := ` + resource R {} + + let x: @R? <- create R() + let y: @AnyResource <- x as! @AnyResource + ` + + inter := parseCheckAndInterpret(t, code) + + value := inter.Globals.Get("y").GetValue(inter) + + require.IsType(t, + &interpreter.SomeValue{}, + value, + ) + + require.IsType(t, + &interpreter.CompositeValue{}, + value.(*interpreter.SomeValue). + InnerValue(inter, interpreter.EmptyLocationRange), + ) + }) + + t.Run("resource cast as!", func(t *testing.T) { + t.Parallel() + + code := ` + resource R {} + + let x: @R?? <- create R() + let y: @R <- x as! @R + ` + + inter := parseCheckAndInterpret(t, code) + + value := inter.Globals.Get("y").GetValue(inter) + + require.IsType(t, + &interpreter.CompositeValue{}, + value, + ) + }) + + t.Run("resource cast as?", func(t *testing.T) { + t.Parallel() + + code := ` + resource R {} + + fun test(): @R? { + + let x: @R? <- create R() + + if let z <- x as? @R { + return <-z + } else { + destroy x + return nil + } + } + + ` + + inter := parseCheckAndInterpret(t, code) + + result, err := inter.Invoke("test") + require.NoError(t, err) + require.IsType(t, + &interpreter.SomeValue{}, + result, + ) + + require.IsType(t, + &interpreter.CompositeValue{}, + result.(*interpreter.SomeValue). + InnerValue(inter, interpreter.EmptyLocationRange), + ) + + }) + + t.Run("resource cast AnyResource as?", func(t *testing.T) { + t.Parallel() + + code := ` + resource R {} + + fun test(): @AnyResource? { + + let x: @R? <- create R() + + if let z <- x as? @AnyResource { + return <-z + } else { + destroy x + return nil + } + } + + ` + + inter := parseCheckAndInterpret(t, code) + + result, err := inter.Invoke("test") + require.NoError(t, err) + require.IsType(t, + &interpreter.SomeValue{}, + result, + ) + + require.IsType(t, + &interpreter.CompositeValue{}, + result.(*interpreter.SomeValue). + InnerValue(inter, interpreter.EmptyLocationRange), + ) + + }) + + t.Run("AnyStruct boxing as!", func(t *testing.T) { + t.Parallel() + + code := ` + let x: Int? = 42 + let y: AnyStruct??? = x as! AnyStruct?? + ` + + inter := parseCheckAndInterpret(t, code) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredIntValueFromInt64(42), + ), + ), + ), + inter.Globals.Get("y").GetValue(inter), + ) + }) + + t.Run("AnyStruct unboxing as!", func(t *testing.T) { + t.Parallel() + + code := ` + let x: Int??? = 42 + let y: AnyStruct = x as! AnyStruct?? + ` + + inter := parseCheckAndInterpret(t, code) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredIntValueFromInt64(42), + ), + ), + ), + inter.Globals.Get("y").GetValue(inter), + ) + }) + + t.Run("AnyStruct cast to Int? as!", func(t *testing.T) { + t.Parallel() + + code := ` + let x: AnyStruct = 42 + let y: Int? = x as! Int? + ` + + inter := parseCheckAndInterpret(t, code) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredIntValueFromInt64(42), + ), + inter.Globals.Get("y").GetValue(inter), + ) + }) } From 29456622c108113a75d01faf3aba0b039a193c91 Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Fri, 13 Sep 2024 09:59:17 -0400 Subject: [PATCH 8/8] Add requested tests for optional boxing --- .../tests/interpreter/dynamic_casting_test.go | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/runtime/tests/interpreter/dynamic_casting_test.go b/runtime/tests/interpreter/dynamic_casting_test.go index b2bbf82829..2a3a96292f 100644 --- a/runtime/tests/interpreter/dynamic_casting_test.go +++ b/runtime/tests/interpreter/dynamic_casting_test.go @@ -4022,6 +4022,26 @@ func TestInterpretDynamicCastingOptionalUnwrapping(t *testing.T) { ) }) + t.Run("return optional as!", func(t *testing.T) { + t.Parallel() + + code := ` + let x: Int??? = 42 + let y: Int? = x as! Int? + ` + + inter := parseCheckAndInterpret(t, code) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredIntValueFromInt64(42), + ), + inter.Globals.Get("y").GetValue(inter), + ) + }) + t.Run("return nested optional as!", func(t *testing.T) { t.Parallel() @@ -4044,6 +4064,30 @@ func TestInterpretDynamicCastingOptionalUnwrapping(t *testing.T) { ) }) + t.Run("return optional as?", func(t *testing.T) { + t.Parallel() + + code := ` + let x: Int??? = 42 + let y: Int??? = x as? Int?? + ` + + inter := parseCheckAndInterpret(t, code) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredIntValueFromInt64(42), + ), + ), + ), + inter.Globals.Get("y").GetValue(inter), + ) + }) + t.Run("AnyResource as!", func(t *testing.T) { t.Parallel()