def modify_psyir_tree(): ''' Apply modifications to the PSyIR tree created in create.py :returns: a modified PSyIR tree. :rtype: :py:class:`psyclone.psyir.nodes.Container` ''' file_container = create_psyir_tree() container = file_container.children[0] subroutine = container.children[0] # Rename one of the subroutine local symbols. tmp_symbol = subroutine.symbol_table.lookup("psyir_tmp") subroutine.symbol_table.rename_symbol(tmp_symbol, "new_variable") # The type of a symbol might be unknown symbol = Symbol("unused") container.symbol_table.add(symbol) # later its type could be determined. However, we don't want to # replace the existing symbol instance with a new instance as it # may have references, which could then lead to inconsistencies. Therefore # we support the specialise method, which transforms the existing # node type to a subclass of type without changing the memory # location of the instance. Note, any additional subclass properties would # have to be added manually. symbol.specialise(RoutineSymbol) # In some cases we may want to replace one node with another. This # can be simply done using a node's replace_with method. assignment = subroutine.children[2] assignment_rhs = assignment.rhs assignment_rhs.replace_with(Reference(tmp_symbol)) return file_container
def test_symbol_specialise(): '''Test the Symbol.specialise() method.''' # pylint: disable = unidiomatic-typecheck asym = Symbol("a") assert type(asym) is Symbol assert str(asym) == "a" asym.specialise(RoutineSymbol) assert type(asym) is RoutineSymbol assert str(asym) == "a : RoutineSymbol"
def test_symbol_copy(): ''' Test the Symbol.copy() method. ''' csym = ContainerSymbol("some_mod") asym = Symbol("a", visibility=Symbol.Visibility.PRIVATE, interface=GlobalInterface(csym)) new_sym = asym.copy() assert new_sym is not asym assert new_sym.name == asym.name assert new_sym.interface == asym.interface assert new_sym.visibility == asym.visibility
def test_kernelfunctor_str(): '''Check the str method of the KernelFunctor class.''' symbol = DataTypeSymbol("hello", StructureType()) arg = Reference(Symbol("dummy")) klr = KernelFunctor.create(symbol, [arg]) assert klr.__str__() == "KernelFunctor[name='hello']"
def test_kernelfunctor_node_str(): '''Check the node_str method of the KernelFunctor class.''' symbol = DataTypeSymbol("hello", StructureType()) arg = Reference(Symbol("dummy")) klr = KernelFunctor.create(symbol, [arg]) coloredtext = colored("KernelFunctor", KernelFunctor._colour) assert klr.node_str() == coloredtext + "[name='hello']"
def test_create_struct_reference(): ''' Tests for the _create_struct_reference() utility. ''' one = Literal("1", INTEGER_TYPE) with pytest.raises(InternalError) as err: _create_struct_reference(None, StructureReference, Symbol("fake"), ["hello", 1], []) assert ("List of members must contain only strings or tuples but found " "entry of type 'int'" in str(err.value)) with pytest.raises(NotImplementedError) as err: _create_struct_reference(None, StructureType, Symbol("fake"), ["hello"], []) assert "Cannot create structure reference for type '" in str(err.value) with pytest.raises(InternalError) as err: _create_struct_reference(None, StructureReference, DataSymbol("fake", DeferredType()), ["hello"], [one.copy()]) assert ("Creating a StructureReference but array indices have been " "supplied" in str(err.value)) with pytest.raises(InternalError) as err: _create_struct_reference(None, ArrayOfStructuresReference, DataSymbol("fake", DeferredType()), ["hello"], []) assert ("Cannot create an ArrayOfStructuresReference without one or more " "index expressions" in str(err.value)) ref = _create_struct_reference(None, StructureReference, DataSymbol("fake", DeferredType()), ["hello"], []) assert isinstance(ref, StructureReference) assert isinstance(ref.member, Member) assert ref.member.name == "hello" # Check that we can create an ArrayOfStructuresReference and that any # PSyIR nodes are copied. idx_var = one.copy() idx_var2 = one.copy() aref = _create_struct_reference(None, ArrayOfStructuresReference, DataSymbol("fake", DeferredType()), ["a", ("b", [idx_var2])], [idx_var]) assert isinstance(aref, ArrayOfStructuresReference) assert isinstance(aref.member, StructureMember) assert aref.member.name == "a" assert aref.member.member.name == "b" assert len(aref.member.member.indices) == 1 assert aref.member.member.indices[0] is not idx_var2 assert len(aref.indices) == 1 assert aref.indices[0] is not idx_var
def test_kernelfunctor_invalid_symbol(): '''Check KernelFunctor raises the expected exception if the type of the symbol argument is invalid. ''' with pytest.raises(TypeError) as info: _ = KernelFunctor(Symbol("hello")) assert ("KernelFunctor symbol argument should be a DataTypeSymbol but " "found 'Symbol'." in str(info.value))
def test_kernelfunctor_create_invalid_symbol(): '''Check that the create method of KernelFunctor raises the expected exception if the provided symbol argument is not the correct type. ''' symbol = Symbol("hello") with pytest.raises(GenerationError) as info: _ = KernelFunctor.create(symbol, []) assert ("KernelFunctor create() symbol argument should be a DataTypeSymbol" " but found 'Symbol'." in str(info.value))
def test_symbol_interface_setter(): '''Test that the Symbol interface setter behaves as expected, including raising an exception if the input is of the wrong type. Also use this to test the is_local, is_global and is_argument and is_unresolved properties. ''' symbol = Symbol('sym1') assert symbol.is_local assert not symbol.is_global assert not symbol.is_argument assert not symbol.is_unresolved symbol.interface = GlobalInterface(ContainerSymbol("my_mod")) assert not symbol.is_local assert symbol.is_global assert not symbol.is_argument assert not symbol.is_unresolved symbol.interface = ArgumentInterface() assert not symbol.is_local assert not symbol.is_global assert symbol.is_argument assert not symbol.is_unresolved symbol.interface = UnresolvedInterface() assert not symbol.is_local assert not symbol.is_global assert not symbol.is_argument assert symbol.is_unresolved with pytest.raises(TypeError) as info: symbol.interface = "hello" assert ("The interface to a Symbol must be a SymbolInterface but got " "'str'" in str(info.value))
def test_symbol_initialisation(): '''Test that a Symbol instance can be created when valid arguments are given, otherwise raise relevant exceptions. Also tests the internal Visibility class, the name, visibility and interface properties. ''' sym = Symbol("sym1") assert isinstance(sym, Symbol) assert sym.name == "sym1" assert sym.visibility == Symbol.DEFAULT_VISIBILITY assert isinstance(sym.interface, LocalInterface) # Check that the default visibility is public assert Symbol.DEFAULT_VISIBILITY == Symbol.Visibility.PUBLIC sym = Symbol("sym2", Symbol.Visibility.PRIVATE) assert sym.visibility == Symbol.Visibility.PRIVATE sym = Symbol("sym3", interface=UnresolvedInterface()) assert isinstance(sym.interface, UnresolvedInterface) with pytest.raises(TypeError) as error: sym = Symbol(None) assert ("Symbol 'name' attribute should be of type 'str'" in str(error.value)) with pytest.raises(TypeError) as error: Symbol('sym1', visibility="hello") assert ("Symbol 'visibility' attribute should be of type " "psyir.symbols.Symbol.Visibility but" in str(error.value)) with pytest.raises(TypeError) as error: Symbol('sym1', interface="hello") assert ("The interface to a Symbol must be a SymbolInterface but got " "'str'" in str(error.value))
def test_kernelfunctor_create(cls): '''Check that the create method of KernelFunctor works as expected. ''' symbol = DataTypeSymbol("hello", StructureType()) klr = cls.create(symbol, []) # pylint: disable=unidiomatic-typecheck assert type(klr) is cls assert klr._symbol == symbol assert len(klr.children) == 0 arg = Reference(Symbol("dummy")) klr = KernelFunctor.create(symbol, [arg]) assert len(klr.children) == 1 assert klr.children[0] == arg assert arg.parent == klr
def test_specialise_symbol(): '''Test that the specialise_symbol method work as expected. ''' symbol = Symbol("hello") # Check that a Symbol is specialised InvokeCallTrans._specialise_symbol(symbol) assert isinstance(symbol, TypeSymbol) # pylint: disable=no-member assert isinstance(symbol.datatype, StructureType) # Check that something that is not a symbol is ignored test = "hello" InvokeCallTrans._specialise_symbol(test) assert isinstance(test, str) assert test == "hello"
def name_from_tag(self, tag, root=None, check_ancestors=True): '''If the supplied tag exists in this symbol table (if the `check_ancestors` argument is False) or in this or any ancestor symbol table (if the `check_ancestors` argument is True), then return the symbol name associated with it, otherwise create a new symbol associated with this tag (using the tag as name or optionally the provided root) and return the name of the new symbol. Note that this method creates generic Symbols without properties like datatypes and just returns the name string (not the Symbol object). This is commonly needed on the current psy-layer implementation but not recommended on new style PSyIR. This method may be deprecated in the future. (TODO #720) There is no need to check the argument types as this method calls methods which check the argument types. :param str tag: tag identifier. :param str root: optional name of the new symbols if this needs to \ be created. :param bool check_ancestors: optional logical flag indicating \ whether the tag should be from just this symbol table \ (False) or this and all ancestor symbol tables \ (True). Defaults to True. :returns: name associated with the given tag. :rtype: str ''' try: return self.lookup_with_tag(tag, check_ancestors=check_ancestors).name except KeyError: if root: name = self.new_symbol_name(root, check_ancestors=check_ancestors) else: name = self.new_symbol_name(tag, check_ancestors=check_ancestors) # No need to check ancestors as this has already been done # when creating the name. self.add(Symbol(name), tag=tag, check_ancestors=False) return name
def test_symbol_array_handling(fortran_reader): '''Verifies the handling of arrays together with access information. ''' # Make sure that a normal `Symbol` raises an exception if it is tested # if it is an array. A `Symbol` is only used if there is no type # information is available, e.g. because it is imported from another # module: asym = Symbol("a") with pytest.raises(ValueError) as error: _ = asym.is_array assert "No array information is available for the symbol 'a'." \ in str(error.value) # Import additional tests from access_info_test to reach 100% coverage # for the is_array_access function. Import these tests locally only. # pylint: disable=import-outside-toplevel from psyclone.tests.core.access_info_test import \ test_symbol_array_detection test_symbol_array_detection(fortran_reader)
def test_symbol_resolve_deferred(monkeypatch): ''' Test the resolve_deferred method. ''' # resolve_deferred() for a local symbol has nothing to do so should # just return itself. asym = Symbol("a") assert asym.resolve_deferred() is asym # Now test for a symbol that is imported from another Container other_container = ContainerSymbol("some_mod") bsym = Symbol("b", visibility=Symbol.Visibility.PRIVATE, interface=GlobalInterface(other_container)) # Monkeypatch the get_external_symbol() method so that it just returns # a new DataSymbol monkeypatch.setattr(bsym, "get_external_symbol", lambda: DataSymbol("b", INTEGER_SINGLE_TYPE)) new_sym = bsym.resolve_deferred() # We should have a brand new symbol but with some of the properties # of the original 'bsym' symbol. assert new_sym is not bsym assert new_sym.datatype == INTEGER_SINGLE_TYPE assert new_sym.visibility == Symbol.Visibility.PRIVATE assert new_sym.is_global
def create_driver(self, input_list, output_list): # pylint: disable=too-many-locals, too-many-statements '''This function creates a driver that can read the output created by the extraction code. This is a stand-alone program that will read the input data, calls the kernels/ instrumented region, and then compares the results with the stored results in the file. TODO: #644: we need type information here. :param input_list: list of variables that are input parameters. :type input_list: list of str :param output_list: list of variables that are output parameters. :type output_list: list or str ''' from psyclone.f2pygen import AllocateGen, AssignGen, CallGen,\ CommentGen, DeclGen, ModuleGen, SubroutineGen, UseGen, \ TypeDeclGen from psyclone.gocean1p0 import GOSymbolTable from psyclone.psyir.symbols import Symbol all_vars = list(set(input_list).union(set(output_list))) all_vars.sort() module_name, region_name = self.region_identifier module = ModuleGen(name=module_name) prog = SubroutineGen(parent=module, name=module_name+"_code", implicitnone=True) module.add(prog) use = UseGen(prog, self.add_psydata_class_prefix("psy_data_mod"), only=True, funcnames=[self.add_psydata_class_prefix("PSyDataType")]) prog.add(use) # Use a symbol table to make sure all variable names are unique sym_table = GOSymbolTable() sym = Symbol("PSyDataType") sym_table.add(sym) psy_data = sym_table.new_symbol_name(self.add_psydata_class_prefix ("psy_data")) sym_table.add(Symbol(psy_data)) var_decl = TypeDeclGen(prog, datatype=self.add_psydata_class_prefix ("PSyDataType"), entity_decls=[psy_data]) prog.add(var_decl) call = CallGen(prog, "{0}%OpenRead(\"{1}\", \"{2}\")" .format(psy_data, module_name, region_name)) prog.add(call) post_suffix = self._post_name # Variables might need to be renamed in order to guarantee unique # variable names in the driver: An example of this would be if the # user code contains a variable 'dx', and the kernel takes a # property 'dx' as well. In the original code that is no problem, # since the property is used via field%grid%dx. But the stand-alone # driver renames field%grid%dx to dx, which can cause a name clash. # Similar problems can exist with any user defined type, since all # user defined types are rewritten to just use the field name. # We use a mapping to support renaming of variables: it takes as # key the variable as used in the original program (e.g. 'dx' from # an expression like field%grid%dx), and maps it to a unique local # name (e.g. dx_0). rename_variable = {} for var_name in all_vars: # TODO #644: we need to identify arrays!! # Support GOcean properties, which are accessed via a # derived type (e.g. 'fld%grid%dx'). In this stand-alone # driver we don't have the derived type, instead we create # variable based on the field in the derived type ('dx' # in the example above), and pass this variable to the # instrumented code. last_percent = var_name.rfind("%") if last_percent > -1: # Strip off the derived type, and only leave the last # field, which is used as the local variable name. local_name = var_name[last_percent+1:] else: # No derived type, so we can just use the # variable name directly in the driver local_name = var_name unique_local_name = sym_table.new_symbol_name(local_name) rename_variable[local_name] = unique_local_name sym_table.add(Symbol(unique_local_name)) local_name = unique_local_name # TODO: #644 - we need to identify arrays!! # Any variable used needs to be defined. We also need # to handle the kind property better and not rely on # a hard-coded value. decl = DeclGen(prog, "real", [local_name], kind="8", dimension=":,:", allocatable=True) prog.add(decl) is_input = var_name in input_list is_output = var_name in output_list if is_input and not is_output: # We only need the pre-variable, and we can read # it from the file (this call also allocates space for it). call = CallGen(prog, "{0}%ReadVariable(\"{1}\", {2})" .format(psy_data, var_name, local_name)) prog.add(call) elif is_input: # Now must be input and output: # First read the pre-variable (which also allocates it): call = CallGen(prog, "{0}%ReadVariable(\"{1}\", {2})" .format(psy_data, var_name, local_name)) prog.add(call) # Then declare the post variable, and and read its values # (ReadVariable will also allocate it) sym = Symbol(local_name+post_suffix) sym_table.add(sym) decl = DeclGen(prog, "real", [local_name+post_suffix], dimension=":,:", kind="8", allocatable=True) prog.add(decl) call = CallGen(prog, "{0}%ReadVariable(\"{1}{3}\", {2}{3})" .format(psy_data, var_name, local_name, post_suffix)) prog.add(call) else: # Now the variable is output only. We need to read the # post variable in, and create and allocate a pre variable # with the same size as the post sym = Symbol(local_name+post_suffix) sym_table.add(sym) decl = DeclGen(prog, "real", [local_name+post_suffix], dimension=":,:", kind="8", allocatable=True) prog.add(decl) call = CallGen(prog, "{0}%ReadVariable(\"{1}{3}\", {2}{3})" .format(psy_data, var_name, local_name, post_suffix)) prog.add(call) decl = DeclGen(prog, "real", [local_name], kind="8", dimension=":,:", allocatable=True) prog.add(decl) alloc = AllocateGen(prog, [var_name], mold="{0}".format(local_name + post_suffix)) prog.add(alloc) # Initialise the variable with 0, since it might contain # values that are not set at all (halo regions, or a # kernel might not set all values). This way the array # comparison with the post value works as expected # TODO #644 - create the right "0.0" type here (e.g. # 0.0d0, ...) assign = AssignGen(prog, local_name, "0.0d0") prog.add(assign) # Now add the region that was extracted here: prog.add(CommentGen(prog, "")) prog.add(CommentGen(prog, " RegionStart")) # For the driver we have to re-create the code of the # instrumented region, but in this stand-alone driver the # arguments are not dl_esm_inf fields anymore, but simple arrays. # Similarly, for properties we cannot use e.g. 'fld%grid%dx' # anymore, we have to use e.g. a local variable 'dx' that has # been created. Since we are using the existing way of creating # the code for the instrumented region, we need to modify how # these variables are created. We do this by temporarily # modifying the properties in the config file. api_config = Config.get().api_conf("gocean1.0") all_props = api_config.grid_properties # Keep a copy of the original values, so we can restore # them later orig_props = dict(all_props) # 1) A grid property is defined like "{0}%grid%dx". This is # changed to be just 'dx', i.e. the final component of # the current value (but we also take renaming into account, # so 'dx' might become 'dx_0'). # If a property is not used, it doesn't matter if we modify # its definition, so we just change all properties. for name, prop in all_props.items(): last_percent = prop.fortran.rfind("%") if last_percent > -1: # Get the last field name, which will be the # local variable name local_name = prop.fortran[last_percent+1:] unique_name = rename_variable.get(local_name, local_name) all_props[name] = GOceanConfig.make_property( unique_name, prop.type, prop.intrinsic_type) # 2) The property 'grid_data' is a reference to the data on the # grid (i.e. the actual field) , and it is defined as "{0}%data". # This just becomes {0} ('a_fld%data' in the original program # becomes just 'a_fld', and 'a_fld' is declared to be a plain # Fortran 2d-array) all_props["go_grid_data"] = GOceanConfig.make_property( "{0}", "array", "real") # Each kernel caches the argument code, so we also # need to clear this cached data to make sure the new # value for "go_grid_data" is actually used. from psyclone.psyGen import CodedKern for kernel in self.psy_data_body.walk(CodedKern): kernel.clear_cached_data() # Recreate the instrumented region. Due to the changes in the # config files, fields and properties will now become local # plain arrays and variables: for child in self.psy_data_body: child.gen_code(prog) # Now reset all properties back to the original values: for name in all_props.keys(): all_props[name] = orig_props[name] prog.add(CommentGen(prog, " RegionEnd")) prog.add(CommentGen(prog, "")) for var_name in output_list: prog.add(CommentGen(prog, " Check {0}".format(var_name))) code = str(module.root) with open("driver-{0}-{1}.f90". format(module_name, region_name), "w") as out: out.write(code)
def test_symbol_str(): '''Test that a Symbol instance can be stringified''' sym = Symbol("my_symbol") assert str(sym) == "my_symbol"
def __init__(self, ast=None, children=None, parent=None, options=None): if not options: options = {} # This string stores a prefix to be used with all external PSyData # symbols (i.e. data types and module name), used in the # method 'add_psydata_class_prefix'. self._class_string = options.get("prefix", "") if self._class_string: self._class_string = self._class_string + "_" # Root of the name to use for variables associated with # PSyData regions self._psy_data_symbol_with_prefix = \ self.add_psydata_class_prefix("psy_data") # The use statement that will be inserted. Any use of a module # of the same name that doesn't match this will result in a # NotImplementedError at code-generation time. self.use_stmt = "use {0}, only: "\ .format(self.add_psydata_class_prefix("psy_data_mod")) + \ ", ".join(self.add_psydata_class_prefix(symbol) for symbol in PSyDataNode.symbols) if children: # We need to store the position of the original children, # i.e. before they are added to a schedule node_position = children[0].position # A PSyData node always contains a Schedule sched = self._insert_schedule(children) super(PSyDataNode, self).__init__(ast=ast, children=[sched], parent=parent) # Get or create a symbol table so we can avoid name clashes # when creating variables if parent and hasattr(self.root, 'symbol_table'): symtab = self.root.symbol_table else: # FIXME: This may not be a good solution symtab = SymbolTable() # Store the name of the PSyData variable that is used for this # PSyDataNode. This allows the variable name to be shown in str # (and also, calling create_name in gen() would result in the name # being changed every time gen() is called). self._var_name = symtab.new_symbol_name( self._psy_data_symbol_with_prefix) symtab.add(Symbol(self._var_name)) if children and parent: # Correct the parent's list of children. Use a slice of the list # of nodes so that we're looping over a local copy of the list. # Otherwise things get confused when we remove children from # the list. for child in children[:]: # Remove child from the parent's list of children parent.children.remove(child) # Add this node as a child of the parent # of the nodes being enclosed and at the original location # of the first of these nodes parent.addchild(self, index=node_position) elif parent: parent.addchild(self) # Name of the region. In general at constructor time we might # not have a parent subroutine or any child nodes, so # the name is left empty, unless explicitly provided by the # user. If names are not provided here then the region and # module names are set the first time gen() is called (and # then remain unchanged). self._module_name = None self._region_name = None # The region identifier caches the computed module- and region-name # as a tuple of strings. This is required so that a derived class can # query the actual name of region (e.g. during generation of a driver # for an extract node). If the user does not define a name, i.e. # module_name and region_name are empty, a unique name will be # computed in gen_code(). If this name would then be stored in # module_name and region_name, and gen() is called again, the # names would not be computed again, since the code detects already # defined module and region names. This can then result in duplicated # region names: The test 'test_region' in profile_test triggers this. # gen()) is called first after one profile region is applied, then # another profile region is added, and gen() is called again. The # second profile region would compute a new name, which then happens # to be the same as the name computed for the first region in the # first gen_code call (which indeed implies that the name of the # first profile region is different the second time it is computed). # So in order to guarantee that the computed module and region names # are unique when gen_code is called more than once, we # cannot store a computed name in module_name and region_name. self._region_identifier = ("", "") name = options.get("region_name", None) if name: # pylint: disable=too-many-boolean-expressions if not isinstance(name, tuple) or not len(name) == 2 or \ not name[0] or not isinstance(name[0], str) or \ not name[1] or not isinstance(name[1], str): raise InternalError("Error in PSyDataNode. The name must be a " "tuple containing two non-empty strings.") # pylint: enable=too-many-boolean-expressions # Valid PSyData names have been provided by the user. self._module_name = name[0] self._region_name = name[1] self.set_region_identifier(self._module_name, self._region_name)
def test_get_external_symbol(monkeypatch): ''' Test the get_external_symbol() method. ''' asym = Symbol("a") with pytest.raises(NotImplementedError) as err: asym.get_external_symbol() assert ("trying to resolve symbol 'a' properties, the lazy evaluation " "of 'Local' interfaces is not supported" in str(err.value)) other_container = ContainerSymbol("some_mod") ctable = SymbolTable() ctable.add(other_container) # Create a Symbol that is imported from the "some_mod" Container bsym = Symbol("b", interface=GlobalInterface(other_container)) ctable.add(bsym) _ = Container.create("test", ctable, [KernelSchedule("dummy")]) # Monkeypatch the container's FortranModuleInterface so that it always # appears to be unable to find the "some_mod" module def fake_import(name): raise SymbolError("Oh dear") monkeypatch.setattr(other_container._interface, "import_container", fake_import) with pytest.raises(SymbolError) as err: bsym.get_external_symbol() assert ("trying to resolve the properties of symbol 'b' in module " "'some_mod': PSyclone SymbolTable error: Oh dear" in str(err.value)) # Now create a Container for the 'some_mod' module and attach this to # the ContainerSymbol ctable2 = SymbolTable() some_mod = Container.create("some_mod", ctable2, [KernelSchedule("dummy2")]) other_container._reference = some_mod # Currently the Container does not contain an entry for 'b' with pytest.raises(SymbolError) as err: bsym.get_external_symbol() assert ("trying to resolve the properties of symbol 'b'. The interface " "points to module 'some_mod' but could not find the definition" in str(err.value)) # Add an entry for 'b' to the Container's symbol table ctable2.add(DataSymbol("b", INTEGER_SINGLE_TYPE)) new_sym = bsym.resolve_deferred() assert isinstance(new_sym, DataSymbol) assert new_sym.datatype == INTEGER_SINGLE_TYPE
def test_find_symbol_table(): ''' Test the find_symbol_table() method. ''' sym = Symbol("a_var") with pytest.raises(TypeError) as err: sym.find_symbol_table("3") assert ("expected to be passed an instance of psyir.nodes.Node but got " "'str'" in str(err.value)) # Search for a SymbolTable with only one level of hierarchy sched = KernelSchedule("dummy") table = sched.symbol_table table.add(sym) assert sym.find_symbol_table(sched) is table # Create a Container so that we have two levels of hierarchy ctable = SymbolTable() sym2 = Symbol("b_var") ctable.add(sym2) _ = Container.create("test", ctable, [sched]) assert sym2.find_symbol_table(sched) is ctable # A Symbol that isn't in any table sym3 = Symbol("missing") assert sym3.find_symbol_table(sched) is None # When there is no SymbolTable associated with the PSyIR node orphan = Literal("1", INTEGER_SINGLE_TYPE) assert sym3.find_symbol_table(orphan) is None