Skip to content

wake.ir.expressions.identifier module #

Identifier class #

Bases: ExpressionAbc

Represents a single identifier referencing a declaration (or multiple overloaded declarations).

Source code in wake/ir/expressions/identifier.py
class Identifier(ExpressionAbc):
    """
    Represents a single identifier referencing a declaration (or multiple overloaded declarations).
    """

    _ast_node: SolcIdentifier
    _parent: weakref.ReferenceType[SolidityAbc]  # TODO: make this more specific

    _name: str
    _overloaded_declarations: List[AstNodeId]
    _referenced_declaration_ids: Set[AstNodeId]

    def __init__(
        self, init: IrInitTuple, identifier: SolcIdentifier, parent: SolidityAbc
    ):
        from ..meta.import_directive import ImportDirective

        super().__init__(init, identifier, parent)
        self._name = identifier.name
        self._overloaded_declarations = list(identifier.overloaded_declarations)
        if identifier.referenced_declaration is None:
            assert isinstance(self.parent, ImportDirective)
            self._referenced_declaration_ids = set()
        else:
            self._referenced_declaration_ids = {identifier.referenced_declaration}
        init.reference_resolver.register_post_process_callback(
            self._post_process, priority=-1
        )

    def _post_process(self, callback_params: CallbackParams):
        from ..meta.import_directive import ImportDirective

        new_referenced_declaration_ids = set()

        for referenced_declaration_id in self._referenced_declaration_ids:
            if referenced_declaration_id < 0:
                global_symbol = GlobalSymbol(referenced_declaration_id)
                self._reference_resolver.register_global_symbol_reference(
                    global_symbol, self
                )
                self._reference_resolver.register_destroy_callback(
                    self.source_unit.file, partial(self._destroy, global_symbol)
                )
                new_referenced_declaration_ids.add(referenced_declaration_id)
            else:
                node = self._reference_resolver.resolve_node(
                    referenced_declaration_id, self.source_unit.cu_hash
                )

                if isinstance(node, DeclarationAbc):
                    node.register_reference(self)
                    self._reference_resolver.register_destroy_callback(
                        self.source_unit.file, partial(self._destroy, node)
                    )
                    new_referenced_declaration_ids.add(referenced_declaration_id)
                elif isinstance(node, ImportDirective):
                    # make this node to reference the source unit directly
                    assert node.unit_alias is not None
                    source_unit = callback_params.source_units[node.imported_file]
                    node_path_order = self._reference_resolver.get_node_path_order(
                        AstNodeId(source_unit.ast_node_id),
                        source_unit.cu_hash,
                    )
                    new_referenced_declaration_ids.add(
                        self._reference_resolver.get_ast_id_from_cu_node_path_order(
                            node_path_order, self.source_unit.cu_hash
                        )
                    )
                else:
                    raise TypeError(f"Unexpected type: {type(node)}")

        self._referenced_declaration_ids = new_referenced_declaration_ids

    def _destroy(
        self, referenced_declaration: Union[GlobalSymbol, DeclarationAbc]
    ) -> None:
        if isinstance(referenced_declaration, GlobalSymbol):
            self._reference_resolver.unregister_global_symbol_reference(
                referenced_declaration, self
            )
        elif isinstance(referenced_declaration, DeclarationAbc):
            referenced_declaration.unregister_reference(self)
        else:
            raise TypeError(f"Unexpected type: {type(referenced_declaration)}")

    @property
    def parent(self) -> SolidityAbc:
        return super().parent

    @property
    def name(self) -> str:
        """
        Returns:
            Name of the referenced declaration.
        """
        return self._name

    @property
    def overloaded_declarations(self) -> FrozenSet[FunctionDefinition]:
        """
        Returns:
            Empty set if [referenced_declaration][wake.ir.expressions.identifier.Identifier.referenced_declaration] is not overloaded.
                Otherwise, set of all overloaded declarations (including [referenced_declaration][wake.ir.expressions.identifier.Identifier.referenced_declaration]) with the same name as the identifier.
        """
        from ..declarations.function_definition import FunctionDefinition

        overloaded_declarations = set()
        for overloaded_declaration_id in self._overloaded_declarations:
            assert overloaded_declaration_id >= 0

            overloaded_declaration = self._reference_resolver.resolve_node(
                overloaded_declaration_id, self.source_unit.cu_hash
            )
            assert isinstance(overloaded_declaration, FunctionDefinition)
            overloaded_declarations.add(overloaded_declaration)

        # fix overloaded declarations are not set for identifiers in ImportDirective symbol alias
        if len(overloaded_declarations) == 0:
            ref_decl = self.referenced_declaration
            if isinstance(ref_decl, set):
                return frozenset(ref_decl)

        return frozenset(overloaded_declarations)

    @property
    def referenced_declaration(
        self,
    ) -> Union[DeclarationAbc, GlobalSymbol, SourceUnit, FrozenSet[FunctionDefinition]]:
        """
        If the referenced function name is overloaded and a single function cannot be inferred from the context, returns a set of all overloaded functions.
        This is the case of identifiers in [ImportDirectives][wake.ir.meta.import_directive.ImportDirective].
        In the following example, the identifier `max` in the example import directive references both `max` functions from `Math.sol`:

        ```solidity title="Math.sol"
        function max(uint256 a, uint256 b) pure returns (uint256) {
            return a >= b ? a : b;
        }
        function max(int256 a, int256 b) pure returns (int256) {
            return a >= b ? a : b;
        }
        ```

        ```solidity title="A.sol"
        import { max } from "./Math.sol";
        ```

        If the referenced function name is overloaded and a single function can be inferred from the context, returns the inferred function.

        A [SourceUnit][wake.ir.meta.source_unit.SourceUnit] is returned if the identifier references a source unit alias defined in an [ImportDirective][wake.ir.meta.import_directive.ImportDirective].
        For example, `Math` in `Math.max(-1, 2)` references the source unit `Math.sol`:

        ```solidity
        import "./Math.sol" as Math;

        contract C {
            function test() public {
                Math.max(-1, 2);
            }
        }
        ```

        Returns:
            Referenced declaration(s).
        """

        def resolve(referenced_declaration_id: AstNodeId):
            if referenced_declaration_id < 0:
                return GlobalSymbol(referenced_declaration_id)

            node = self._reference_resolver.resolve_node(
                referenced_declaration_id, self.source_unit.cu_hash
            )
            assert isinstance(
                node, (DeclarationAbc, SourceUnit)
            ), f"Unexpected type: {type(node)}\n{node.source}\n{self.source}\n{self.source_unit.file}"
            return node

        from ..declarations.function_definition import FunctionDefinition
        from ..meta.source_unit import SourceUnit

        assert len(self._referenced_declaration_ids) != 0
        if len(self._referenced_declaration_ids) == 1:
            return resolve(next(iter(self._referenced_declaration_ids)))
        else:
            # Identifier in ImportDirective symbol alias referencing multiple overloaded functions
            ret = set(map(resolve, self._referenced_declaration_ids))
            assert all(isinstance(x, FunctionDefinition) for x in ret)
            return ret  # pyright: ignore reportGeneralTypeIssues

    @property
    @weak_self_lru_cache(maxsize=2048)
    def is_ref_to_state_variable(self) -> bool:
        referenced_declaration = self.referenced_declaration
        return (
            isinstance(referenced_declaration, VariableDeclaration)
            and referenced_declaration.is_state_variable
        )

    @property
    def modifies_state(
        self,
    ) -> Set[Tuple[Union[ExpressionAbc, StatementAbc, YulAbc], ModifiesStateFlag]]:
        return set()

