Skip to content

Commit

Permalink
Merge branch 'master' of github.com:onflow/cadence into sainati/entit…
Browse files Browse the repository at this point in the history
…lements-migration
  • Loading branch information
dsainati1 committed Jan 5, 2024
2 parents 444ad83 + 32ddd35 commit 1413852
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 81 deletions.
99 changes: 70 additions & 29 deletions runtime/sema/check_pragma.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,47 +20,88 @@ package sema

import "github.com/onflow/cadence/runtime/ast"

// VisitPragmaDeclaration checks that the pragma declaration is valid.
// It is valid if the root expression is an identifier or invocation.
// Invocations must
func (checker *Checker) VisitPragmaDeclaration(declaration *ast.PragmaDeclaration) (_ struct{}) {

switch expression := declaration.Expression.(type) {
case *ast.InvocationExpression:
// Type arguments are not supported for pragmas
if len(expression.TypeArguments) > 0 {
checker.report(&InvalidPragmaError{
Message: "type arguments are not supported",
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
expression.TypeArguments[0],
),
})
}

// Ensure arguments are string expressions
for _, arg := range expression.Arguments {
_, ok := arg.Expression.(*ast.StringExpression)
if !ok {
checker.report(&InvalidPragmaError{
Message: "invalid non-string argument",
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
arg.Expression,
),
})
}
}

case *ast.IdentifierExpression:
// NO-OP
// valid, NO-OP

case *ast.InvocationExpression:
checker.checkPragmaInvocationExpression(expression)

default:
checker.report(&InvalidPragmaError{
Message: "pragma must be identifier or invocation expression",
Message: "expression must be literal, identifier, or invocation",
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
declaration,
expression,
),
})
}

return
}

func (checker *Checker) checkPragmaInvocationExpression(expression *ast.InvocationExpression) {
// Invoked expression must be an identifier
if _, ok := expression.InvokedExpression.(*ast.IdentifierExpression); !ok {
checker.report(&InvalidPragmaError{
Message: "invoked expression must be an identifier",
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
expression.InvokedExpression,
),
})
}

// Type arguments are not supported for pragmas
if len(expression.TypeArguments) > 0 {
checker.report(&InvalidPragmaError{
Message: "type arguments are not supported",
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
expression.TypeArguments[0],
),
})
}

// Ensure arguments are valid
for _, arg := range expression.Arguments {
checker.checkPragmaArgumentExpression(arg.Expression)
}
}

func (checker *Checker) checkPragmaArgumentExpression(expression ast.Expression) {
switch expression := expression.(type) {
case *ast.InvocationExpression:
checker.checkPragmaInvocationExpression(expression)
return

case *ast.StringExpression,
*ast.IntegerExpression,
*ast.FixedPointExpression,
*ast.ArrayExpression,
*ast.DictionaryExpression,
*ast.NilExpression,
*ast.BoolExpression,
*ast.PathExpression:

return

case *ast.UnaryExpression:
if expression.Operation == ast.OperationMinus {
return
}
}

checker.report(&InvalidPragmaError{
Message: "expression in invocation must be literal or invocation",
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
expression,
),
})
}
145 changes: 93 additions & 52 deletions runtime/tests/checker/pragma_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package checker

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -27,37 +28,101 @@ import (
"github.com/onflow/cadence/runtime/sema"
)

func TestCheckPragmaInvalidExpr(t *testing.T) {
func TestCheckPragmaExpression(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
#"string"
`)

errs := RequireCheckerErrors(t, err, 1)
assert.IsType(t, &sema.InvalidPragmaError{}, errs[0])
}

func TestCheckPragmaValidIdentifierExpr(t *testing.T) {

t.Parallel()
_, err := ParseAndCheck(t, `
#pedantic
`)

require.NoError(t, err)
}

func TestCheckPragmaValidInvocationExpr(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
#version("1.0")
`)

require.NoError(t, err)
type testCase struct {
name string
code string
valid bool
}

test := func(testCase testCase) {
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()

_, err := ParseAndCheck(t, fmt.Sprintf(`#%s`, testCase.code))

if testCase.valid {
require.NoError(t, err)
} else {
errs := RequireCheckerErrors(t, err, 1)
assert.IsType(t, &sema.InvalidPragmaError{}, errs[0])
}
})
}

