From ec7cb9e054d6450201d449177dccc0008a96d625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 3 Aug 2020 16:05:24 -0700 Subject: [PATCH] handle incomplete types in checker parsing might produce incomplete types. instead of panicing when converting a nil type, just return an invalid type --- runtime/parser2/type_test.go | 140 +++++++++++++++++++---- runtime/sema/checker.go | 4 + runtime/tests/checker/dictionary_test.go | 52 +++++++++ runtime/tests/checker/utils.go | 9 +- 4 files changed, 176 insertions(+), 29 deletions(-) create mode 100644 runtime/tests/checker/dictionary_test.go diff --git a/runtime/parser2/type_test.go b/runtime/parser2/type_test.go index 6d2792a608..910f1a4f5a 100644 --- a/runtime/parser2/type_test.go +++ b/runtime/parser2/type_test.go @@ -22,6 +22,7 @@ import ( "math/big" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/cadence/runtime/ast" @@ -440,7 +441,7 @@ func TestParseRestrictedType(t *testing.T) { t.Parallel() - _, errs := ParseType("{ T , }") + result, errs := ParseType("{ T , }") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -450,13 +451,31 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + utils.AssertEqualWithDiff(t, + &ast.RestrictedType{ + Restrictions: []*ast.NominalType{ + { + Identifier: ast.Identifier{ + Identifier: "T", + Pos: ast.Position{Line: 1, Column: 2, Offset: 2}, + }, + }, + }, + Range: ast.Range{ + StartPos: ast.Position{Line: 1, Column: 0, Offset: 0}, + EndPos: ast.Position{Line: 1, Column: 6, Offset: 6}, + }, + }, + result, + ) }) t.Run("invalid: without restricted type, type without comma", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{ T U }") + result, errs := ParseType("{ T U }") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -466,13 +485,16 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + // TODO: return type + assert.Nil(t, result) }) t.Run("invalid: without restricted type, colon", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{ T , U : V }") + result, errs := ParseType("{ T , U : V }") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -482,13 +504,16 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + // TODO: return type + assert.Nil(t, result) }) t.Run("invalid: with restricted type, colon", func(t *testing.T) { t.Parallel() - _, errs := ParseType("T{U , V : W }") + result, errs := ParseType("T{U , V : W }") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -498,13 +523,16 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + // TODO: return type + assert.Nil(t, result) }) t.Run("invalid: without restricted type, first is non-nominal", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{[T]}") + result, errs := ParseType("{[T]}") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -514,13 +542,16 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + // TODO: return type with non-nominal restrictions + assert.Nil(t, result) }) t.Run("invalid: with restricted type, first is non-nominal", func(t *testing.T) { t.Parallel() - _, errs := ParseType("T{[U]}") + result, errs := ParseType("T{[U]}") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -530,13 +561,16 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + // TODO: return type + assert.Nil(t, result) }) t.Run("invalid: without restricted type, second is non-nominal", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{T, [U]}") + result, errs := ParseType("{T, [U]}") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -546,13 +580,16 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + // TODO: return type + assert.Nil(t, result) }) t.Run("invalid: with restricted type, second is non-nominal", func(t *testing.T) { t.Parallel() - _, errs := ParseType("T{U, [V]}") + result, errs := ParseType("T{U, [V]}") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -562,13 +599,16 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + // TODO: return type + assert.Nil(t, result) }) t.Run("invalid: without restricted type, missing end", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{") + result, errs := ParseType("{") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -578,13 +618,15 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + assert.Nil(t, result) }) t.Run("invalid: with restricted type, missing end", func(t *testing.T) { t.Parallel() - _, errs := ParseType("T{") + result, errs := ParseType("T{") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -594,13 +636,15 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + assert.Nil(t, result) }) t.Run("invalid: without restricted type, missing end after type", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{U") + result, errs := ParseType("{U") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -610,13 +654,15 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + assert.Nil(t, result) }) t.Run("invalid: with restricted type, missing end after type", func(t *testing.T) { t.Parallel() - _, errs := ParseType("T{U") + result, errs := ParseType("T{U") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -626,13 +672,15 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + assert.Nil(t, result) }) t.Run("invalid: without restricted type, missing end after comma", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{U,") + result, errs := ParseType("{U,") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -642,13 +690,15 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + assert.Nil(t, result) }) t.Run("invalid: with restricted type, missing end after comma", func(t *testing.T) { t.Parallel() - _, errs := ParseType("T{U,") + result, errs := ParseType("T{U,") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -658,13 +708,15 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + assert.Nil(t, result) }) t.Run("invalid: without restricted type, just comma", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{,}") + result, errs := ParseType("{,}") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -674,13 +726,15 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + assert.Nil(t, result) }) t.Run("invalid: with restricted type, just comma", func(t *testing.T) { t.Parallel() - _, errs := ParseType("T{,}") + result, errs := ParseType("T{,}") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -690,6 +744,8 @@ func TestParseRestrictedType(t *testing.T) { }, errs, ) + + assert.Nil(t, result) }) } @@ -731,7 +787,7 @@ func TestParseDictionaryType(t *testing.T) { t.Parallel() - _, errs := ParseType("{T:}") + result, errs := ParseType("{T:}") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -741,13 +797,30 @@ func TestParseDictionaryType(t *testing.T) { }, errs, ) + + utils.AssertEqualWithDiff(t, + &ast.DictionaryType{ + KeyType: &ast.NominalType{ + Identifier: ast.Identifier{ + Identifier: "T", + Pos: ast.Position{Line: 1, Column: 1, Offset: 1}, + }, + }, + ValueType: nil, + Range: ast.Range{ + StartPos: ast.Position{Line: 1, Column: 0, Offset: 0}, + EndPos: ast.Position{Line: 1, Column: 3, Offset: 3}, + }, + }, + result, + ) }) t.Run("invalid, missing key and value type", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{:}") + result, errs := ParseType("{:}") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -757,13 +830,15 @@ func TestParseDictionaryType(t *testing.T) { }, errs, ) + + assert.Nil(t, result) }) t.Run("invalid, missing key type", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{:U}") + result, errs := ParseType("{:U}") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -773,13 +848,16 @@ func TestParseDictionaryType(t *testing.T) { }, errs, ) + + // TODO: return type + assert.Nil(t, result) }) t.Run("invalid, unexpected comma after value type", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{T:U,}") + result, errs := ParseType("{T:U,}") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -789,13 +867,16 @@ func TestParseDictionaryType(t *testing.T) { }, errs, ) + + // TODO: return type + assert.Nil(t, result) }) t.Run("invalid, unexpected colon after value type", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{T:U:}") + result, errs := ParseType("{T:U:}") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -805,13 +886,16 @@ func TestParseDictionaryType(t *testing.T) { }, errs, ) + + // TODO: return type + assert.Nil(t, result) }) t.Run("invalid, unexpected colon after colon", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{T::U}") + result, errs := ParseType("{T::U}") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -821,13 +905,16 @@ func TestParseDictionaryType(t *testing.T) { }, errs, ) + + // TODO: return type + assert.Nil(t, result) }) t.Run("invalid, missing value type after colon", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{T:") + result, errs := ParseType("{T:") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -837,13 +924,15 @@ func TestParseDictionaryType(t *testing.T) { }, errs, ) + + assert.Nil(t, result) }) t.Run("invalid, missing end after key type and value type", func(t *testing.T) { t.Parallel() - _, errs := ParseType("{T:U") + result, errs := ParseType("{T:U") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -853,8 +942,9 @@ func TestParseDictionaryType(t *testing.T) { }, errs, ) - }) + assert.Nil(t, result) + }) } func TestParseFunctionType(t *testing.T) { diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index fdf700c4d9..8de274c32b 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -857,6 +857,10 @@ func (checker *Checker) ConvertType(t ast.Type) Type { case *ast.InstantiationType: return checker.convertInstantiationType(t) + + case nil: + // The AST might contain "holes" if parsing failed + return &InvalidType{} } panic(&astTypeConversionError{invalidASTType: t}) diff --git a/runtime/tests/checker/dictionary_test.go b/runtime/tests/checker/dictionary_test.go new file mode 100644 index 0000000000..c185cca9b6 --- /dev/null +++ b/runtime/tests/checker/dictionary_test.go @@ -0,0 +1,52 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2020 Dapper Labs, Inc. + * + * 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 checker + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/runtime/sema" +) + +func TestCheckIncompleteDictionaryType(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheckWithOptions(t, + ` + let dict: {Int:} = {} + `, + ParseAndCheckOptions{ + IgnoreParseError: true, + }, + ) + + require.NoError(t, err) + + assert.IsType(t, + &sema.DictionaryType{ + KeyType: &sema.IntType{}, + ValueType: &sema.InvalidType{}, + }, + checker.GlobalValues["dict"].Type, + ) +} diff --git a/runtime/tests/checker/utils.go b/runtime/tests/checker/utils.go index 1fc1852343..ff003997e3 100644 --- a/runtime/tests/checker/utils.go +++ b/runtime/tests/checker/utils.go @@ -36,9 +36,10 @@ func ParseAndCheck(t *testing.T, code string) (*sema.Checker, error) { } type ParseAndCheckOptions struct { - ImportResolver ast.ImportResolver - Location ast.Location - Options []sema.Option + ImportResolver ast.ImportResolver + Location ast.Location + IgnoreParseError bool + Options []sema.Option } func ParseAndCheckWithOptions( @@ -48,7 +49,7 @@ func ParseAndCheckWithOptions( ) (*sema.Checker, error) { program, err := parser2.ParseProgram(code) - if !assert.NoError(t, err) { + if !options.IgnoreParseError && !assert.NoError(t, err) { assert.FailNow(t, errors.UnrollChildErrors(err)) return nil, err }