Skip to content

Offer config blocks and deferred declarations to importers - #91

Merged
henrygarner merged 3 commits into
juxt:mainfrom
yenda:fix/config-deferred-imports
Aug 26, 2026
Merged

Offer config blocks and deferred declarations to importers#91
henrygarner merged 3 commits into
juxt:mainfrom
yenda:fix/config-deferred-imports

Conversation

@yenda

@yenda yenda commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #89.

Bug

The offered-names set that a qualified reference alias/Name is validated against (collect_referenced_trigger_names) held declared type names and referenced triggers only. Two reference forms drew false allium.reference.unknownName warnings (exit 1) although the aliased module visibly carries the construct:

1. alias/config.param — contradicting the language reference. The reference's "Config parameter references" section documents exactly this form:

A config parameter's default value can reference a parameter from an imported module's config block using a qualified name: required_copies: Integer = core/config.required_copies

and checker rule 46 requires it to resolve ("A qualified config reference in a default expression must resolve to a declared parameter in an imported module's config block"). On main, even that documented config-default position warns — and the message claims the module "does not define" a config block it plainly defines.

2. alias/DeferredName — a deferred declaration the module carries. Natural cross-module form: a module declaring deferred UnhandledException is exactly the module another spec wants to name in requires: mobile_auth/UnhandledException(request).

Repro (fails on main, passes here — transcribed into cross_module_lifecycle.rs): provider with a config block + deferred ExternalHelper; consumer referencing p/config.page_size (both in a config default and in a rule) and p/ExternalHelper → 3 false warnings on main, 0 here.

Fix

collect_referenced_trigger_names additionally offers:

  • config, when the module declares a config block;
  • each deferred declaration's root name (deferred Foo and deferred Foo.bar both offer Foo). A deferred declared against another module's alias (deferred other/Foo) names a construct that module owns and is deliberately not offered — extract_leading_ident returns None for qualified paths.

Not in scope (follow-up)

Field-level validation of config references (rules 46/47: does page_size exist in the imported block, do the types match). Today the QRef for alias/config.page_size carries only the name config, so a field typo produces the byte-identical diagnostic to a valid reference. This fix stops the false positive on the block reference; field resolution needs the qref collector to carry the member chain.

Tests

  • 3 unit tests on the offering set (config + deferred + dotted-deferred root offered; neither offered by a module without them; a foreign deferred other/Foreign offers neither the name nor the alias)
  • 3 integration tests (the ticket repro resolves and exits 0; p/NoSuchDeferred still warns; p/config against a module with no config block still warns)
  • Full workspace suite green: 655 tests, 0 failures.

Real-world impact

On a ~180-spec production corpus, the v3.5.0 → v3.5.3 upgrade produced 8 false unknownName warnings of these two shapes (5 config, 3 deferred), each now carrying a suppression directive plus an explanatory comment. With this fix those suppressions become unnecessary.


Note: this PR and the config/deferred one both append tests to cross_module_lifecycle.rs and the analysis.rs test module — whichever merges second needs a trivial union rebase; happy to rebase promptly.

yenda and others added 2 commits August 13, 2026 10:01
The offered-names set a qualified reference alias/Name is validated against
held declared types and referenced triggers only. A module's config block and
its deferred declarations were absent, so two documented reference forms drew
false allium.reference.unknownName warnings:

- alias/config.param — the language reference's 'Config parameter references'
  section documents exactly this form, and checker rule 46 requires it to
  resolve; even the documented config-default position warned.
- alias/DeferredName — a deferred declaration the module visibly carries.

The diagnostic also claimed the module 'does not define' a config block it
plainly defines.

collect_referenced_trigger_names now offers 'config' when the module declares
a config block, and each deferred declaration's root name (deferred Foo and
deferred Foo.bar both offer Foo). A deferred declared against another
module's alias (deferred other/Foo) stays a foreign reference, not a local
offering. Field-level validation of config references (rules 46/47: does
page_size exist, do the types match) remains open — this fix stops the false
positive on the block reference itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fered

deferred other/Foreign records that 'other' owns the construct; the declaring
module must offer neither the name nor the alias. The behaviour was already
implemented (extract_leading_ident returns None for qualified paths) but had
no test holding it in place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mports

# Conflicts:
#	crates/allium-parser/src/analysis.rs
#	crates/allium/tests/cross_module_lifecycle.rs
@henrygarner
henrygarner merged commit 0503f6a into juxt:main Aug 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants