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

Type check transaction role declarations #2257

Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
38a92a3
parse access modifier for transaction fields, report error in checker…
turbolent Jan 11, 2023
6317884
parse transaction role fields using transaction fields parser
turbolent Jan 11, 2023
394ff6e
improve naming
turbolent Jan 11, 2023
593b9e9
generalize prepare function checking
turbolent Jan 11, 2023
1273482
add transaction role type
turbolent Jan 11, 2023
3ef4ec4
declare transaction roles in transaction type
turbolent Jan 12, 2023
b561386
test transaction roles
turbolent Jan 12, 2023
6143f4b
Merge branch 'bastian/extended-transaction-format' into bastian/exten…
turbolent Jan 17, 2023
11dac16
allow visiting transaction role declarations
turbolent Jan 17, 2023
71ba55d
store transaction role types in elaboration
turbolent Jan 17, 2023
3a999f8
simplify container type
turbolent Jan 17, 2023
a462223
check transaction roles
turbolent Jan 17, 2023
2951125
test declaration of transaction roles as members of transaction
turbolent Jan 17, 2023
85ece62
extend test case to check for multiple roles
turbolent Jan 17, 2023
c142f71
report invalid moves of transaction roles
turbolent Jan 17, 2023
01e00b8
test transaction role prepare parameters have AuthAccount type
turbolent Jan 18, 2023
557080c
change names
turbolent Jan 18, 2023
a5ca45a
refactor repeated names into constants
turbolent Jan 18, 2023
abf74c3
add error note to indicate previous field declaration
turbolent Jan 18, 2023
f2396c7
simplify getting field declaration of transaction declaration
turbolent Jan 18, 2023
ea61344
add helper function to get field members
turbolent Jan 18, 2023
c768ac4
report invalid move of transaction role-typed values. requires checki…
turbolent Jan 18, 2023
1490851
still declare members
turbolent Jan 18, 2023
a0fa0f4
fix test, test role
turbolent Jan 18, 2023
40873ef
test transaction parameter usage in transaction role
turbolent Jan 18, 2023
17610f2
check prepare parameter lists match, create prepare functions types o…
turbolent Jan 18, 2023
babfa48
lint
turbolent Jan 19, 2023
d1de556
test role with prepare and no prepare in transaction
turbolent Jan 20, 2023
67c6922
only keep local set of roles
turbolent Jan 23, 2023
170f96a
simplify role / field clash detection
turbolent Jan 23, 2023
20d5044
simplify role duplication / field clash
turbolent Jan 23, 2023
36f2848
improve invalid value move tests
turbolent Jan 23, 2023
bb6beba
check declaration kind instead of type
turbolent Jan 24, 2023
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
4 changes: 4 additions & 0 deletions runtime/ast/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type StatementDeclarationVisitor[T any] interface {
VisitCompositeDeclaration(*CompositeDeclaration) T
VisitInterfaceDeclaration(*InterfaceDeclaration) T
VisitTransactionDeclaration(*TransactionDeclaration) T
VisitTransactionRoleDeclaration(*TransactionRoleDeclaration) T
}

