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

Improve reference expressions #2010

Merged
merged 7 commits into from Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 28 additions & 13 deletions docs/language/references.md
Expand Up @@ -7,30 +7,45 @@ A reference can be used to access fields and call functions on the referenced ob

References are **copied**, i.e. they are value types.

References are created by using the `&` operator, followed by the object,
the `as` keyword, and the type through which they should be accessed.
The given type must be a supertype of the referenced object's type.

References have the type `&T`, where `T` is the type of the referenced object.

References are created using the `&` operator.
The reference type must be explicitly provided,
for example through a type annotation on a variable declaration,
or a type assertion using the `as` operator.

```cadence
let hello = "Hello"

// Create a reference to the "Hello" string, typed as a `String`
// Create a reference to the `String` `hello`.
// Provide the reference type `&String` using a type assertion
//
let helloRef: &String = &hello as &String
let helloRef = &hello as &String

helloRef.length // is `5`

// Create another reference to the `String` `hello`.
// Provide the reference type `&String` using a type annotation instead
//
let alsoHelloRef: &String = &hello

// Invalid: Cannot create a reference without an explicit type
//
let unknownRef = &hello
```

The reference type must be a supertype of the referenced object's type.

```cadence
// Invalid: Cannot create a reference to `hello`
// typed as `&Int`, as it has type `String`
//
let intRef: &Int = &hello as &Int
let intRef = &hello as &Int
```

If you attempt to reference an optional value, you will receive an optional reference.
If the referenced value is nil, the reference itself will be nil. If the referenced value
exists, then forcing the optional reference will yield a reference to that value:
When creating a reference to an optional value, the result is an optional reference.
If the referenced value is nil, the resulting reference itself will be nil.
If the referenced value exists, then forcing the optional reference will yield a reference to that value:

```cadence
let nilValue: String? = nil
Expand Down Expand Up @@ -98,7 +113,7 @@ Also, authorized references are subtypes of unauthorized references.
// typed with the restricted type `&{HasCount}`,
// i.e. some resource that conforms to the `HasCount` interface
//
let countRef: &{HasCount} = &counter as &{HasCount}
let countRef = &counter as &{HasCount}

countRef.count // is `43`

Expand All @@ -120,7 +135,7 @@ let counterRef2: &Counter = countRef as? &Counter
// again with the restricted type `{HasCount}`, i.e. some resource
// that conforms to the `HasCount` interface
//
let authCountRef: auth &{HasCount} = &counter as auth &{HasCount}
let authCountRef = &counter as auth &{HasCount}

// Conditionally downcast to reference type `&Counter`.
// This is valid, because the reference `authCountRef` is authorized
Expand All @@ -134,5 +149,5 @@ counterRef3.increment()
counterRef3.count // is `44`
```

