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_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 parse_text( text: Text, name: Text, print_on_error: bool, *, import_cache: ImportCache, additional_search_paths: Tuple[str, ...], filename: Text, fs_open: Callable[[Text], io.IOBase] = None, ) -> Tuple[ast.Module, type_info_mod.TypeInfo]: """Parses text into a module with name "name" and typechecks it.""" logging.vlog(1, 'Parsing text; name: %r', name) module = parser_helpers.parse_text(text, name, print_on_error=print_on_error, filename=filename, fs_open=fs_open) logging.vlog(1, 'Parsed text; name: %r', name) try: type_info = cpp_typecheck.check_module(module, import_cache, additional_search_paths) except (XlsTypeError, TypeInferenceError) as e: if print_on_error: # If the typecheck fails, pretty-print the error. parser_helpers.pprint_positional_error(e, fs_open=fs_open) raise return module, type_info
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 main(argv): binary = os.path.basename(argv[0]) if len(argv) < 2: raise app.UsageError('Wrong number of command-line arguments; ' 'expect %s <input-file>' % binary) path = argv[1] with open(path, 'r') as f: text = f.read() name = os.path.basename(path) name, _ = os.path.splitext(name) module = parser_helpers.parse_text(text, name, print_on_error=True, filename=path) import_cache = {} f_import = functools.partial(import_routines.do_import, cache=import_cache) try: node_to_type = typecheck.check_module(module, f_import) if FLAGS.entry: print( ir_converter.convert_one_function(module, FLAGS.entry, node_to_type)) else: print(ir_converter.convert_module(module, node_to_type)) except PositionalError as e: parser_helpers.pprint_positional_error(e) if FLAGS.raise_exception: raise else: sys.exit(1)
def test_parser_errors(self): with self.assertRaises(parser.ParseError): 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) in { i } }""" with fakefs_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_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_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_sign_convert_args_batch(self): dslx_text = 'fn main(y: s8) -> s8 { y }' filename = '/fake/test_module.x' with fakefs_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 main(argv): binary = os.path.basename(argv[0]) if len(argv) < 2: raise app.UsageError('Wrong number of command-line arguments; ' 'expect %s <input-file>' % binary) init_xls.init_xls(sys.argv) path = argv[1] with open(path, 'r') as f: text = f.read() name = os.path.basename(path) name, _ = os.path.splitext(name) module = parser_helpers.parse_text(text, name, print_on_error=True, filename=path) importer = import_helpers.Importer(tuple(FLAGS.dslx_path)) type_info = None try: type_info = cpp_typecheck.check_module( module, importer.cache, importer.additional_search_paths) if FLAGS.entry: print( ir_converter.convert_one_function(module, FLAGS.entry, type_info)) else: print(ir_converter.convert_module(module, type_info)) except (PositionalError, cpp_parser.CppParseError) as e: parser_helpers.pprint_positional_error(e) if FLAGS.raise_exception: raise else: sys.exit(1) finally: if type_info is not None: type_info.clear_type_info_refs_for_gc()
def parse_text( text: Text, name: Text, print_on_error: bool, *, f_import: Optional[Callable], filename: Text, fs_open: Callable[[Text], io.IOBase] = None, ) -> Tuple[ast.Module, deduce.NodeToType]: """Parses text into a module with name "name" and typechecks it.""" module = parser_helpers.parse_text(text, name, print_on_error=print_on_error, filename=filename, fs_open=fs_open) node_to_type = _run_typecheck(module, print_on_error, f_import, fs_open=fs_open) return module, node_to_type
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)