Skip to content

Commit

Permalink
check prepare parameter lists match, create prepare functions types o…
Browse files Browse the repository at this point in the history
…nly if needed
  • Loading branch information
turbolent committed Jan 18, 2023
1 parent 40873ef commit 17610f2
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 32 deletions.
116 changes: 96 additions & 20 deletions runtime/sema/check_transaction_declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ func (checker *Checker) VisitTransactionDeclaration(declaration *ast.Transaction
fieldMembers := getFieldMembers(members, fields)

checker.checkTransactionFields(fields)
checker.checkPrepareExists(declaration.Prepare, fields)

prepareFunctionDeclaration := declaration.Prepare
checker.checkPrepareExists(prepareFunctionDeclaration, fields)

// enter a new scope for this transaction
checker.enterValueScope()
Expand All @@ -54,15 +56,23 @@ func (checker *Checker) VisitTransactionDeclaration(declaration *ast.Transaction
checker.checkTransactionParameters(declaration, transactionType.Parameters)
}

checker.visitPrepareFunction(
declaration.Prepare,
transactionType,
transactionType.PrepareFunctionType(),
fieldMembers,
)
if prepareFunctionDeclaration != nil {
prepareFunctionType := transactionType.PrepareFunctionType()

checker.visitPrepareFunction(
prepareFunctionDeclaration,
transactionType,
prepareFunctionType,
fieldMembers,
)
}

// Check roles
for _, roleDeclaration := range declaration.Roles {
ast.AcceptDeclaration[struct{}](roleDeclaration, checker)

for _, role := range declaration.Roles {
ast.AcceptDeclaration[struct{}](role, checker)
// Each role's prepare parameter list must match the transaction's prepare parameter list
checker.checkRolePrepareParameterList(roleDeclaration, transactionType)
}

if declaration.PreConditions != nil {
Expand All @@ -84,6 +94,73 @@ func (checker *Checker) VisitTransactionDeclaration(declaration *ast.Transaction
return
}

// checkRolePrepareParameterList checks that the role's prepare block parameter list
// matches the transaction's prepare parameter list:
// They must have the same length and the parameter types must match.
func (checker *Checker) checkRolePrepareParameterList(
roleDeclaration *ast.TransactionRoleDeclaration,
transactionType *TransactionType,
) {
transactionRoleType := checker.Elaboration.TransactionRoleDeclarationType(roleDeclaration)
// Type should have been declared in declareTransactionDeclaration
if transactionRoleType == nil {
panic(errors.NewUnreachableError())
}

transactionPrepareParameterCount := len(transactionType.PrepareParameters)
transactionRolePrepareParameterCount := len(transactionRoleType.PrepareParameters)
if transactionPrepareParameterCount != transactionRolePrepareParameterCount {
if roleDeclaration.Prepare == nil {
checker.report(
&MissingRolePrepareError{
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
roleDeclaration.Identifier,
),
},
)
} else {
checker.report(
&PrepareParameterCountMismatchError{
ActualCount: transactionRolePrepareParameterCount,
ExpectedCount: transactionPrepareParameterCount,
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
roleDeclaration.Prepare,
),
},
)
}
}

minPrepareParameterCount := transactionPrepareParameterCount
if transactionRolePrepareParameterCount < minPrepareParameterCount {
minPrepareParameterCount = transactionRolePrepareParameterCount
}

for prepareParameterIndex := 0; prepareParameterIndex < minPrepareParameterCount; prepareParameterIndex++ {
transactionPrepareParameter := transactionType.PrepareParameters[prepareParameterIndex]
transactionRolePrepareParameter := transactionRoleType.PrepareParameters[prepareParameterIndex]

if !transactionRolePrepareParameter.TypeAnnotation.
Equal(transactionPrepareParameter.TypeAnnotation) {

parameter := roleDeclaration.Prepare.FunctionDeclaration.ParameterList.Parameters[prepareParameterIndex]

checker.report(
&TypeMismatchError{
ExpectedType: transactionPrepareParameter.TypeAnnotation.Type,
ActualType: transactionRolePrepareParameter.TypeAnnotation.Type,
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
parameter.TypeAnnotation,
),
},
)
}
}
}