References are ephemeral, i.e they cannot be [stored](accounts#account-storage).
References are ephemeral, i.e. they cannot be [stored](accounts#account-storage).
Instead, consider [storing a capability and borrowing it](capability-based-access-control) when needed.
3 changes: 2 additions & 1 deletion runtime/account_test.go
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
"testing"

"github.com/onflow/cadence/runtime/errors"
"github.com/onflow/cadence/runtime/interpreter"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -816,7 +817,7 @@ func encodeArgs(argValues []cadence.Value) [][]byte {
var err error
args[i], err = json.Encode(arg)
if err != nil {
panic(fmt.Errorf("broken test: invalid argument: %w", err))
panic(errors.NewUnexpectedError("broken test: invalid argument: %w", err))
}
}
return args
Expand Down
10 changes: 1 addition & 9 deletions runtime/ast/expression.go
Expand Up @@ -1739,7 +1739,6 @@ func (*DestroyExpression) precedence() precedence {

type ReferenceExpression struct {
Expression Expression
Type Type `json:"TargetType"`
StartPos Position `json:"-"`
}

Expand All @@ -1749,14 +1748,12 @@ var _ Expression = &ReferenceExpression{}
func NewReferenceExpression(
gauge common.MemoryGauge,
expression Expression,
targetType Type,
startPos Position,
) *ReferenceExpression {
common.UseMemory(gauge, common.ReferenceExpressionMemoryUsage)

return &ReferenceExpression{
Expression: expression,
Type: targetType,
StartPos: startPos,
}
}
Expand All @@ -1779,7 +1776,6 @@ func (e *ReferenceExpression) String() string {
}

var referenceExpressionRefOperatorDoc prettier.Doc = prettier.Text("&")
var referenceExpressionAsOperatorDoc prettier.Doc = prettier.Text("as")

func (e *ReferenceExpression) Doc() prettier.Doc {
doc := parenthesizedExpressionDoc(
Expand All @@ -1793,10 +1789,6 @@ func (e *ReferenceExpression) Doc() prettier.Doc {
prettier.Group{
Doc: doc,
},
prettier.Line{},
referenceExpressionAsOperatorDoc,
prettier.Line{},
e.Type.Doc(),
},
}
}
Expand All @@ -1806,7 +1798,7 @@ func (e *ReferenceExpression) StartPosition() Position {
}

func (e *ReferenceExpression) EndPosition(memoryGauge common.MemoryGauge) Position {
return e.Type.EndPosition(memoryGauge)
return e.Expression.EndPosition(memoryGauge)
}

func (e *ReferenceExpression) MarshalJSON() ([]byte, error) {
Expand Down
120 changes: 4 additions & 116 deletions runtime/ast/expression_test.go
Expand Up @@ -4091,12 +4091,6 @@ func TestReferenceExpression_MarshalJSON(t *testing.T) {
Pos: Position{Offset: 1, Line: 2, Column: 3},
},
},
Type: &NominalType{
Identifier: Identifier{
Identifier: "AB",
Pos: Position{Offset: 4, Line: 5, Column: 6},
},
},
StartPos: Position{Offset: 7, Line: 8, Column: 9},
}

Expand All @@ -4117,18 +4111,8 @@ func TestReferenceExpression_MarshalJSON(t *testing.T) {
"StartPos": {"Offset": 1, "Line": 2, "Column": 3},
"EndPos": {"Offset": 6, "Line": 2, "Column": 8}
},
"TargetType": {
"Type": "NominalType",
"Identifier": {
"Identifier": "AB",
"StartPos": {"Offset": 4, "Line": 5, "Column": 6},
"EndPos": {"Offset": 5, "Line": 5, "Column": 7}
},
"StartPos": {"Offset": 4, "Line": 5, "Column": 6},
"EndPos": {"Offset": 5, "Line": 5, "Column": 7}
},
"StartPos": {"Offset": 7, "Line": 8, "Column": 9},
"EndPos": {"Offset": 5, "Line": 5, "Column": 7}
"EndPos": {"Offset": 6, "Line": 2, "Column": 8}
}
`,
string(actual),
Expand All @@ -4149,14 +4133,6 @@ func TestReferenceExpression_Doc(t *testing.T) {
Value: big.NewInt(42),
Base: 10,
},
Type: &ReferenceType{
Authorized: true,
Type: &NominalType{
Identifier: Identifier{
Identifier: "Int",
},
},
},
}

assert.Equal(t,
Expand All @@ -4166,14 +4142,6 @@ func TestReferenceExpression_Doc(t *testing.T) {
prettier.Group{
Doc: prettier.Text("42"),
},
prettier.Line{},
prettier.Text("as"),
prettier.Line{},
prettier.Concat{
prettier.Text("auth "),
prettier.Text("&"),
prettier.Text("Int"),
},
},
},
expr.Doc(),
Expand All @@ -4191,22 +4159,6 @@ func TestReferenceExpression_Doc(t *testing.T) {
Value: big.NewInt(42),
Base: 10,
},
Type: &ReferenceType{
Authorized: true,
Type: &NominalType{
Identifier: Identifier{
Identifier: "AnyStruct",
},
},
},
},
Type: &ReferenceType{
Authorized: true,
Type: &NominalType{
Identifier: Identifier{
Identifier: "XYZ",
},
},
},
}

Expand All @@ -4221,25 +4173,9 @@ func TestReferenceExpression_Doc(t *testing.T) {
prettier.Group{
Doc: prettier.Text("42"),
},
prettier.Line{},
prettier.Text("as"),
prettier.Line{},
prettier.Concat{
prettier.Text("auth "),
prettier.Text("&"),
prettier.Text("AnyStruct"),
},
},
},
},
prettier.Line{},
prettier.Text("as"),
prettier.Line{},
prettier.Concat{
prettier.Text("auth "),
prettier.Text("&"),
prettier.Text("XYZ"),
},
},
},
expr.Doc(),
Expand All @@ -4264,14 +4200,6 @@ func TestReferenceExpression_Doc(t *testing.T) {
},
},
},
Type: &ReferenceType{
Authorized: true,
Type: &NominalType{
Identifier: Identifier{
Identifier: "Int",
},
},
},
}

assert.Equal(t,
Expand Down Expand Up @@ -4304,14 +4232,6 @@ func TestReferenceExpression_Doc(t *testing.T) {
},
},
},
prettier.Line{},
prettier.Text("as"),
prettier.Line{},
prettier.Concat{
prettier.Text("auth "),
prettier.Text("&"),
prettier.Text("Int"),
},
},
},
expr.Doc(),
Expand All @@ -4333,18 +4253,10 @@ func TestReferenceExpression_String(t *testing.T) {
Value: big.NewInt(42),
Base: 10,
},
Type: &ReferenceType{
Authorized: true,
Type: &NominalType{
Identifier: Identifier{
Identifier: "Int",
},
},
},
}

assert.Equal(t,
"&42 as auth &Int",
"&42",
expr.String(),
)
})
Expand All @@ -4360,27 +4272,11 @@ func TestReferenceExpression_String(t *testing.T) {
Value: big.NewInt(42),
Base: 10,
},
Type: &ReferenceType{
Authorized: true,
Type: &NominalType{
Identifier: Identifier{
Identifier: "AnyStruct",
},
},
},
},
Type: &ReferenceType{
Authorized: true,
Type: &NominalType{
Identifier: Identifier{
Identifier: "XYZ",
},
},
},
}

assert.Equal(t,
"&&42 as auth &AnyStruct as auth &XYZ",
"&&42",
expr.String(),
)
})
Expand All @@ -4403,18 +4299,10 @@ func TestReferenceExpression_String(t *testing.T) {
},
},
},
Type: &ReferenceType{
Authorized: true,
Type: &NominalType{
Identifier: Identifier{
Identifier: "Int",
},
},
},
}

assert.Equal(t,
"&(foo - bar) as auth &Int",
"&(foo - bar)",
expr.String(),
)
})
Expand Down