testCases := []testCase{
{"string", `"string"`, false},
{"bool", `true`, false},
{"integer", `1`, false},
{"fixed-point", `1.2`, false},
{"unary, minus", `-1`, false},
{"unary, move", `<-r`, false},
{"array", `[1, 2, 3]`, false},
{"dictionary", `{1: 2}`, false},
{"nil", `nil`, false},
{"path", `/storage/foo`, false},
{"reference", `&x`, false},
{"index", `xs[0]`, false},
{"binary", `a + b`, false},
{"conditional", `a ? b : c`, false},
{"force", `a!`, false},
{"function", `fun() {}`, false},
{"create", `create R()`, false},
{"destroy", `destroy r`, false},
{"identifier", `foo`, true},
// invocations
{"invocation of member", `foo.bar()`, false},
{"invocation with type arguments", `foo<X>()`, false},
{"invocation without arguments", `foo()`, true},
{"invocation with identifier argument", `foo(bar)`, false},
{"invocation with string argument", `foo("string")`, true},
{"invocation with bool argument", `foo(true)`, true},
{"invocation with integer argument", `foo(1)`, true},
{"invocation with fixed-point argument", `foo(1.2)`, true},
{"invocation with unary (minus) argument", `foo(-1)`, true},
{"invocation with unary (move) argument", `foo(<-r)`, false},
{"invocation with array argument", `foo([1, 2, 3])`, true},
{"invocation with dictionary argument", `foo({1: 2})`, true},
{"invocation with nil argument", `foo(nil)`, true},
{"invocation with path argument", `foo(/storage/foo)`, true},
{"invocation with reference argument", `foo(&x)`, false},
{"invocation with index argument", `foo(xs[0])`, false},
{"invocation with binary argument", `foo(a + b)`, false},
{"invocation with conditional argument", `foo(a ? b : c)`, false},
{"invocation with force argument", `foo(a!)`, false},
{"invocation with function argument", `foo(fun() {})`, false},
{"invocation with create argument", `foo(create R())`, false},
{"invocation with destroy argument", `destroy r`, false},
// nested invocations
{"nested invocation without argument", `foo(bar())`, true},
{"nested invocation with identifier argument", `foo(bar(baz))`, false},
{"nested invocation with string argument", `foo(bar("string"))`, true},
// FLIX
{
"FLIX",
`interaction(
version: "1.1.0",
title: "Flow Token Balance",
description: "Get account Flow Token balance",
language: "en-US",
parameters: [
Parameter(
name: "address",
title: "Address",
description: "Get Flow token balance of Flow account"
)
],
)`,
true,
},
}

for _, testCase := range testCases {
test(testCase)
}
}

func TestCheckPragmaInvalidLocation(t *testing.T) {
Expand All @@ -73,27 +138,3 @@ func TestCheckPragmaInvalidLocation(t *testing.T) {
errs := RequireCheckerErrors(t, err, 1)
assert.IsType(t, &sema.InvalidDeclarationError{}, errs[0])
}

func TestCheckPragmaInvalidInvocationExprNonStringExprArgument(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
#version(y)
`)

errs := RequireCheckerErrors(t, err, 1)
assert.IsType(t, &sema.InvalidPragmaError{}, errs[0])
}

func TestCheckPragmaInvalidInvocationExprTypeArgs(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
#version<X>()
`)

errs := RequireCheckerErrors(t, err, 1)
assert.IsType(t, &sema.InvalidPragmaError{}, errs[0])
}

0 comments on commit 1413852

Please sign in to comment.