How to explain the behaviour of TreeForm?

I would call this a bug. Please report it to Wolfram.

IGraph/M's IGExpressionTree can handle this:

IGExpressionTree[Hold[g[a]]]

enter image description here

The documentation has examples that show how to get a more TreeForm-like output. Do keep in mind that the purpose of IGExpressionTree is not a perfect visualization of expressions, but simply converting fairly benign expressions to Graph (or rather to generate tree graphs which are easier to assemble as expressions). It is definitely possible to construct examples where it leaks evaluation, and I am not going to fix these if the fix would affect performance.


If the symbols with OwnValues are all on the context path, then one idea is to write the expression to a string or file, where contexts are not included, and then to block the context and context path so that all symbols are loaded in a temporary context. The symbols in the temporary context will not have OwnValues, so you can use TreeForm without worrying about leaky symbols.

There is one issue to deal with. The new symbols should only be temporary, which means the output should not contain any references to these symbols. The way to fix this is to convert the TreeForm output to boxes, and then remove the symbols before returning the TreeForm box output. I will use Export[..., "Text"] to convert the expression to a string, there may be better options:

sealedTreeForm[expr_] := With[{es = ExportString[expr, "Text"]},
    Block[{$Context="FOO`", $ContextPath={"System`", "FOO`"}},
        Internal`WithLocalSettings[
            Null,
            Quiet[
                RawBoxes @ ToBoxes @ TreeForm @ ImportString[es],
                General::shdw
            ],
            Remove["FOO`*"]
        ]
    ]
]

Example:

g=1; a=1; sealedTreeForm[Hold[g[a]]]

enter image description here