name: str property #

Returns:

Type Description
str

Name of the referenced declaration.

overloaded_declarations: FrozenSet[FunctionDefinition] property #

Returns:

Type Description
FrozenSet[FunctionDefinition]

Empty set if referenced_declaration is not overloaded. Otherwise, set of all overloaded declarations (including referenced_declaration) with the same name as the identifier.

referenced_declaration: Union[DeclarationAbc, GlobalSymbol, SourceUnit, FrozenSet[FunctionDefinition]] property #

If the referenced function name is overloaded and a single function cannot be inferred from the context, returns a set of all overloaded functions. This is the case of identifiers in ImportDirectives. In the following example, the identifier max in the example import directive references both max functions from Math.sol:

Math.sol
function max(uint256 a, uint256 b) pure returns (uint256) {
    return a >= b ? a : b;
}
function max(int256 a, int256 b) pure returns (int256) {
    return a >= b ? a : b;
}
A.sol
import { max } from "./Math.sol";

If the referenced function name is overloaded and a single function can be inferred from the context, returns the inferred function.

A SourceUnit is returned if the identifier references a source unit alias defined in an ImportDirective. For example, Math in Math.max(-1, 2) references the source unit Math.sol:

import "./Math.sol" as Math;

contract C {
    function test() public {
        Math.max(-1, 2);
    }
}

Returns:

Type Description
Union[DeclarationAbc, GlobalSymbol, SourceUnit, FrozenSet[FunctionDefinition]]

Referenced declaration(s).