def _typecheck(self, text: Text, error: Optional[Text] = None, error_type=XlsTypeError): """Checks the first function in "text" for type errors. Args: text: Text to parse. error: Whether it is expected that the text will cause a type error. error_type: Type of error to check for, if "error" is given. """ filename = '/fake/test_module.x' with fakefs_test_util.scoped_fakefs(filename, text): try: m = parser_helpers.parse_text(text, 'test_module', print_on_error=True, filename=filename) except cpp_parser.CppParseError as e: parser_helpers.pprint_positional_error(e) raise if error: with self.assertRaises(error_type) as cm: typecheck.check_module(m, f_import=None) self.assertIn(error, str(cm.exception)) else: try: typecheck.check_module(m, f_import=None) except (span.PositionalError, cpp_parser.CppParseError) as e: parser_helpers.pprint_positional_error(e) raise
def _get_module( self, program: Text) -> Tuple[ast.Module, type_info_mod.TypeInfo]: filename = '/fake/test_program.x' with fakefs_test_util.scoped_fakefs(filename, program): m, type_info = parse_and_typecheck.parse_text(program, 'test_program', print_on_error=True, f_import=None, filename=filename) return m, type_info
def test_bad_dim_expression(self): with self.assertRaises(parser.CppParseError): program = """ fn [X: u32, Y: u32] foo(x: bits[X + Y]) -> bits[5] { u5:5 } """ with fakefs_test_util.scoped_fakefs(self.fake_filename, program): parser_helpers.parse_text(program, name=self.fake_filename, print_on_error=True, filename=self.fake_filename)
def test_bad_dim(self): with self.assertRaises(parser.ParseError): program = """ fn foo(x: bits[+]) -> bits[5] { u5:5 } """ with fakefs_test_util.scoped_fakefs(self.fake_filename, program): parser_helpers.parse_text(program, name=self.fake_filename, print_on_error=True, filename=self.fake_filename)
def test_invalid_parameter_cast(self): program = """ fn [N: u32]addN(x: u32) -> u32 { x + u32: N } """ with fakefs_test_util.scoped_fakefs(self.fake_filename, program): with self.assertRaises(CppParseError): parser_helpers.parse_text(program, name=self.fake_filename, print_on_error=True, filename=self.fake_filename)
def _get_module(self, program: Text) -> Tuple[ast.Module, type_info_mod.TypeInfo]: filename = '/fake/test_program.x' with fakefs_test_util.scoped_fakefs(filename, program): m, type_info = parse_and_typecheck.parse_text( program, 'test_program', print_on_error=True, import_cache=ImportCache(), additional_search_paths=(), filename=filename) return m, type_info
def _parse_internal( self, program: Text, bindings: Optional[parser.Bindings], fparse: Callable[[parser.Parser, parser.Bindings], TypeVar('T')] ) -> TypeVar('T'): with fakefs_test_util.scoped_fakefs(self.fake_filename, program): s = scanner.Scanner(self.fake_filename, program) b = bindings or parser.Bindings(None) try: e = fparse(parser.Parser(s, 'test_module'), b) except parser.CppParseError as e: parser_helpers.pprint_positional_error(e) raise self.assertTrue(s.at_eof()) return e
def test_generates_valid_functions(self): g = ast_generator.AstGenerator( random.Random(0), ast_generator.AstGeneratorOptions()) for i in range(32): print('Generating sample', i) _, m = g.generate_function_in_module('main', 'test') text = str(m) filename = '/fake/test_sample.x' with fakefs_test_util.scoped_fakefs(filename, text): try: module = parser_helpers.parse_text( text, name='test_sample', print_on_error=True, filename=filename) typecheck.check_module(module, f_import=None) except PositionalError as e: parser_helpers.pprint_positional_error(e) raise
def test_parser_errors(self): with self.assertRaises(parser.CppParseError): program = """ type Tuple2 = (u32, u32); fn tuple_assign(x: u32, y: u32) -> (u32) { // We don't have compound expressions yet, so the use of // curly braces should result in an error. let (i, i): Tuple2 = (x, y); { i } }""" with fakefs_test_util.scoped_fakefs(self.fake_filename, program): parser_helpers.parse_text(program, name=self.fake_filename, print_on_error=True, filename=self.fake_filename)
def test_sign_convert_args_batch(self): dslx_text = 'fn main(y: s8) -> s8 { y }' filename = '/fake/test_module.x' with fakefs_test_util.scoped_fakefs(filename, dslx_text): m = parser_helpers.parse_text(dslx_text, 'test_module', print_on_error=True, filename=filename) f = m.get_function('main') self.assertEqual( sample_runner.sign_convert_args_batch( f, m, ((Value.make_ubits(8, 2), ), )), ((Value.make_sbits(8, 2), ), )) # Converting a value which is already signed is no problem. self.assertEqual( sample_runner.sign_convert_args_batch( f, m, ((Value.make_sbits(8, 2), ), )), ((Value.make_sbits(8, 2), ), ))
def test_pprint_parse_error(self): output = io.StringIO() filename = '/fake/test_file.x' text = 'oh\nwhoops\nI did an\nerror somewhere\nthat is bad' with fakefs_test_util.scoped_fakefs(filename, text): pos = scanner.Pos(filename, lineno=2, colno=0) span = Span(pos, pos.bump_col()) error = parser.ParseError(span, 'This is bad') parser_helpers.pprint_positional_error( error, output=cast(io.IOBase, output), color=False, error_context_line_count=3) expected = textwrap.dedent("""\ /fake/test_file.x:2-4 0002: whoops * 0003: I did an ^^ This is bad @ /fake/test_file.x:3:1 0004: error somewhere """) self.assertMultiLineEqual(expected, output.getvalue())
def test_co_recursion(self): with self.assertRaises(CppParseError) as cm: program = """ // Co-recursion // // Also illegal. Can be found by finding cycles in the call graph. // fn foo(x: u32) -> u32 { let y: u32 = 7; bar(x - 1, y) } fn bar(x: u32, y: u32) -> u32 { let z: u32 = 11; foo(x - 1 + y + z) } """ with fakefs_test_util.scoped_fakefs(self.fake_filename, program): parser_helpers.parse_text(program, name=self.fake_filename, print_on_error=True, filename=self.fake_filename) self.assertIn("Cannot find a definition for name: 'bar'", str(cm.exception))
def test_invalid_constructs(self): # TODO(rhundt): Over time these tests will fail at parse time or # runtime. At this point, we will have to check the relevant # errors and handle them properly. program = """ // A handful of language constructs that could be considered harmful, borderline // illegal, or definitely illegal. They could be allowed, warned about, or // be reported as errors. This file collects all such cases and it will be // used in testing, once the semantics have been clarified. As new cases will // emerge, they should be added to this file as well. // Parameter re-definition. // // This is certainly allowed in imperative languages, and even Haskell allows // the following expression: // f x = let x = 3 in x * x // No matter what you pass to this function, it will always return 9. // // The question is whether or not to warn on this, perhaps we should warn // optionally? // fn param_redefine(x: u32) -> u32 { let x: u32 = 3; x * x } // Dead code // // The following code is legal, but is probably not what the user intended. // We should emit a warning, perhaps optionally. // fn dead_code(x: u32) -> u32 { let y: u32 = 3; let y: u32 = 4; x * y } // Unused code // // The following code is legal, but contains an unused expression. // Languages like Go warn on unused variables. fn unused_code(x: u32) -> u32 { let y: u32 = 3; let z: u32 = 4; x * y } // Unclear semantics in tuple assignments // // This code is probably legal, but it is not clear which value // will be assigned to i in the end. // // Note that types have to be defined globally (we should probably change that // and allow function-scoped types. Also, it is weird that 'type' needs a // semicolon. // type Tuple2 = (u32, u32); fn tuple_assign(x: u32, y: u32) -> (u32) { let (i, i): Tuple2 = (x, y); i } // Invalid init expression size // // It is not clear whether or not this should be caught in the front-end, // or whether some other semantic applies, eg., non-initialized variables // are auto-initialized to the zero element of a given type. fn invalid_init_size() { const K: u32[2] = [0x428a2f98]; K[1] } // Invalid init expression type // // This should probably be an error fn invalid_init_type() { const K: u32[2] = ['a', 'b']; K[1] } // Unused parameter // // Parameter is not used, this could point to a code problem. // fn unused_parm(x: u32, y: u32) { x + 1 } // Double defined parameter // // Two parameters have the same name. This could point to a code problem. // fn double_defined_parm(x: u32, x: u32) { x + 1 } // Invalid index // // Static or dynamic indices can be out of range. The front-end could find // static bounds check violations, but only a limited subset. The middle-end // can find a wider class of violations via copy propatation. The run-time // can find dynamic violations. This test should help to clarify what the limits // are. // fn invalid_index(x: u32) -> u32 { let K: u32[10] = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9); let v: u32 = 10; K[10] // static violation with constant. Should be an error. + K[v] // static violation. Should be found via copy prop. + K[x] // dynamic violation. Can only be found at runtime or via // inter-procedural copy propagation. } // Tail recursion // // Tail recursion can be transformed into a loop, but unless we have // the corresponding pass, this type of code must be considered // illegal. // fn tail_recursion(x: u32) -> u32 { select(x==0, 0, x + tail_recursion(x - 1)) } // Regular recursion // // Is also illegal. // fn regular_recursion(x: u32) -> u32 { let y: u32 = 7; let z: u32 = regular_recursion(x - 1); y + z } """ with fakefs_test_util.scoped_fakefs(self.fake_filename, program): parser_helpers.parse_text(program, name=self.fake_filename, print_on_error=True, filename=self.fake_filename)