feat(typecheck): module-qualified type/effect path resolution (ADR-014, Refs #228)#447
Merged
Merged
Conversation
Symmetric to PR #178 (`Resolve.lower_qualified_value_paths`) at the value-path layer: when a folded `Mod::T` reaches `lower_type_expr` / `lower_effect_expr`, the leading `Mod::` qualifier is stripped iff `Mod` was introduced by a `use Mod;` decl in the program, raising a clear `UnknownModule` error otherwise. Before: `pub fn f(x: NoSuchMod.Thing) -> ()` passed typecheck silently (folded `TyCon "NoSuchMod::Thing"` fell through the TyCon lookup as an abstract `TCon`). After: it fails with `Unknown module 'NoSuchMod' in qualified type/effect reference (ADR-014, #228). Add `use NoSuchMod;` to bring the module into scope, …`. The same path now governs effect position — replacing the pre-existing misleading `declare `effect NoSuchMod::IO;`` hint with the correct UnknownModule message. Surface: • `type_error`: new `UnknownModule of string` constructor + formatter (cites ADR-014/#228, suggests `use M;` or `use M::{Item};`). • new `Module_resolution_error` exception (paralleling `Effect_validation_error`); caught at `check_program` boundary. • `context`: new `module_quals : (string, unit) Hashtbl.t` populated from `prog.prog_imports` at `check_program` entry — `ImportSimple` only, matching the value-path semantics in `Resolve.import_qualifiers`. • new `strip_module_qualifier` helper called from `TyCon`/`TyApp` arms of `lower_type_expr` and the `resolve` helper in `lower_effect_expr`. No AST walk; the lowering chokepoints already visit every type/effect site. Tests (`test/test_qualified_paths.ml`, 6 cases, all green): • qualified type + `use` (both `.` and `::` separators) → passes • qualified type, no `use` → UnknownModule with correct attribution • qualified effect, no `use` → UnknownModule (not UnknownEffect) • bare TyCon unchanged (regression guard for lenient-unknown pre-existing behaviour) • qualified reserved effect (`Net`) + `use` → strips & resolves into the reserved-effect path (proves strip happens before the canonical-effect lookup) Out of scope for this PR (surfaced in the #228 design comment): • lowercase-module qualified refs (`json.Value`) still parse-error because `qualified_type_name` head requires `upper_ident` • `use A.B;` registers only `B` as the qualifier • `use A::{Item}` (multi-segment + brace-list) parse-errors • bare unknown TyCons silently pass typecheck (broader, pre-existing) Build: `dune build @test/runtest` → 352/352 OK. Refs #228 Refs hyperpolymath/standards#124
🔍 Hypatia Security ScanFindings: 82 issues detected
View findings[
{
"reason": "Action perpolymath/standards/.github/workflows/governance-reusable.yml@main\n needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action ons/checkout@v6\n needs attention",
"type": "unpinned_action",
"file": "publish-jsr.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action land/setup-deno@v2\n needs attention",
"type": "unpinned_action",
"file": "publish-jsr.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in affine-vscode-publish.yml",
"type": "unknown",
"file": "affine-vscode-publish.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in casket-pages.yml",
"type": "unknown",
"file": "casket-pages.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in casket-pages.yml",
"type": "unknown",
"file": "casket-pages.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in ci.yml",
"type": "unknown",
"file": "ci.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in ci.yml",
"type": "unknown",
"file": "ci.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in ci.yml",
"type": "unknown",
"file": "ci.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in ci.yml",
"type": "unknown",
"file": "ci.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Symmetric to #178 (
Resolve.lower_qualified_value_paths) at the value-path layer: when a foldedMod::Treacheslower_type_expr/lower_effect_expr, the leadingMod::qualifier is stripped iffModwas introduced byuse Mod;. Otherwise a clearUnknownModuleerror is raised — replacing the pre-existing silent typecheck-pass (type position) and misleadingdeclare \effect ...`` hint (effect position).Closes the resolution half of the #228 work whose parser half landed in #241 (ADR-014).
What changes
type_error:UnknownModule of stringconstructor + formatter (cites ADR-014/LANG: type/effect grammar has no module-qualified path —Pkg.Type/Pkg.Effectunrepresentable (estate-wide port blocker; ADR-014) #228, suggestsuse M;/use M::{Item};)Module_resolution_errorexception caught atcheck_programboundary (mirrorsEffect_validation_error)context.module_quals: populated atcheck_programentry fromprog.prog_imports(ImportSimpleonly — matches value-path semantics inResolve.import_qualifiers)strip_module_qualifierhelper applied inTyCon/TyApparms oflower_type_exprand theresolvehelper oflower_effect_expr. No AST walk — the lowering chokepoints already visit every type/effect site.Before / after
pub fn f(x: NoSuchMod.Thing) -> ()Unknown module 'NoSuchMod' in qualified type/effect reference (ADR-014, #228). Add \use NoSuchMod;` ...`pub fn f() -{NoSuchMod.IO}-> ()Unknown effect 'NoSuchMod::IO'. ... declare it with \effect NoSuchMod::IO;``UnknownModuletext as aboveuse Ajv; pub fn f(x: Ajv.Schema) -> ()pub fn f(x: Int) -> ()Tests
test/test_qualified_paths.ml— 6 alcotest cases, all green:use(both.and::) → passesuse→UnknownModuleuse→UnknownModule(notUnknownEffect)Net) +use→ strips & resolves into the reserved-effect pathFull suite:
dune build @test/runtest→ 352/352 OK.Out of scope (surfaced in #228 design comment)
These are real but separate; I'll file follow-ups if the seam-analyst note isn't already tracked elsewhere:
json.Value) still parse-error —qualified_type_namehead requiresupper_identwhile stdlib has lowercasemodule json;/option;/prelude;use A.B;registers onlyBas the qualifier (last segment)use A::{Item}(multi-segment + brace-list) parse-errorsPkg.Type/Pkg.Effectunrepresentable (estate-wide port blocker; ADR-014) #228; would change behaviour for many existing filesRefs
Refs #228 (this is the resolution slice; parser slice was #241)
Refs hyperpolymath/standards#124 (estate proof-debt / language-readiness umbrella)