func (checker *Checker) checkTransactionParameters(declaration *ast.TransactionDeclaration, parameters []Parameter) {
checker.checkArgumentLabels(declaration.ParameterList)
checker.checkParameters(declaration.ParameterList, parameters)
Expand Down Expand Up @@ -143,7 +220,7 @@ func (checker *Checker) checkPrepareExists(
firstField := fields[0]

checker.report(
&MissingPrepareError{
&MissingPrepareForFieldError{
FirstFieldName: firstField.Identifier.Identifier,
FirstFieldPos: firstField.Identifier.Pos,
},
Expand All @@ -157,10 +234,6 @@ func (checker *Checker) visitPrepareFunction(
prepareFunctionType *FunctionType,
fieldMembers *MemberFieldDeclarationOrderedMap,
) {
if prepareFunction == nil {
return
}

initializationInfo := NewInitializationInfo(containerType, fieldMembers)

checker.checkFunction(
Expand Down Expand Up @@ -390,12 +463,15 @@ func (checker *Checker) VisitTransactionRoleDeclaration(declaration *ast.Transac

checker.declareSelfValue(transactionRoleType, "")

checker.visitPrepareFunction(
declaration.Prepare,
transactionRoleType,
transactionRoleType.PrepareFunctionType(),
fieldMembers,
)
if declaration.Prepare != nil {
prepareFunctionType := transactionRoleType.PrepareFunctionType()
checker.visitPrepareFunction(
declaration.Prepare,
transactionRoleType,
prepareFunctionType,
fieldMembers,
)
}

return
}
58 changes: 48 additions & 10 deletions runtime/sema/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2902,32 +2902,32 @@ func (e *ReadOnlyTargetAssignmentError) Error() string {
return "cannot assign to read-only target"
}

// TransactionMissingPrepareError

type MissingPrepareError struct {
// MissingPrepareForFieldError
//
type MissingPrepareForFieldError struct {
FirstFieldName string
FirstFieldPos ast.Position
}

var _ SemanticError = &MissingPrepareError{}
var _ errors.UserError = &MissingPrepareError{}
var _ SemanticError = &MissingPrepareForFieldError{}
var _ errors.UserError = &MissingPrepareForFieldError{}

func (*MissingPrepareError) isSemanticError() {}
func (*MissingPrepareForFieldError) isSemanticError() {}

func (*MissingPrepareError) IsUserError() {}
func (*MissingPrepareForFieldError) IsUserError() {}

func (e *MissingPrepareError) Error() string {
func (e *MissingPrepareForFieldError) Error() string {
return fmt.Sprintf(
"missing prepare block for field `%s`",
e.FirstFieldName,
)
}

func (e *MissingPrepareError) StartPosition() ast.Position {
func (e *MissingPrepareForFieldError) StartPosition() ast.Position {
return e.FirstFieldPos
}

func (e *MissingPrepareError) EndPosition(memoryGauge common.MemoryGauge) ast.Position {
func (e *MissingPrepareForFieldError) EndPosition(memoryGauge common.MemoryGauge) ast.Position {
length := len(e.FirstFieldName)
return e.FirstFieldPos.Shifted(memoryGauge, length-1)
}
Expand Down Expand Up @@ -3046,6 +3046,44 @@ func (e *TransactionRoleWithFieldNameError) ErrorNotes() []errors.ErrorNote {
}
}

// PrepareParameterCountMismatchError
type PrepareParameterCountMismatchError struct {
ActualCount int
ExpectedCount int
ast.Range
}

var _ SemanticError = &PrepareParameterCountMismatchError{}
var _ errors.UserError = &PrepareParameterCountMismatchError{}

func (*PrepareParameterCountMismatchError) isSemanticError() {}

func (*PrepareParameterCountMismatchError) IsUserError() {}

func (e *PrepareParameterCountMismatchError) Error() string {
return fmt.Sprintf(
"role's prepare parameter count must match transaction's: expected %d, got %d",
e.ExpectedCount,
e.ActualCount,
)
}

// MissingRolePrepareError
type MissingRolePrepareError struct {
ast.Range
}

var _ SemanticError = &MissingRolePrepareError{}
var _ errors.UserError = &MissingRolePrepareError{}

func (*MissingRolePrepareError) isSemanticError() {}

func (*MissingRolePrepareError) IsUserError() {}

func (e *MissingRolePrepareError) Error() string {
return "role is missing prepare block that matches transaction's"
}

// InvalidNestedDeclarationError

type InvalidNestedDeclarationError struct {
Expand Down
79 changes: 77 additions & 2 deletions runtime/tests/checker/transactions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func TestCheckTransactions(t *testing.T) {
}
`,
[]error{
&sema.MissingPrepareError{},
&sema.MissingPrepareForFieldError{},
},
)
})
Expand Down Expand Up @@ -450,7 +450,7 @@ func TestCheckTransactionRoles(t *testing.T) {
}
`,
[]error{
&sema.MissingPrepareError{},
&sema.MissingPrepareForFieldError{},
},
)
})
Expand Down Expand Up @@ -553,6 +553,8 @@ func TestCheckTransactionRoles(t *testing.T) {
`
transaction {
prepare(signer: AuthAccount) {}
role buyer {
let foo: Int
Expand All @@ -564,6 +566,79 @@ func TestCheckTransactionRoles(t *testing.T) {
`,
[]error{
&sema.InvalidTransactionPrepareParameterTypeError{},
&sema.TypeMismatchError{},
},
)
})

t.Run("matching prepare", func(t *testing.T) {
test(
t,
`
transaction {
prepare(signer: AuthAccount) {}
role buyer {
prepare(signer: AuthAccount) {}
}
}
`,
nil,
)
})

t.Run("missing prepare", func(t *testing.T) {
test(
t,
`
transaction {
prepare(signer: AuthAccount) {}
role buyer {}
}
`,
[]error{
&sema.MissingRolePrepareError{},
},
)
})

t.Run("fewer prepare parameters", func(t *testing.T) {
test(
t,
`
transaction {
prepare(signer: AuthAccount) {}
role buyer {
prepare() {}
}
}
`,
[]error{
&sema.PrepareParameterCountMismatchError{},
},
)
})

t.Run("more prepare parameters", func(t *testing.T) {
test(
t,
`
transaction {
prepare(signer: AuthAccount) {}
role buyer {
prepare(firstSigner: AuthAccount, secondSigner: AuthAccount) {}
}
}
`,
[]error{
&sema.PrepareParameterCountMismatchError{},
},
)
})
Expand Down

0 comments on commit 17610f2

Please sign in to comment.