type DeclarationVisitor[T any] interface {
Expand Down Expand Up @@ -78,6 +79,9 @@ func AcceptDeclaration[T any](declaration Declaration, visitor DeclarationVisito

case ElementTypeTransactionDeclaration:
return visitor.VisitTransactionDeclaration(declaration.(*TransactionDeclaration))

case ElementTypeTransactionRoleDeclaration:
return visitor.VisitTransactionRoleDeclaration(declaration.(*TransactionRoleDeclaration))
}

panic(errors.NewUnreachableError())
Expand Down
4 changes: 4 additions & 0 deletions runtime/common/declarationkind.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ func (k DeclarationKind) Name() string {
return "self"
case DeclarationKindTransaction:
return "transaction"
case DeclarationKindTransactionRole:
return "role"
case DeclarationKindPrepare:
return "prepare"
case DeclarationKindExecute:
Expand Down Expand Up @@ -175,6 +177,8 @@ func (k DeclarationKind) Keywords() string {
return "self"
case DeclarationKindTransaction:
return "transaction"
case DeclarationKindTransactionRole:
return "role"
case DeclarationKindPrepare:
return "prepare"
case DeclarationKindExecute:
Expand Down
5 changes: 5 additions & 0 deletions runtime/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,11 @@ func (compiler *Compiler) VisitTransactionDeclaration(_ *ast.TransactionDeclarat
panic(errors.NewUnreachableError())
}

func (compiler *Compiler) VisitTransactionRoleDeclaration(_ *ast.TransactionRoleDeclaration) ir.Stmt {
// TODO
panic(errors.NewUnreachableError())
}

func (compiler *Compiler) VisitEnumCaseDeclaration(_ *ast.EnumCaseDeclaration) ir.Stmt {
// TODO
panic(errors.NewUnreachableError())
Expand Down
5 changes: 5 additions & 0 deletions runtime/interpreter/interpreter_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,8 @@ func (interpreter *Interpreter) declareTransactionEntryPoint(declaration *ast.Tr
transactionFunction,
)
}

func (interpreter *Interpreter) VisitTransactionRoleDeclaration(_ *ast.TransactionRoleDeclaration) StatementResult {
// TODO:
panic("TODO")
}
147 changes: 147 additions & 0 deletions runtime/parser/declaration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3258,6 +3258,153 @@ func TestParseTransactionDeclaration(t *testing.T) {
)
})

t.Run("field with access, prepare", func(t *testing.T) {
const code = `
transaction {

priv var x: Int
var y: String

prepare(signer: AuthAccount) {
x = 0
}
}
`
result, errs := testParseProgram(code)
require.Empty(t, errs)

utils.AssertEqualWithDiff(t,
[]ast.Declaration{
&ast.TransactionDeclaration{
Fields: []*ast.FieldDeclaration{
{
Access: ast.AccessPrivate,
VariableKind: ast.VariableKindVariable,
Identifier: ast.Identifier{
Identifier: "x",
Pos: ast.Position{Offset: 35, Line: 4, Column: 15},
},
TypeAnnotation: &ast.TypeAnnotation{
IsResource: false,
Type: &ast.NominalType{
Identifier: ast.Identifier{
Identifier: "Int",
Pos: ast.Position{Offset: 38, Line: 4, Column: 18},
},
},
StartPos: ast.Position{Offset: 38, Line: 4, Column: 18},
},
Range: ast.Range{
StartPos: ast.Position{Offset: 26, Line: 4, Column: 6},
EndPos: ast.Position{Offset: 40, Line: 4, Column: 20},
},
},
{
Access: ast.AccessNotSpecified,
VariableKind: ast.VariableKindVariable,
Identifier: ast.Identifier{
Identifier: "y",
Pos: ast.Position{Offset: 58, Line: 5, Column: 16},
},
TypeAnnotation: &ast.TypeAnnotation{
IsResource: false,
Type: &ast.NominalType{
Identifier: ast.Identifier{
Identifier: "String",
Pos: ast.Position{Offset: 61, Line: 5, Column: 19},
},
},
StartPos: ast.Position{Offset: 61, Line: 5, Column: 19},
},
Range: ast.Range{
StartPos: ast.Position{Offset: 54, Line: 5, Column: 12},
EndPos: ast.Position{Offset: 66, Line: 5, Column: 24},
},
},
},
Prepare: &ast.SpecialFunctionDeclaration{
Kind: common.DeclarationKindPrepare,
FunctionDeclaration: &ast.FunctionDeclaration{
Access: ast.AccessNotSpecified,
Identifier: ast.Identifier{
Identifier: "prepare",
Pos: ast.Position{Offset: 75, Line: 7, Column: 6},
},
ParameterList: &ast.ParameterList{
Parameters: []*ast.Parameter{
{
Label: "",
Identifier: ast.Identifier{
Identifier: "signer",
Pos: ast.Position{Offset: 83, Line: 7, Column: 14},
},
TypeAnnotation: &ast.TypeAnnotation{
IsResource: false,
Type: &ast.NominalType{
Identifier: ast.Identifier{
Identifier: "AuthAccount",
Pos: ast.Position{Offset: 91, Line: 7, Column: 22},
},
},
StartPos: ast.Position{Offset: 91, Line: 7, Column: 22},
},
StartPos: ast.Position{Offset: 83, Line: 7, Column: 14},
},
},
Range: ast.Range{
StartPos: ast.Position{Offset: 82, Line: 7, Column: 13},
EndPos: ast.Position{Offset: 102, Line: 7, Column: 33},
},
},
ReturnTypeAnnotation: nil,
FunctionBlock: &ast.FunctionBlock{
Block: &ast.Block{
Statements: []ast.Statement{
&ast.AssignmentStatement{
Target: &ast.IdentifierExpression{
Identifier: ast.Identifier{
Identifier: "x",
Pos: ast.Position{Offset: 117, Line: 8, Column: 11},
},
},
Transfer: &ast.Transfer{
Operation: ast.TransferOperationCopy,
Pos: ast.Position{Offset: 119, Line: 8, Column: 13},
},
Value: &ast.IntegerExpression{
PositiveLiteral: []byte("0"),
Value: new(big.Int),
Base: 10,
Range: ast.Range{
StartPos: ast.Position{Offset: 121, Line: 8, Column: 15},
EndPos: ast.Position{Offset: 121, Line: 8, Column: 15},
},
},
},
},
Range: ast.Range{
StartPos: ast.Position{Offset: 104, Line: 7, Column: 35},
EndPos: ast.Position{Offset: 126, Line: 9, Column: 3},
},
},
PreConditions: nil,
PostConditions: nil,
},
StartPos: ast.Position{Offset: 75, Line: 7, Column: 6},
},
},
PreConditions: nil,
PostConditions: nil,
Range: ast.Range{
StartPos: ast.Position{Offset: 5, Line: 2, Column: 4},
EndPos: ast.Position{Offset: 132, Line: 10, Column: 4},
},
},
},
result.Declarations(),
)
})

t.Run("field, prepare, pre, execute, post", func(t *testing.T) {
const code = `
transaction {
Expand Down
54 changes: 24 additions & 30 deletions runtime/parser/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ func parseTransactionPrepare(p *parser) (*ast.SpecialFunctionDeclaration, error)
}

func parseTransactionFields(p *parser) (fields []*ast.FieldDeclaration, err error) {
access := ast.AccessNotSpecified
var accessPos *ast.Position

for {
_, docString := p.parseTrivia(triviaOptions{
skipNewlines: true,
Expand All @@ -238,19 +241,35 @@ func parseTransactionFields(p *parser) (fields []*ast.FieldDeclaration, err erro
case keywordLet, keywordVar:
field, err := parseFieldWithVariableKind(
p,
ast.AccessNotSpecified,
nil,
access,
accessPos,
nil,
nil,
docString,
)
if err != nil {
return nil, err
}
access = ast.AccessNotSpecified
accessPos = nil

fields = append(fields, field)
continue

case keywordPriv, keywordPub, keywordAccess:
if access != ast.AccessNotSpecified {
return nil, p.syntaxError("invalid second access modifier")
}
pos := p.current.StartPos
accessPos = &pos
var err error
access, err = parseAccess(p)
if err != nil {
return nil, err
}

continue

default:
return
}
Expand Down Expand Up @@ -320,34 +339,9 @@ func parseTransactionRole(p *parser, docString string) (*ast.TransactionRoleDecl
}

// Fields
var fields []*ast.FieldDeclaration
for {
_, docString := p.parseTrivia(triviaOptions{
skipNewlines: true,
parseDocStrings: true,
})

if p.current.Is(lexer.TokenIdentifier) {
switch string(p.currentTokenSource()) {
case keywordLet, keywordVar:
field, err := parseFieldWithVariableKind(
p,
ast.AccessNotSpecified,
nil,
nil,
nil,
docString,
)
if err != nil {
return nil, err
}

fields = append(fields, field)
continue
}
}

break
fields, err := parseTransactionFields(p)
if err != nil {
return nil, err
}

// Prepare (optional)
Expand Down
3 changes: 3 additions & 0 deletions runtime/sema/check_assignment.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,16 @@ func (checker *Checker) accessedSelfMember(expression ast.Expression) *Member {
}

var members *StringMemberOrderedMap
// TODO: refactor
switch containerType := variable.Type.(type) {
case *CompositeType:
members = containerType.Members
case *InterfaceType:
members = containerType.Members
case *TransactionType:
members = containerType.Members
case *TransactionRoleType:
members = containerType.Members
default:
panic(errors.NewUnreachableError())
}
Expand Down
6 changes: 2 additions & 4 deletions runtime/sema/check_composite_declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,8 @@ func (checker *Checker) visitCompositeDeclaration(declaration *ast.CompositeDecl
panic(errors.NewUnreachableError())
}

checker.containerTypes[compositeType] = true
defer func() {
checker.containerTypes[compositeType] = false
}()
checker.containerTypes[compositeType] = struct{}{}
defer delete(checker.containerTypes, compositeType)

checker.checkDeclarationAccessModifier(
declaration.Access,
Expand Down
2 changes: 1 addition & 1 deletion runtime/sema/check_create_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (checker *Checker) checkResourceCreationOrDestruction(compositeType *Compos
return
}
} else {
if checker.containerTypes[contractType] {
if _, ok := checker.containerTypes[contractType]; ok {
return
}
}
Expand Down
6 changes: 2 additions & 4 deletions runtime/sema/check_interface_declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,8 @@ func (checker *Checker) VisitInterfaceDeclaration(declaration *ast.InterfaceDecl
panic(errors.NewUnreachableError())
}

checker.containerTypes[interfaceType] = true
defer func() {
checker.containerTypes[interfaceType] = false
}()
checker.containerTypes[interfaceType] = struct{}{}
defer delete(checker.containerTypes, interfaceType)

checker.checkDeclarationAccessModifier(
declaration.Access,
Expand Down
17 changes: 12 additions & 5 deletions runtime/sema/check_member_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,12 @@ func (checker *Checker) visitMember(expression *ast.MemberExpression) (accessedT
// isReadableMember returns true if the given member can be read from
// in the current location of the checker
func (checker *Checker) isReadableMember(member *Member) bool {
if checker.isReadableAccess(member.Access) ||
checker.containerTypes[member.ContainerType] {
if checker.isReadableAccess(member.Access) {
return true
}

_, ok := checker.containerTypes[member.ContainerType]
if ok {
return true
}

Expand All @@ -313,7 +316,7 @@ func (checker *Checker) isReadableMember(member *Member) bool {
// check if the current location is contained in the member's contract

contractType := containingContractKindedType(member.ContainerType)
if checker.containerTypes[contractType] {
if _, ok := checker.containerTypes[contractType]; ok {
return true
}

Expand All @@ -338,8 +341,12 @@ func (checker *Checker) isReadableMember(member *Member) bool {
// isWriteableMember returns true if the given member can be written to
// in the current location of the checker
func (checker *Checker) isWriteableMember(member *Member) bool {
return checker.isWriteableAccess(member.Access) ||
checker.containerTypes[member.ContainerType]
if checker.isWriteableAccess(member.Access) {
return true
}

_, ok := checker.containerTypes[member.ContainerType]
return ok
}

// isMutatableMember returns true if the given member can be mutated
Expand Down
Loading