-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_qualified_paths.ml
More file actions
119 lines (107 loc) · 5.18 KB
/
Copy pathtest_qualified_paths.ml
File metadata and controls
119 lines (107 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
(* SPDX-License-Identifier: MPL-2.0 *)
(* Copyright (c) 2026 Jonathan D.A. Jewell <jonathan.jewell@open.ac.uk> *)
(** ADR-014 / #228 — module-qualified type/effect path resolution.
The parser (after #241) accepts `Pkg.T` / `Pkg::T` (mixed seps) at
type and effect positions, folding the segments into a canonical
`::`-joined name. This test module exercises the *resolution*
counterpart shipped here: the typechecker strips the leading `Mod::`
qualifier when `Mod` was introduced by `use Mod;`, and raises a
clear [UnknownModule] error when it was not. Symmetric to
[Resolve.lower_qualified_value_paths] (#178, value position).
*)
open Affinescript
(** parse -> resolve -> typecheck an inline source string. *)
let frontend (src : string) : (unit, string) result =
let open Result in
let ( let* ) = bind in
let* prog =
try Ok (Parse_driver.parse_string ~file:"<test_qualified_paths>" src)
with
| Parse_driver.Parse_error (m, sp) ->
Error (Printf.sprintf "Parse error at %s: %s" (Span.show sp) m)
| e -> Error (Printf.sprintf "Unexpected: %s" (Printexc.to_string e))
in
let loader = Module_loader.create (Module_loader.default_config ()) in
let* resolve_ctx =
match Resolve.resolve_program_with_loader prog loader with
| Ok (rc, _) -> Ok rc
| Error (e, _) -> Error ("Resolution error: " ^ Resolve.show_resolve_error e)
in
match Typecheck.check_program resolve_ctx.symbols prog with
| Ok _ -> Ok ()
| Error e -> Error ("Type error: " ^ Typecheck.format_type_error e)
let contains ~needle s =
let nl = String.length needle and sl = String.length s in
let rec go i = i + nl <= sl && (String.sub s i nl = needle || go (i + 1)) in
nl = 0 || go 0
(* `use Mod;` + qualified type ref → strips back to bare name, passes
typecheck. `Ajv.Schema` lowers to `Schema` (current TyCon leniency
keeps the unknown name as an abstract `TCon` — that is the
pre-existing behaviour we deliberately do not change here). *)
let qualified_type_with_use_passes () =
let src = "use Ajv;\npub fn f(x: Ajv.Schema) -> () { () }\n" in
match frontend src with
| Ok () -> ()
| Error m -> Alcotest.failf "expected Ok, got: %s" m
(* `::` separator works the same as `.` (parser folds both). *)
let qualified_type_with_double_colon_passes () =
let src = "use Ajv;\npub fn f(x: Ajv::Schema) -> () { () }\n" in
match frontend src with
| Ok () -> ()
| Error m -> Alcotest.failf "expected Ok, got: %s" m
(* No `use`, qualified type ref → UnknownModule error mentioning the
exact qualifier and ADR-014 / #228 attribution. *)
let qualified_type_unknown_module_rejected () =
let src = "pub fn f(x: NoSuchMod.Thing) -> () { () }\n" in
match frontend src with
| Ok () -> Alcotest.fail "expected UnknownModule error, got Ok"
| Error m ->
Alcotest.(check bool) "names the missing module" true
(contains ~needle:"NoSuchMod" m);
Alcotest.(check bool) "cites ADR-014 / #228" true
(contains ~needle:"#228" m);
Alcotest.(check bool) "suggests use" true
(contains ~needle:"use NoSuchMod" m)
(* No `use`, qualified effect ref → same UnknownModule path (not the
permissive `Unknown effect` message). Pre-this-change the misleading
`declare \`effect NoSuchMod::IO;\`` hint was emitted. *)
let qualified_effect_unknown_module_rejected () =
let src = "pub fn f() -{NoSuchMod.IO}-> () { () }\n" in
match frontend src with
| Ok () -> Alcotest.fail "expected UnknownModule error, got Ok"
| Error m ->
Alcotest.(check bool) "names the missing module" true
(contains ~needle:"NoSuchMod" m);
Alcotest.(check bool) "is the UnknownModule error not UnknownEffect" true
(contains ~needle:"#228" m)
(* Bare (unqualified) type refs are unaffected — the strip helper is a
no-op when no `::` is present. Regression guard: this change must
not perturb existing single-name lookup behaviour. *)
let bare_typecon_unaffected () =
let src = "pub fn f(x: Int) -> Int { x }\n\
pub fn g(y: SomeAbstract) -> () { () }\n" in
match frontend src with
| Ok () -> ()
| Error m -> Alcotest.failf "expected permissive Ok on bare names, got: %s" m
(* Qualified ref with `use` but to a name that *is* a v1 effect after
stripping (`Net` is reserved) resolves into the reserved-effect path,
confirming the strip happens *before* the canonical-name lookup. *)
let qualified_reserved_effect_with_use_passes () =
let src = "use Network;\npub fn f() -{Network.Net}-> () { () }\n" in
match frontend src with
| Ok () -> ()
| Error m -> Alcotest.failf "expected Ok, got: %s" m
let tests = [
Alcotest.test_case "qualified type + use → passes" `Quick
qualified_type_with_use_passes;
Alcotest.test_case "qualified type + use (`::`) → passes" `Quick
qualified_type_with_double_colon_passes;
Alcotest.test_case "qualified type, no use → UnknownModule" `Quick
qualified_type_unknown_module_rejected;
Alcotest.test_case "qualified effect, no use → UnknownModule" `Quick
qualified_effect_unknown_module_rejected;
Alcotest.test_case "bare TyCon unaffected (regression)" `Quick
bare_typecon_unaffected;
Alcotest.test_case "qualified reserved effect + use → passes" `Quick
qualified_reserved_effect_with_use_passes;
]