Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce dereference operator for references #2982

Merged
merged 21 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions runtime/interpreter/interpreter_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -688,10 +688,25 @@ func (interpreter *Interpreter) VisitUnaryExpression(expression *ast.UnaryExpres
if !ok {
panic(errors.NewUnreachableError())
}
return integerValue.Negate(interpreter, LocationRange{
return integerValue.Negate(
interpreter,
LocationRange{
Location: interpreter.Location,
HasPosition: expression,
},
)

case ast.OperationMul:
referenceValue, ok := value.(ReferenceValue)
if !ok {
panic(errors.NewUnreachableError())
}
locationRange := LocationRange{
Location: interpreter.Location,
HasPosition: expression,
})
}

return DereferenceValue(interpreter, locationRange, referenceValue)

case ast.OperationMove:
interpreter.invalidateResource(value)
Expand Down
16 changes: 16 additions & 0 deletions runtime/interpreter/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -19691,6 +19691,22 @@ type ReferenceValue interface {
ReferencedValue(interpreter *Interpreter, locationRange LocationRange, errorOnFailedDereference bool) *Value
}

func DereferenceValue(
inter *Interpreter,
locationRange LocationRange,
referenceValue ReferenceValue,
) Value {
referencedValue := referenceValue.ReferencedValue(inter, locationRange, true)
return (*referencedValue).Transfer(
inter,
locationRange,
atree.Address{},
false,
nil,
nil,
)
}

// StorageReferenceValue
type StorageReferenceValue struct {
BorrowedType sema.Type
Expand Down
6 changes: 6 additions & 0 deletions runtime/parser/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,12 @@ func init() {
operation: ast.OperationMove,
})

defineExpr(unaryExpr{
tokenType: lexer.TokenStar,
bindingPower: exprLeftBindingPowerUnaryPrefix,
operation: ast.OperationMul,
})

defineExpr(postfixExpr{
tokenType: lexer.TokenExclamationMark,
bindingPower: exprLeftBindingPowerUnaryPostfix,
Expand Down
148 changes: 120 additions & 28 deletions runtime/parser/expression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2201,6 +2201,43 @@ func TestParseBlockComment(t *testing.T) {
})
}

func TestParseMulInfixExpression(t *testing.T) {

t.Parallel()

result, errs := testParseExpression(" 1 ** 2")
require.Empty(t, errs)

utils.AssertEqualWithDiff(t,
&ast.BinaryExpression{
Operation: ast.OperationMul,
Left: &ast.IntegerExpression{
PositiveLiteral: []byte("1"),
Value: big.NewInt(1),
Base: 10,
Range: ast.Range{
StartPos: ast.Position{Line: 1, Column: 1, Offset: 1},
EndPos: ast.Position{Line: 1, Column: 1, Offset: 1},
},
},
Right: &ast.UnaryExpression{
Operation: ast.OperationMul,
Expression: &ast.IntegerExpression{
PositiveLiteral: []byte("2"),
Value: big.NewInt(2),
Base: 10,
Range: ast.Range{
StartPos: ast.Position{Line: 1, Column: 6, Offset: 6},
EndPos: ast.Position{Line: 1, Column: 6, Offset: 6},
},
},
StartPos: ast.Position{Line: 1, Column: 4, Offset: 4},
},
},
result,
)
}

func BenchmarkParseInfix(b *testing.B) {

for i := 0; i < b.N; i++ {
Expand Down Expand Up @@ -4931,40 +4968,95 @@ func TestParseUnaryExpression(t *testing.T) {

t.Parallel()

const code = `
let foo = -boo
`
result, errs := testParseProgram(code)
require.Empty(t, errs)
t.Run("minus", func(t *testing.T) {

utils.AssertEqualWithDiff(t,
[]ast.Declaration{
&ast.VariableDeclaration{
Access: ast.AccessNotSpecified,
IsConstant: true,
Identifier: ast.Identifier{
Identifier: "foo",
Pos: ast.Position{Offset: 10, Line: 2, Column: 9},
t.Parallel()

const code = ` - boo`

result, errs := testParseExpression(code)
require.Empty(t, errs)

utils.AssertEqualWithDiff(t,
&ast.UnaryExpression{
Operation: ast.OperationMinus,
Expression: &ast.IdentifierExpression{
Identifier: ast.Identifier{
Identifier: "boo",
Pos: ast.Position{Offset: 3, Line: 1, Column: 3},
},
},
Transfer: &ast.Transfer{
Operation: ast.TransferOperationCopy,
Pos: ast.Position{Offset: 14, Line: 2, Column: 13},
StartPos: ast.Position{Offset: 1, Line: 1, Column: 1},
},
result,
)
})

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

t.Parallel()

const code = ` ! boo`

result, errs := testParseExpression(code)
require.Empty(t, errs)

utils.AssertEqualWithDiff(t,
&ast.UnaryExpression{
Operation: ast.OperationNegate,
Expression: &ast.IdentifierExpression{
Identifier: ast.Identifier{
Identifier: "boo",
Pos: ast.Position{Offset: 3, Line: 1, Column: 3},
},
},
Value: &ast.UnaryExpression{
Operation: ast.OperationMinus,
Expression: &ast.IdentifierExpression{
Identifier: ast.Identifier{
Identifier: "boo",
Pos: ast.Position{Offset: 17, Line: 2, Column: 16},
},
StartPos: ast.Position{Offset: 1, Line: 1, Column: 1},
},
result,
)
})

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

t.Parallel()

const code = ` * boo`

result, errs := testParseExpression(code)
require.Empty(t, errs)

utils.AssertEqualWithDiff(t,
&ast.UnaryExpression{
Operation: ast.OperationMul,
Expression: &ast.IdentifierExpression{
Identifier: ast.Identifier{
Identifier: "boo",
Pos: ast.Position{Offset: 3, Line: 1, Column: 3},
},
StartPos: ast.Position{Offset: 16, Line: 2, Column: 15},
},
StartPos: ast.Position{Offset: 6, Line: 2, Column: 5},
StartPos: ast.Position{Offset: 1, Line: 1, Column: 1},
},
},
result.Declarations(),
)
result,
)
})

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

t.Parallel()

const code = ` % boo`

_, errs := testParseExpression(code)
utils.AssertEqualWithDiff(t,
[]error{
&SyntaxError{
Message: "unexpected token in expression: '%'",
Pos: ast.Position{Line: 1, Column: 2, Offset: 2},
},
},
errs,
)
})
}

func TestParseOrExpression(t *testing.T) {
Expand Down
38 changes: 38 additions & 0 deletions runtime/sema/check_unary_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,44 @@ func (checker *Checker) VisitUnaryExpression(expression *ast.UnaryExpression) Ty
case ast.OperationMinus:
return checkExpectedType(valueType, SignedNumberType)

case ast.OperationMul:
referenceType, ok := valueType.(*ReferenceType)
if !ok {
if !valueType.IsInvalidType() {
checker.report(
&InvalidUnaryOperandError{
Operation: expression.Operation,
ExpectedTypeDescription: "reference type",
ActualType: valueType,
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
expression.Expression,
),
},
)
return InvalidType
}
}

innerType := referenceType.Type

// Allow primitives or containers of primitives.
if !IsPrimitiveOrContainerOfPrimitive(innerType) {
checker.report(
&InvalidUnaryOperandError{
Operation: expression.Operation,
ExpectedTypeDescription: "primitive or container of primitives",
ActualType: innerType,
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
expression.Expression,
),
},
)
}

return innerType

case ast.OperationMove:
if !valueType.IsInvalidType() &&
!valueType.IsResourceType() {
Expand Down
32 changes: 21 additions & 11 deletions runtime/sema/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,8 +589,9 @@ func (e *IncorrectArgumentLabelError) SuggestFixes(code string) []errors.Suggest
// InvalidUnaryOperandError

type InvalidUnaryOperandError struct {
ExpectedType Type
ActualType Type
ExpectedType Type
ExpectedTypeDescription string
ActualType Type
ast.Range
Operation ast.Operation
}
Expand All @@ -611,16 +612,25 @@ func (e *InvalidUnaryOperandError) Error() string {
}

func (e *InvalidUnaryOperandError) SecondaryError() string {
expected, actual := ErrorMessageExpectedActualTypes(
e.ExpectedType,
e.ActualType,
)
expectedType := e.ExpectedType
if expectedType != nil {
expected, actual := ErrorMessageExpectedActualTypes(
e.ExpectedType,
e.ActualType,
)

return fmt.Sprintf(
"expected `%s`, got `%s`",
expected,
actual,
)
return fmt.Sprintf(
"expected `%s`, got `%s`",
expected,
actual,
)
} else {
return fmt.Sprintf(
"expected %s, got `%s`",
e.ExpectedTypeDescription,
e.ActualType.QualifiedString(),
)
}
}

// InvalidBinaryOperandError
Expand Down
16 changes: 16 additions & 0 deletions runtime/sema/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -6448,6 +6448,22 @@ func (t *AddressType) initializeMemberResolvers() {
})
}

func IsPrimitiveOrContainerOfPrimitive(ty Type) bool {
switch ty := ty.(type) {
case *VariableSizedType:
return IsPrimitiveOrContainerOfPrimitive(ty.Type)

case *ConstantSizedType:
return IsPrimitiveOrContainerOfPrimitive(ty.Type)

case *DictionaryType:
return IsPrimitiveOrContainerOfPrimitive(ty.ValueType)

default:
return ty.IsPrimitiveType()
}
}

// IsSubType determines if the given subtype is a subtype
// of the given supertype.
//
Expand Down
Loading
Loading