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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! File-level `@[was("old::module::path")]` → [`HirFile.module`] for the
//! native surface (issue #1286, `docs/decision-log.md` "Native module
//! identity" 2026-07-22 + the 2026-07-23 `@[was]` follow-up ruling).
//!
//! Native module identity is filesystem-derived — a `.brink` file's module is
//! `native_module_path(root-relative path)` (`brink-db::modules`), folded into
//! every definition's `DefinitionId`. Moving the file (or relocating the
//! `brink.toml` root) changes that path, hence every id, hence breaks saves
//! keyed on the old ids. `@[was("old::path")]` is the migration record: it
//! names the module's *previous* path so the analyzer can emit an
//! `AliasEntry { old, new }` (`brink-analyzer::manifest`) mapping each stale
//! `DefinitionId` to its current one.
//!
//! This is the **same** module-rename feature ink already has (`#@was`,
//! `docs/modules-spec.md` §5); only the spelling differs — native names a
//! full `::`-separated module path, where ink names a bare module name.
//! The read path (`brink-db::queries::module_map_query`) and the
//! alias-table codegen (`brink-analyzer::manifest::insert_symbol`) are
//! already wired for both; the only piece this slice adds is parsing the
//! authored record into `HirFile.module.was` so it stops being silently
//! dropped.
//!
//! The path travels in **either** of two spellings and [`was_old_path`]
//! accepts both: the original quoted string (`@[was("old::path")]`) and the
//! unquoted `::`-path form `brink-syntax-native`'s annotation-arg grammar
//! gained in issue #1349 (`@[was(story::old::path)]`, `AnnotationArg::path`).
//! Wiring the unquoted shape in (issue #1355) is what makes the #1349
//! grammar addition actually usable — until then it parsed cleanly but still
//! diagnosed `E132` here.
//!
//! The produced [`ModuleDecl`] carries an **empty `name`** deliberately: a
//! native file's current module identity is a project-layer, path-derived fact
//! (`module_map_query` stamps it from the file's location and overrides any
//! name here), not something a single-file lowering can know. This node exists
//! only to carry the authored `was` rename record; its `name` is never read for
//! a native file (`module_map_query` reads only `.was`).
use SyntaxKind as N;
use ;
use TextRange;
use crateFileId;
use crate::;
use SyntaxNode;
use unescape_string_token;
/// The annotation-line name (`@[was(…)]`) that declares a native module's
/// rename. A bare identifier in the annotation grammar, not a lexer keyword —
/// so no keyword-list bookkeeping is involved (contrast `#@was`, an ink
/// directive tag).
const WAS: &str = "was";
/// Scan a native file's top level for a `@[was(…)]` annotation — either the
/// quoted `@[was("old::path")]` or unquoted `@[was(old::path)]` spelling —
/// and, if one is present, produce the [`ModuleDecl`] carrying its rename
/// record.
///
/// First-one-wins if a file (mistakenly) carries several — the same
/// "first declaration wins" discipline `brink-db::modules::resolve_modules`
/// already applies to a multi-file module's aggregated `was`. A `@[was]`
/// with no recognizable old path (empty, or an argument that is neither a
/// string literal nor a `::`-path) is a malformed migration directive: it
/// is **not** silently dropped (`CLAUDE.md` "Flag silent data drops") but
/// diagnosed `E132` and skipped.
pub
/// Extract the old module path from a `@[was(…)]` line: its first argument
/// must be either a quoted string literal (`@[was("old::path")]`) or the
/// unquoted `::`-path form (`@[was(old::path)]`, issue #1349). Returns `None`
/// for a missing or otherwise-shaped argument, which the caller diagnoses
/// `E132`.
/// Unescape a `STRING_LIT` node's contents (the quoted `@[was("…")]` form).
/// Join an unquoted `::`-path arg's segments (the `@[was(old::path)]` form,
/// issue #1349's `AnnotationArg::path`) back into the same `"::"`-separated
/// spelling the quoted form produces, so both spellings feed
/// [`lower_file_module`] identically. `None` if the arg isn't this shape at
/// all (e.g. a bare-ident arg with no `::`, or a nested-args clause).