Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion ast/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ type StatementDeclarationVisitor[T any] interface {
VisitEntitlementDeclaration(*EntitlementDeclaration) T
VisitEntitlementMappingDeclaration(*EntitlementMappingDeclaration) T
VisitTransactionDeclaration(*TransactionDeclaration) T
VisitPragmaDeclaration(*PragmaDeclaration) T
}

type DeclarationVisitor[T any] interface {
StatementDeclarationVisitor[T]
VisitFieldDeclaration(*FieldDeclaration) T
VisitEnumCaseDeclaration(*EnumCaseDeclaration) T
VisitPragmaDeclaration(*PragmaDeclaration) T
VisitImportDeclaration(*ImportDeclaration) T
}

Expand Down Expand Up @@ -177,6 +177,9 @@ func AcceptStatement[T any](statement Statement, visitor StatementVisitor[T]) (_

case ElementTypeRemoveStatement:
return visitor.VisitRemoveStatement(statement.(*RemoveStatement))

case ElementTypePragmaDeclaration:
return visitor.VisitPragmaDeclaration(statement.(*PragmaDeclaration))
}

panic(errors.NewUnreachableError())
Expand Down
5 changes: 3 additions & 2 deletions bbq/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4165,8 +4165,9 @@ func (c *Compiler[_, _]) VisitFieldDeclaration(_ *ast.FieldDeclaration) (_ struc
}

func (c *Compiler[_, _]) VisitPragmaDeclaration(_ *ast.PragmaDeclaration) (_ struct{}) {
// TODO
panic(errors.NewUnreachableError())
// Pragmas are directives for the checker (e.g. #exhaustive).
// Nothing to compile.
return
}

func (c *Compiler[_, _]) VisitImportDeclaration(declaration *ast.ImportDeclaration) (_ struct{}) {
Expand Down
6 changes: 6 additions & 0 deletions cmd/errors/errors.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 53 additions & 0 deletions interpreter/switch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/stretchr/testify/require"

"github.com/onflow/cadence/common"
"github.com/onflow/cadence/interpreter"
. "github.com/onflow/cadence/test_utils/interpreter_utils"
)
Expand Down Expand Up @@ -233,4 +234,56 @@ func TestInterpretSwitchStatement(t *testing.T) {
AssertValuesEqual(t, inter, testCase.expected, actual)
}
})

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

t.Parallel()

inter := parseCheckAndPrepare(t, `
enum Color: UInt8 {
case red
case green
case blue
}

fun test(): [String] {
let results: [String] = []
let rawValues: [UInt8] = [0, 1, 2]
for rawValue in rawValues {
let c = Color(rawValue: rawValue)!
results.append(name(c))
}
return results
}

fun name(_ c: Color): String {
#exhaustive
switch c {
case Color.red:
return "red"
case Color.green:
return "green"
case Color.blue:
return "blue"
}
}
`)

actual, err := inter.Invoke("test")
require.NoError(t, err)

AssertValuesEqual(t, inter,
interpreter.NewArrayValue(
inter,
&interpreter.VariableSizedStaticType{
Type: interpreter.PrimitiveStaticTypeString,
},
common.ZeroAddress,
interpreter.NewUnmeteredStringValue("red"),
interpreter.NewUnmeteredStringValue("green"),
interpreter.NewUnmeteredStringValue("blue"),
),
actual,
)
})
}
25 changes: 23 additions & 2 deletions sema/check_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (checker *Checker) visitStatements(statements []ast.Statement) {
functionActivation := checker.functionActivations.Current()

// check all statements
for _, statement := range statements {
for i, statement := range statements {

// Is this statement unreachable? Report it once for this statement,
// but avoid noise and don't report it for all remaining unreachable statements
Expand Down Expand Up @@ -60,6 +60,23 @@ func (checker *Checker) visitStatements(statements []ast.Statement) {

// check statement

switch stmt := statement.(type) {
case *ast.SwitchStatement:
isExhaustive := isPrecedingStatementExhaustivePragma(statements, i)
checker.checkSwitchStatement(stmt, isExhaustive)
continue

case *ast.PragmaDeclaration:
if isExhaustivePragma(stmt) && !isStatementFollowedBySwitch(statements, i) {
checker.report(
&InvalidPragmaError{
Message: "the #exhaustive pragma must be placed directly before a switch statement",
Range: ast.NewRangeFromPositioned(checker.memoryGauge, stmt),
},
)
}
}

ast.AcceptStatement[struct{}](statement, checker)
}
}
Expand All @@ -75,9 +92,13 @@ func (checker *Checker) checkValidStatement(statement ast.Statement) bool {

// Only function and variable declarations are allowed locally

switch declaration.(type) {
switch decl := declaration.(type) {
case *ast.FunctionDeclaration, *ast.VariableDeclaration:
return true
case *ast.PragmaDeclaration:
if isExhaustivePragma(decl) {
return true
}
}

identifier := declaration.DeclarationIdentifier()
Expand Down
4 changes: 4 additions & 0 deletions sema/check_composite_declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -1090,13 +1090,17 @@ func (checker *Checker) declareEnumConstructor(

memberCaseTypeAnnotation := NewTypeAnnotation(compositeType)

compositeType.EnumCases = make([]string, 0, len(enumCases))

for _, enumCase := range enumCases {
caseName := enumCase.Identifier.Identifier

if enumLookupFunctionType.Members.Contains(caseName) {
continue
}

compositeType.EnumCases = append(compositeType.EnumCases, caseName)

enumLookupFunctionType.Members.Set(
caseName,
&Member{
Expand Down
46 changes: 46 additions & 0 deletions sema/check_exhaustive_switch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Cadence - The resource-oriented smart contract programming language
*
* Copyright Flow Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sema

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

func isExhaustivePragma(pragma *ast.PragmaDeclaration) bool {
ident, ok := pragma.Expression.(*ast.IdentifierExpression)
return ok && ident.Identifier.Identifier == "exhaustive"
}

// isPrecedingStatementExhaustivePragma checks whether the statement at the given index
// is immediately preceded by an #exhaustive pragma.
func isPrecedingStatementExhaustivePragma(statements []ast.Statement, index int) bool {
if index == 0 {
return false
}
pragma, ok := statements[index-1].(*ast.PragmaDeclaration)
return ok && isExhaustivePragma(pragma)
}

// isStatementFollowedBySwitch checks whether the statement at the given index
// is immediately followed by a switch statement.
func isStatementFollowedBySwitch(statements []ast.Statement, index int) bool {
if index+1 >= len(statements) {
return false
}
_, ok := statements[index+1].(*ast.SwitchStatement)
return ok
}
Loading
Loading