src/basilisp/lang/compiler/generator.py
def _def_to_py_ast(  # pylint: disable=too-many-branches
    ctx: GeneratorContext, node: Def
) -> GeneratedPyAST:
    """Return a Python AST Node for a `def` expression."""
    assert node.op == NodeOp.DEF

    defsym = node.name
    is_defn = False

    if node.init is not None:
        # Since Python function definitions always take the form `def name(...):`,
        # it is redundant to assign them to the their final name after they have
        # been defined under a private alias. This codepath generates `defn`
        # declarations by directly generating the Python `def` with the correct
        # function name and short-circuiting the default double-declaration.
        if node.init.op == NodeOp.FN:
            assert isinstance(node.init, Fn)
            def_ast = _fn_to_py_ast(ctx, node.init, def_name=defsym.name)
            is_defn = True
        elif (
            node.init.op == NodeOp.WITH_META
            and isinstance(node.init, WithMeta)
            and node.init.expr.op == NodeOp.FN
        ):
            assert isinstance(node.init, WithMeta)
            def_ast = _with_meta_to_py_ast(ctx, node.init, def_name=defsym.name)
            is_defn = True
        else:
            def_ast = gen_py_ast(ctx, node.init)
    else:
        def_ast = GeneratedPyAST(node=ast.NameConstant(None))

    ns_name = ast.Call(func=_NEW_SYM_FN_NAME, args=[_NS_VAR_NAME], keywords=[])
    def_name = ast.Call(func=_NEW_SYM_FN_NAME, args=[ast.Str(defsym.name)], keywords=[])
    safe_name = munge(defsym.name)

    assert node.meta is not None, "Meta should always be attached to Def nodes"
    def_meta = node.meta.form
    assert isinstance(def_meta, lmap.Map), "Meta should always be a map"

    # If the Var is marked as dynamic, we need to generate a keyword argument
    # for the generated Python code to set the Var as dynamic
    is_dynamic = def_meta.entry(SYM_DYNAMIC_META_KEY, False)
    dynamic_kwarg = (
        [ast.keyword(arg="dynamic", value=ast.NameConstant(is_dynamic))]
        if is_dynamic
        else []
    )

    # Warn if this symbol is potentially being redefined
    if __should_warn_on_redef(ctx, defsym, safe_name, def_meta):
        logger.warning(
            f"redefining local Python name '{safe_name}' in module '{ctx.current_ns.module.__name__}'"
        )

    meta_ast = gen_py_ast(ctx, node.meta)

    # For defn style def generation, we specifically need to generate the
    # global declaration prior to emitting the Python `def` otherwise the
    # Python compiler will throw an exception during compilation
    # complaining that we assign the value prior to global declaration.
    if is_defn:
        def_dependencies = list(
            chain(
                [] if node.top_level else [ast.Global(names=[safe_name])],
                def_ast.dependencies,
                [] if meta_ast is None else meta_ast.dependencies,
            )
        )
    else:
        def_dependencies = list(
            chain(
                def_ast.dependencies,
                [] if node.top_level else [ast.Global(names=[safe_name])],
                [
                    ast.Assign(
                        targets=[ast.Name(id=safe_name, ctx=ast.Store())],
                        value=def_ast.node,
                    )
                ],
                [] if meta_ast is None else meta_ast.dependencies,
            )
        )

    return GeneratedPyAST(
        node=ast.Call(
            func=_INTERN_VAR_FN_NAME,
            args=[ns_name, def_name, ast.Name(id=safe_name, ctx=ast.Load())],
            keywords=list(
                chain(
                    dynamic_kwarg,
                    []
                    if meta_ast is None
                    else [ast.keyword(arg="meta", value=meta_ast.node)],
                )
            ),
        ),
        dependencies=def_dependencies,
    )
def _def_to_py_ast(  # pylint: disable=too-many-branches
    ctx: GeneratorContext, node: Def
) -> GeneratedPyAST:
    """Return a Python AST Node for a `def` expression."""
    assert node.op == NodeOp.DEF

    defsym = node.name
    is_defn = False

    if node.init is not None:
        # Since Python function definitions always take the form `def name(...):`,
        # it is redundant to assign them to the their final name after they have
        # been defined under a private alias. This codepath generates `defn`
        # declarations by directly generating the Python `def` with the correct
        # function name and short-circuiting the default double-declaration.
        if node.init.op == NodeOp.FN:
            assert isinstance(node.init, Fn)
            def_ast = _fn_to_py_ast(ctx, node.init, def_name=defsym.name)
            is_defn = True
        elif (
            node.init.op == NodeOp.WITH_META
            and isinstance(node.init, WithMeta)
            and node.init.expr.op == NodeOp.FN
        ):
            assert isinstance(node.init, WithMeta)
            def_ast = _with_meta_to_py_ast(ctx, node.init, def_name=defsym.name)
            is_defn = True
        else:
            def_ast = gen_py_ast(ctx, node.init)
    else:
        def_ast = GeneratedPyAST(node=ast.NameConstant(None))

    ns_name = ast.Call(func=_NEW_SYM_FN_NAME, args=[_NS_VAR_NAME], keywords=[])
    def_name = ast.Call(func=_NEW_SYM_FN_NAME, args=[ast.Str(defsym.name)], keywords=[])
    safe_name = munge(defsym.name)

    assert node.meta is not None, "Meta should always be attached to Def nodes"
    def_meta = node.meta.form
    assert isinstance(def_meta, lmap.Map), "Meta should always be a map"

    # If the Var is marked as dynamic, we need to generate a keyword argument
    # for the generated Python code to set the Var as dynamic
    is_dynamic = def_meta.entry(SYM_DYNAMIC_META_KEY, False)
    dynamic_kwarg = (
        [ast.keyword(arg="dynamic", value=ast.NameConstant(is_dynamic))]
        if is_dynamic
        else []
    )

    # Warn if this symbol is potentially being redefined
    if __should_warn_on_redef(ctx, defsym, safe_name, def_meta):
        logger.warning(
            f"redefining local Python name '{safe_name}' in module '{ctx.current_ns.module.__name__}'"
        )

    meta_ast = gen_py_ast(ctx, node.meta)

    # For defn style def generation, we specifically need to generate the
    # global declaration prior to emitting the Python `def` otherwise the
    # Python compiler will throw an exception during compilation
    # complaining that we assign the value prior to global declaration.
    if is_defn:
        def_dependencies = list(
            chain(
                [] if node.top_level else [ast.Global(names=[safe_name])],
                def_ast.dependencies,
                [] if meta_ast is None else meta_ast.dependencies,
            )
        )
    else:
        def_dependencies = list(
            chain(
                def_ast.dependencies,
                [] if node.top_level else [ast.Global(names=[safe_name])],
                [
                    ast.Assign(
                        targets=[ast.Name(id=safe_name, ctx=ast.Store())],
                        value=def_ast.node,
                    )
                ],
                [] if meta_ast is None else meta_ast.dependencies,
            )
        )

    return GeneratedPyAST(
        node=ast.Call(
            func=_INTERN_VAR_FN_NAME,
            args=[ns_name, def_name, ast.Name(id=safe_name, ctx=ast.Load())],
            keywords=list(
                chain(
                    dynamic_kwarg,
                    []
                    if meta_ast is None
                    else [ast.keyword(arg="meta", value=meta_ast.node)],
                )
            ),
        ),
        dependencies=def_dependencies,
    )
Return a Python AST Node for a `def` expression.