Summary
The @inline <name> never pragma parses and is stored, but it has no effect on the optimizer. A binding annotated never is still inlined whenever the ordinary heuristics would inline it, so the annotation is a no-op. @inline always works as expected; only never is broken.
This surfaced while documenting the inliner for #44.
Root cause
The annotation reaches the optimizer as the binding expression's annotation, but the only place that reads it, isInlinableExpr in Backend.IR.Optimizer, collapses Just Never to the same result as Nothing:
hasInlineAnnotation = getAnn >>> \case
Just Always -> True
Just Never -> False
Nothing -> False
Both inlining sites then decide with isInlinableExpr expr || <used once>:
optimizeModule inlines a top-level binding if isInlinableExpr expr || isUsedOnce qname.
inlineLocalBindings inlines a let binding if isInlinableExpr inlinee || countFreeRef name body == 1.
So a never-annotated binding that is a reference, a small literal, or used exactly once is still inlined through the second disjunct. Never never blocks anything Nothing would not also allow.
Expected
@inline <name> never should prevent that binding from being inlined, overriding both the isInlinableExpr heuristic and the single-use rule.
Suggested fix
Treat Just Never as a veto at both inlining sites (for example a shared getAnn expr == Just Never guard that short-circuits the whole decision), and update Note [Inline annotations and inlining heuristics], which currently documents the broken behavior.
A regression test should assert that a never-annotated, single-use binding is left in place by the optimizer.
Summary
The
@inline <name> neverpragma parses and is stored, but it has no effect on the optimizer. A binding annotatedneveris still inlined whenever the ordinary heuristics would inline it, so the annotation is a no-op.@inline alwaysworks as expected; onlyneveris broken.This surfaced while documenting the inliner for #44.
Root cause
The annotation reaches the optimizer as the binding expression's annotation, but the only place that reads it,
isInlinableExprinBackend.IR.Optimizer, collapsesJust Neverto the same result asNothing:Both inlining sites then decide with
isInlinableExpr expr || <used once>:optimizeModuleinlines a top-level binding ifisInlinableExpr expr || isUsedOnce qname.inlineLocalBindingsinlines aletbinding ifisInlinableExpr inlinee || countFreeRef name body == 1.So a
never-annotated binding that is a reference, a small literal, or used exactly once is still inlined through the second disjunct.Nevernever blocks anythingNothingwould not also allow.Expected
@inline <name> nevershould prevent that binding from being inlined, overriding both theisInlinableExprheuristic and the single-use rule.Suggested fix
Treat
Just Neveras a veto at both inlining sites (for example a sharedgetAnn expr == Just Neverguard that short-circuits the whole decision), and updateNote [Inline annotations and inlining heuristics], which currently documents the broken behavior.A regression test should assert that a
never-annotated, single-use binding is left in place by the optimizer.