diff --git a/runtime/sema/check_casting_expression.go b/runtime/sema/check_casting_expression.go index 9de79c21cc..515805fcda 100644 --- a/runtime/sema/check_casting_expression.go +++ b/runtime/sema/check_casting_expression.go @@ -124,7 +124,7 @@ func (checker *Checker) VisitCastingExpression(expression *ast.CastingExpression Range: ast.NewRangeFromPositioned(leftHandExpression), }, ) - } else if checker.lintEnabled && IsSubType(leftHandType, rightHandType) { + } else if checker.devModeEnabled && IsSubType(leftHandType, rightHandType) { switch expression.Operation { case ast.OperationFailableCast: @@ -162,7 +162,7 @@ func (checker *Checker) VisitCastingExpression(expression *ast.CastingExpression // the inferred-type of the expression. i.e: exprActualType == rightHandType // Then, it is not possible to determine whether the target type is redundant. // Therefore, don't check for redundant casts, if there are errors. - if checker.lintEnabled && + if checker.devModeEnabled && !hasErrors && isRedundantCast(leftHandExpression, exprActualType, rightHandType, checker.expectedType) { checker.hint( diff --git a/runtime/sema/check_force_expression.go b/runtime/sema/check_force_expression.go index b0df2a202e..34a86e66f2 100644 --- a/runtime/sema/check_force_expression.go +++ b/runtime/sema/check_force_expression.go @@ -44,7 +44,7 @@ func (checker *Checker) VisitForceExpression(expression *ast.ForceExpression) as if !ok { // A non-optional type is forced. Suggest removing it - if checker.lintEnabled { + if checker.devModeEnabled { checker.hint( &RemovalHint{ Description: "unnecessary force operator", diff --git a/runtime/sema/check_switch.go b/runtime/sema/check_switch.go index 42b7295ac3..97ec4fcb2f 100644 --- a/runtime/sema/check_switch.go +++ b/runtime/sema/check_switch.go @@ -49,6 +49,8 @@ func (checker *Checker) VisitSwitchStatement(statement *ast.SwitchStatement) ast checker.visitSwitchCase(switchCase, defaultAllowed, testType, testTypeIsValid) } + checker.checkDuplicateCases(statement.Cases) + checker.functionActivations.WithSwitch(func() { checker.checkSwitchCasesStatements(statement.Cases) }) @@ -182,3 +184,277 @@ func (checker *Checker) checkSwitchCaseStatements(switchCase *ast.SwitchCase) { } block.Accept(checker) } + +func (checker *Checker) checkDuplicateCases(cases []*ast.SwitchCase) { + // This check is expensive, and catching duplicates is a 'nice to have' + // feature. Therefore, only enabled it during development time. + if !checker.devModeEnabled { + return + } + + duplicates := make(map[*ast.SwitchCase]bool) + + duplicateChecker := newDuplicateCaseChecker(checker) + + for i, switchCase := range cases { + + // If the current case is already identified as a duplicate, + // then no need to check it again. Can simply skip. + if _, isDuplicate := duplicates[switchCase]; isDuplicate { + continue + } + + for j := i + 1; j < len(cases); j++ { + otherCase := cases[j] + if !duplicateChecker.isDuplicate(switchCase.Expression, otherCase.Expression) { + continue + } + + duplicates[otherCase] = true + + checker.report( + &DuplicateSwitchCaseError{ + Range: ast.NewRangeFromPositioned(otherCase.Expression), + }, + ) + } + } +} + +var _ ast.ExpressionVisitor = &duplicateCaseChecker{} + +type duplicateCaseChecker struct { + expr ast.Expression + checker *Checker +} + +func newDuplicateCaseChecker(checker *Checker) *duplicateCaseChecker { + return &duplicateCaseChecker{ + checker: checker, + } +} + +func (d *duplicateCaseChecker) isDuplicate(this ast.Expression, other ast.Expression) bool { + if this == nil || other == nil { + return false + } + + tempExpr := d.expr + d.expr = this + defer func() { + d.expr = tempExpr + }() + + return other.AcceptExp(d).(bool) +} + +func (d *duplicateCaseChecker) VisitBoolExpression(otherExpr *ast.BoolExpression) ast.Repr { + expr, ok := d.expr.(*ast.BoolExpression) + if !ok { + return false + } + + return otherExpr.Value == expr.Value +} + +func (d *duplicateCaseChecker) VisitNilExpression(_ *ast.NilExpression) ast.Repr { + _, ok := d.expr.(*ast.NilExpression) + return ok +} + +func (d *duplicateCaseChecker) VisitIntegerExpression(otherExpr *ast.IntegerExpression) ast.Repr { + expr, ok := d.expr.(*ast.IntegerExpression) + if !ok { + return false + } + + return expr.Value.Cmp(otherExpr.Value) == 0 +} + +func (d *duplicateCaseChecker) VisitFixedPointExpression(otherExpr *ast.FixedPointExpression) ast.Repr { + expr, ok := d.expr.(*ast.FixedPointExpression) + if !ok { + return false + } + + return expr.Negative == otherExpr.Negative && + expr.Fractional.Cmp(otherExpr.Fractional) == 0 && + expr.UnsignedInteger.Cmp(otherExpr.UnsignedInteger) == 0 && + expr.Scale == otherExpr.Scale +} + +func (d *duplicateCaseChecker) VisitArrayExpression(otherExpr *ast.ArrayExpression) ast.Repr { + expr, ok := d.expr.(*ast.ArrayExpression) + if !ok || len(expr.Values) != len(otherExpr.Values) { + return false + } + + for index, value := range expr.Values { + if !d.isDuplicate(value, otherExpr.Values[index]) { + return false + } + } + + return true +} + +func (d *duplicateCaseChecker) VisitDictionaryExpression(otherExpr *ast.DictionaryExpression) ast.Repr { + expr, ok := d.expr.(*ast.DictionaryExpression) + if !ok || len(expr.Entries) != len(otherExpr.Entries) { + return false + } + + for index, entry := range expr.Entries { + otherEntry := otherExpr.Entries[index] + + if !d.isDuplicate(entry.Key, otherEntry.Key) || + !d.isDuplicate(entry.Value, otherEntry.Value) { + return false + } + } + + return true +} + +func (d *duplicateCaseChecker) VisitIdentifierExpression(otherExpr *ast.IdentifierExpression) ast.Repr { + expr, ok := d.expr.(*ast.IdentifierExpression) + if !ok { + return false + } + + return expr.Identifier.Identifier == otherExpr.Identifier.Identifier +} + +func (d *duplicateCaseChecker) VisitInvocationExpression(_ *ast.InvocationExpression) ast.Repr { + // Invocations can be stateful. Thus, it's not possible to determine if + // invoking the same function in two cases would produce the same results. + return false +} + +func (d *duplicateCaseChecker) VisitMemberExpression(otherExpr *ast.MemberExpression) ast.Repr { + expr, ok := d.expr.(*ast.MemberExpression) + if !ok { + return false + } + + return d.isDuplicate(expr.Expression, otherExpr.Expression) && + expr.Optional == otherExpr.Optional && + expr.Identifier.Identifier == otherExpr.Identifier.Identifier +} + +func (d *duplicateCaseChecker) VisitIndexExpression(otherExpr *ast.IndexExpression) ast.Repr { + expr, ok := d.expr.(*ast.IndexExpression) + if !ok { + return false + } + + return d.isDuplicate(expr.TargetExpression, otherExpr.TargetExpression) && + d.isDuplicate(expr.IndexingExpression, otherExpr.IndexingExpression) +} + +func (d *duplicateCaseChecker) VisitConditionalExpression(otherExpr *ast.ConditionalExpression) ast.Repr { + expr, ok := d.expr.(*ast.ConditionalExpression) + if !ok { + return false + } + + return d.isDuplicate(expr.Test, otherExpr.Test) && + d.isDuplicate(expr.Then, otherExpr.Then) && + d.isDuplicate(expr.Else, otherExpr.Else) +} + +func (d *duplicateCaseChecker) VisitUnaryExpression(otherExpr *ast.UnaryExpression) ast.Repr { + expr, ok := d.expr.(*ast.UnaryExpression) + if !ok { + return false + } + + return d.isDuplicate(expr.Expression, otherExpr.Expression) && + expr.Operation == otherExpr.Operation +} + +func (d *duplicateCaseChecker) VisitBinaryExpression(otherExpr *ast.BinaryExpression) ast.Repr { + expr, ok := d.expr.(*ast.BinaryExpression) + if !ok { + return false + } + + return d.isDuplicate(expr.Left, otherExpr.Left) && + d.isDuplicate(expr.Right, otherExpr.Right) && + expr.Operation == otherExpr.Operation +} + +func (d *duplicateCaseChecker) VisitFunctionExpression(_ *ast.FunctionExpression) ast.Repr { + // Not a valid expression for switch-case. Hence, skip. + return false +} + +func (d *duplicateCaseChecker) VisitStringExpression(otherExpr *ast.StringExpression) ast.Repr { + expr, ok := d.expr.(*ast.StringExpression) + if !ok { + return false + } + + return expr.Value == otherExpr.Value +} + +func (d *duplicateCaseChecker) VisitCastingExpression(otherExpr *ast.CastingExpression) ast.Repr { + expr, ok := d.expr.(*ast.CastingExpression) + if !ok { + return false + } + + if !d.isDuplicate(expr.Expression, otherExpr.Expression) || + expr.Operation != otherExpr.Operation { + return false + } + + typeAnnot := d.checker.ConvertTypeAnnotation(expr.TypeAnnotation) + otherTypeAnnot := d.checker.ConvertTypeAnnotation(expr.TypeAnnotation) + return typeAnnot.Equal(otherTypeAnnot) +} + +func (d *duplicateCaseChecker) VisitCreateExpression(_ *ast.CreateExpression) ast.Repr { + // Not a valid expression for switch-case. Hence, skip. + return false +} + +func (d *duplicateCaseChecker) VisitDestroyExpression(_ *ast.DestroyExpression) ast.Repr { + // Not a valid expression for switch-case. Hence, skip. + return false +} + +func (d *duplicateCaseChecker) VisitReferenceExpression(otherExpr *ast.ReferenceExpression) ast.Repr { + expr, ok := d.expr.(*ast.ReferenceExpression) + if !ok { + return false + } + + if !d.isDuplicate(expr.Expression, otherExpr.Expression) { + return false + } + + targetType := d.checker.ConvertType(expr.Type) + otherTargetType := d.checker.ConvertType(otherExpr.Type) + + return targetType.Equal(otherTargetType) +} + +func (d *duplicateCaseChecker) VisitForceExpression(otherExpr *ast.ForceExpression) ast.Repr { + expr, ok := d.expr.(*ast.ForceExpression) + if !ok { + return false + } + + return d.isDuplicate(expr.Expression, otherExpr.Expression) +} + +func (d *duplicateCaseChecker) VisitPathExpression(otherExpr *ast.PathExpression) ast.Repr { + expr, ok := d.expr.(*ast.PathExpression) + if !ok { + return false + } + + return expr.Domain == otherExpr.Domain && + expr.Identifier.Identifier == otherExpr.Identifier.Identifier +} diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 2999e2ce08..715ffb4f12 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -115,7 +115,7 @@ type Checker struct { checkHandler CheckHandlerFunc expectedType Type memberAccountAccessHandler MemberAccountAccessHandlerFunc - lintEnabled bool + devModeEnabled bool } type Option func(*Checker) error @@ -236,12 +236,13 @@ func WithPositionInfoEnabled(enabled bool) Option { } } -// WithLintingEnabled returns a checker option which enables/disables -// advanced linting. +// WithDevModeEnabled returns a checker option which enables/disables +// advanced semantic checks that are not mandatory. +// e.g: advanced linting, duplicate switch-cases // -func WithLintingEnabled(enabled bool) Option { +func WithDevModeEnabled(enabled bool) Option { return func(checker *Checker) error { - checker.lintEnabled = enabled + checker.devModeEnabled = enabled return nil } } diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 200c583243..0a13cf1cdc 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -2966,3 +2966,15 @@ func (e *InvalidEntryPointTypeError) Error() string { e.Type.QualifiedString(), ) } + +// DuplicateSwitchCaseError + +type DuplicateSwitchCaseError struct { + ast.Range +} + +func (e *DuplicateSwitchCaseError) Error() string { + return "duplicate switch case" +} + +func (*DuplicateSwitchCaseError) isSemanticError() {} diff --git a/runtime/sema/type.go b/runtime/sema/type.go index f3fa62eac3..d656021f31 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -3086,14 +3086,14 @@ func numberFunctionArgumentExpressionsChecker(targetType Type) ArgumentExpressio switch argument := argument.(type) { case *ast.IntegerExpression: if CheckIntegerLiteral(argument, targetType, checker.report) { - if checker.lintEnabled { + if checker.devModeEnabled { suggestIntegerLiteralConversionReplacement(checker, argument, targetType, invocationRange) } } case *ast.FixedPointExpression: if CheckFixedPointLiteral(argument, targetType, checker.report) { - if checker.lintEnabled { + if checker.devModeEnabled { suggestFixedPointLiteralConversionReplacement(checker, targetType, argument, invocationRange) } } diff --git a/runtime/tests/checker/conversion_test.go b/runtime/tests/checker/conversion_test.go index 63a5d6d846..23a654f071 100644 --- a/runtime/tests/checker/conversion_test.go +++ b/runtime/tests/checker/conversion_test.go @@ -38,7 +38,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { t.Parallel() - checker, err := ParseAndCheckWithLinting(t, ` + checker, err := ParseAndCheck(t, ` let x = Fix64(1) `) @@ -58,7 +58,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { t.Parallel() - checker, err := ParseAndCheckWithLinting(t, ` + checker, err := ParseAndCheck(t, ` let x = UFix64(1) `) @@ -78,7 +78,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { t.Parallel() - checker, err := ParseAndCheckWithLinting(t, ` + checker, err := ParseAndCheck(t, ` let x = Fix64(-1) `) @@ -100,7 +100,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { t.Parallel() - checker, err := ParseAndCheckWithLinting(t, ` + checker, err := ParseAndCheck(t, ` let x = UFix64(1.2) `) @@ -120,7 +120,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { t.Parallel() - checker, err := ParseAndCheckWithLinting(t, ` + checker, err := ParseAndCheck(t, ` let x = Fix64(-1.2) `) @@ -144,7 +144,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { t.Parallel() - checker, err := ParseAndCheckWithLinting(t, ` + checker, err := ParseAndCheck(t, ` let x = UInt8(1) `) @@ -164,7 +164,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { t.Parallel() - checker, err := ParseAndCheckWithLinting(t, ` + checker, err := ParseAndCheck(t, ` let x = Int8(1) `) @@ -184,7 +184,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { t.Parallel() - checker, err := ParseAndCheckWithLinting(t, ` + checker, err := ParseAndCheck(t, ` let x = Int8(-1) `) @@ -204,7 +204,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { t.Parallel() - checker, err := ParseAndCheckWithLinting(t, ` + checker, err := ParseAndCheck(t, ` let x = Int(1) `) @@ -224,7 +224,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { t.Parallel() - checker, err := ParseAndCheckWithLinting(t, ` + checker, err := ParseAndCheck(t, ` let x = Int(-1) `) diff --git a/runtime/tests/checker/dynamic_casting_test.go b/runtime/tests/checker/dynamic_casting_test.go index b3528f5a96..c60227d4b8 100644 --- a/runtime/tests/checker/dynamic_casting_test.go +++ b/runtime/tests/checker/dynamic_casting_test.go @@ -1319,7 +1319,7 @@ func TestCheckAlwaysSucceedingDynamicCast(t *testing.T) { operation.Symbol(), ) - checker, err := ParseAndCheckWithLinting(t, types+usage) + checker, err := ParseAndCheck(t, types+usage) errs := ExpectCheckerErrors(t, err, 1) diff --git a/runtime/tests/checker/force_test.go b/runtime/tests/checker/force_test.go index c15a7e0e7a..66cb9c0ae3 100644 --- a/runtime/tests/checker/force_test.go +++ b/runtime/tests/checker/force_test.go @@ -54,7 +54,7 @@ func TestCheckForce(t *testing.T) { t.Run("non-optional", func(t *testing.T) { - checker, err := ParseAndCheckWithLinting(t, ` + checker, err := ParseAndCheck(t, ` let x: Int = 1 let y = x! `) diff --git a/runtime/tests/checker/switch_test.go b/runtime/tests/checker/switch_test.go index f021f161e9..2aad4df484 100644 --- a/runtime/tests/checker/switch_test.go +++ b/runtime/tests/checker/switch_test.go @@ -19,6 +19,7 @@ package checker import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -356,3 +357,366 @@ func TestCheckInvalidSwitchStatementMissingStatements(t *testing.T) { assert.IsType(t, &sema.MissingSwitchCaseStatementsError{}, errs[0]) } + +func TestCheckSwitchStatementDuplicateCases(t *testing.T) { + + t.Parallel() + + t.Run("multiple duplicates", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test(): Int { + let s: String? = nil + + switch s { + case "foo": + return 1 + case "bar": + return 2 + case "bar": + return 3 + case "bar": + return 4 + } + + return -1 + } + `) + + // Should only report two errors. i.e: second and the third + // duplicate cases must not be compared with each other. + errs := ExpectCheckerErrors(t, err, 2) + assert.IsType(t, &sema.DuplicateSwitchCaseError{}, errs[0]) + assert.IsType(t, &sema.DuplicateSwitchCaseError{}, errs[1]) + }) + + t.Run("simple literals", func(t *testing.T) { + type test struct { + name string + expr string + } + + expressions := []test{ + { + name: "string", + expr: "\"hello\"", + }, + { + name: "integer", + expr: "5", + }, + { + name: "fixedpoint", + expr: "4.7", + }, + { + name: "boolean", + expr: "true", + }, + } + + for _, testCase := range expressions { + + t.Run(testCase.name, func(t *testing.T) { + _, err := ParseAndCheck(t, fmt.Sprintf(` + fun test(): Int { + let x = %[1]s + switch x { + case %[1]s: + return 1 + case %[1]s: + return 2 + } + return -1 + }`, + testCase.expr), + ) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DuplicateSwitchCaseError{}, errs[0]) + }) + } + }) + + t.Run("identifier", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test(): Int { + let x = 5 + let y = 5 + switch 4 { + case x: + return 1 + case x: + return 2 + case y: // different identifier + return 3 + } + return -1 + } + `) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DuplicateSwitchCaseError{}, errs[0]) + }) + + t.Run("member access", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test(): Int { + let x = Foo() + + switch x.a { + case x.a: + return 1 + case x.a: + return 2 + case x.b: + return 3 + } + return -1 + } + + struct Foo { + pub var a: String + pub var b: String + init() { + self.a = "foo" + self.b = "bar" + } + } + `) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DuplicateSwitchCaseError{}, errs[0]) + }) + + t.Run("index access", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test(): Int { + let x: [Int] = [1, 2, 3] + let y: [Int] = [5, 6, 7] + + switch x[0] { + case x[1]: + return 1 + case x[1]: + return 2 + case x[2]: + return 3 + case y[1]: + return 4 + } + return -1 + } + `) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DuplicateSwitchCaseError{}, errs[0]) + }) + + t.Run("conditional", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test(): Int { + switch "foo" { + case true ? "foo" : "bar": + return 1 + case true ? "foo" : "bar": + return 2 + case true ? "baz" : "bar": // different then expr + return 3 + case true ? "foo" : "baz": // different else expr + return 4 + case false ? "foo" : "bar": // different condition expr + return 5 + } + return -1 + } + `) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DuplicateSwitchCaseError{}, errs[0]) + }) + + t.Run("unary", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test(): Int { + let x = 5 + let y = x + switch x { + case -x: + return 1 + case -x: + return 2 + case -y: // different rhs expr + return 3 + } + return -1 + } + `) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DuplicateSwitchCaseError{}, errs[0]) + }) + + t.Run("binary", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test(): Int { + switch 4 { + case 3+5: + return 1 + case 3+5: + return 2 + case 3+7: // different rhs expr + return 3 + case 7+5: // different lhs expr + return 4 + case 3-5: // different operator + return 5 + } + return -1 + } + `) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DuplicateSwitchCaseError{}, errs[0]) + }) + + t.Run("cast", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test(): Int { + let x = 5 + let y = x as Integer + switch y { + case x as Integer: + return 1 + case x as Integer: + return 2 + case x as! Integer: // different operator + return 3 + case y as Integer: // different expr + return 4 + } + return -1 + } + `) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DuplicateSwitchCaseError{}, errs[0]) + }) + + t.Run("create", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test() { + let x <- create Foo() + switch x { + } + destroy x + } + + resource Foo {} + `) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.NotEquatableTypeError{}, errs[0]) + }) + + t.Run("destroy", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test() { + let x <- create Foo() + switch destroy x { + } + } + + resource Foo {} + `) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.NotEquatableTypeError{}, errs[0]) + }) + + t.Run("reference", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test(): Int { + let x: Int = 5 + let y: Int = 7 + switch (&x as &Int) { + case &x as &Int: + return 1 + case &x as &Int: + return 2 + case &y as &Int: // different expr + return 2 + } + return -1 + } + `) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DuplicateSwitchCaseError{}, errs[0]) + }) + + t.Run("force", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test(): Int { + let x: Int? = 5 + let y: Int? = 5 + switch 4 { + case x!: + return 1 + case x!: + return 2 + case y!: // different expr + return 3 + } + return -1 + } + `) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DuplicateSwitchCaseError{}, errs[0]) + }) + + t.Run("path", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test() { + switch /public/somepath { + } + } + `) + + errs := ExpectCheckerErrors(t, err, 1) + assert.IsType(t, &sema.NotEquatableTypeError{}, errs[0]) + }) + + t.Run("invocation", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test(): Int { + switch "hello" { + case foo(): + return 1 + case foo(): + return 2 + } + return -1 + } + + fun foo(): String { + return "hello" + } + `) + + assert.NoError(t, err) + }) + + t.Run("default", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + fun test(): Int { + switch "hello" { + default: + return -1 + } + } + `) + + assert.NoError(t, err) + }) +} diff --git a/runtime/tests/checker/utils.go b/runtime/tests/checker/utils.go index b60af157f1..68adfadf84 100644 --- a/runtime/tests/checker/utils.go +++ b/runtime/tests/checker/utils.go @@ -83,6 +83,9 @@ func ParseAndCheckWithOptions( checkerOptions := append( []sema.Option{ sema.WithAccessCheckMode(sema.AccessCheckModeNotSpecifiedUnrestricted), + + // Run all tests with dev-mode enabled. + sema.WithDevModeEnabled(true), }, options.Options..., ) diff --git a/runtime/tests/checker/utils_test.go b/runtime/tests/checker/utils_test.go index 50458433a7..2df3f67d97 100644 --- a/runtime/tests/checker/utils_test.go +++ b/runtime/tests/checker/utils_test.go @@ -53,18 +53,6 @@ func ParseAndCheckWithAny(t *testing.T, code string) (*sema.Checker, error) { Kind: common.DeclarationKindType, }, }), - sema.WithLintingEnabled(true), - }, - }, - ) -} - -func ParseAndCheckWithLinting(t *testing.T, code string) (*sema.Checker, error) { - return ParseAndCheckWithOptions(t, - code, - ParseAndCheckOptions{ - Options: []sema.Option{ - sema.WithLintingEnabled(true), }, }, )