Skip to content

check: a qualified related-surface link (related: alias/Surface(item)) warns unknownName because surfaces are never offered to importers #98

Description

@yenda

Environment

Summary

The language reference defines a surface's related: clause as "associated surfaces reachable from this one; the parenthesised expression evaluates to the entity instance that the target surface's context clause binds to". Nothing restricts the target to the same module, and qualified names are the documented way to reference another module's declarations. But a related: entry naming an imported module's surface warns allium.reference.unknownName ("which imported module 'x' does not define"), while the module visibly declares surface X { ... }. Since check exits 1 on warnings, a cross-module navigation link cannot be expressed without a suppression.

Reproduction

merge.allium:

-- allium: 3
-- merge.allium

entity User {
    name: String
}

surface MergeWizard {
    facing admin: User

    context source: User

    exposes:
        source.name
}

queue.allium:

-- allium: 3
-- queue.allium

use "./merge.allium" as merge

entity Refusal {
    sso_user: merge/User?
    label: String
}

surface RefusalQueue {
    facing admin: merge/User

    context refusal: Refusal

    exposes:
        refusal.label

    related:
        merge/MergeWizard(refusal.sso_user) when refusal.sso_user != null
}

allium check . (exit 1), warnings only:

warning  allium.reference.unknownName  ./queue.allium:20  Reference 'merge/MergeWizard' names 'MergeWizard', which imported module 'merge' does not define.

Expected: no warning (merge defines surface MergeWizard, whose context type User matches the expression's type).

Contrast: the same link written unqualified (MergeWizard(refusal.sso_user)) is recognised as a related-surface reference and drawn as allium.surface.relatedUndefined ("references unknown related surface") — so the checker knows the clause's semantics locally; it is only the cross-module offering that is missing.

Root cause (located)

crates/allium-parser/src/analysis.rs, collect_referenced_trigger_names (v3.6.1 line 4740) builds the set a module offers to importers from collect_trigger_outputs, collect_declared_names, deferred roots, config, and when:-referenced triggers. collect_declared_names (line 4672) matches Entity | ExternalEntity | Value | Enum | Actor | Contract blocks and variants; BlockKind::Surface is not in the list, so no surface name is ever offered, and the related: entry's qualified reference resolves against nothing.

Suggested fix

Offer surface names to importers — either by adding BlockKind::Surface to collect_declared_names (they are declared, module-level, PascalCase names exactly like contracts, which are already offered), or, if surfaces should stay out of the general offering, by resolving related: entries specifically against the imported module's surfaces and, ideally, checking the expression's type against that surface's context type (rule the reference already states for the local case). The first is a one-line change; a test pinning related: alias/Surface(x) as clean and related: alias/NoSuchSurface(x) as unknownName would fix the contract either way.

Real-world cost

A superadmin worklist surface in a ~190-spec production corpus offers each open row the account-merge wizard another module owns, pre-selected on the row's pair; that hand-off is the surface's load-bearing guarantee and related: is the construct for it. Under 3.6.1 it carries a line-scoped allium-ignore allium.reference.unknownName explaining that the checker does not export surfaces, which is the checker documented in the spec.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions