v2 / vlib / v / parser / module.v
367 lines · 352 sloc · 9.94 KB · 001998244729b8815046e3dbca44f572eb77ccce
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module parser
5
6import v.ast
7import v.util
8import v.token
9
10// return true if file being parsed imports `mod`
11fn (p &Parser) known_import(mod string) bool {
12 return mod in p.imports
13}
14
15fn (p &Parser) import_alias_for_mod(mod string) ?string {
16 for alias, imported_mod in p.imports {
17 if imported_mod == mod {
18 return alias
19 }
20 }
21 return none
22}
23
24fn (p &Parser) prepend_mod(name string) string {
25 // println('prepend_mod() name=${name} p.mod=${p.mod} expr_mod=${p.expr_mod}')
26 if p.expr_mod != '' {
27 return p.expr_mod + '.' + name
28 }
29 if p.builtin_mod {
30 return name
31 }
32 return p.mod + '.' + name
33}
34
35fn (p &Parser) is_used_import(alias string) bool {
36 return alias in p.used_imports
37}
38
39fn (mut p Parser) register_used_import(alias string) {
40 if !p.is_used_import(alias) {
41 p.used_imports << alias
42 }
43}
44
45fn (mut p Parser) register_used_import_for_symbol_name(sym_name string) {
46 mod_name := sym_name.all_before_last('.')
47 short_import_name := mod_name.all_after_last('.')
48 short_symbol_name := sym_name.all_after_last('.')
49 if p.is_imported_symbol(short_symbol_name) {
50 p.imported_symbols_used[short_symbol_name] = true
51 }
52 if alias := p.import_alias_for_mod(mod_name) {
53 p.register_used_import(alias)
54 return
55 }
56 p.register_used_import(short_import_name)
57}
58
59fn (mut p Parser) register_auto_import(alias string) {
60 if p.mod == alias {
61 return
62 }
63 if alias !in p.imports {
64 p.imports[alias] = alias
65 p.table.imports << alias
66 node := ast.Import{
67 source_name: alias
68 pos: p.tok.pos()
69 mod: alias
70 alias: alias
71 }
72 p.ast_imports << node
73 }
74 if alias !in p.auto_imports {
75 p.auto_imports << alias
76 }
77 // do not call `register_used_import()` here as it may not used by the code.
78 // for example, when using `chan`, but we has no `sync.xx()` call in the code.
79}
80
81fn (mut p Parser) register_implied_import(alias string) {
82 if alias !in p.implied_imports {
83 p.implied_imports << alias
84 }
85}
86
87fn (mut p Parser) check_unused_imports() {
88 if p.pref.is_repl || p.pref.is_fmt {
89 // The REPL should be much more liberal, and should not warn about
90 // unused imports, because they probably will be in the next few lines...
91 // vfmt doesn't care about unused imports either
92 return
93 }
94 for import_m in p.ast_imports {
95 alias := import_m.alias
96 mod := import_m.mod
97 if !(alias.len == 1 && alias[0] == `_`) && !p.is_used_import(alias)
98 && alias !in p.auto_imports {
99 mod_alias := if alias == mod { alias } else { '${alias} (${mod})' }
100 p.warn_with_pos("module '${mod_alias}' is imported but never used. Use `import ${mod_alias} as _`, to silence this warning, or just remove the unused import line",
101 import_m.mod_pos)
102 }
103 }
104}
105
106fn (mut p Parser) module_decl() ast.Module {
107 mut module_attrs := []ast.Attr{}
108 mut attrs_pos := p.tok.pos()
109 for p.tok.kind == .lsbr || p.tok.kind == .at {
110 p.attributes()
111 }
112 module_attrs << p.attrs
113 mut name := 'main'
114 mut module_pos := token.Pos{}
115 mut name_pos := token.Pos{}
116 mut mod_node := ast.Module{}
117 is_skipped := p.tok.kind != .key_module
118 if is_skipped {
119 // the attributes were for something else != module, like a struct/fn/type etc.
120 module_attrs = []
121 } else {
122 p.attrs = []
123 module_pos = p.tok.pos()
124 p.next()
125 name_pos = p.tok.pos()
126 name = p.check_name()
127 mod_node = ast.Module{
128 pos: module_pos
129 }
130 if module_pos.line_nr != name_pos.line_nr {
131 p.error_with_pos('`module` and `${name}` must be at same line', name_pos)
132 return mod_node
133 }
134 // Note: this shouldn't be reassigned into name_pos
135 // as it creates a wrong position when extended
136 // to module_pos
137 n_pos := p.tok.pos()
138 if module_pos.line_nr == n_pos.line_nr && p.tok.kind !in [.comment, .eof, .semicolon] {
139 if p.tok.kind == .name {
140 p.unexpected_with_pos(n_pos,
141 prepend_msg: '`module ${name}`, you can only declare one module,'
142 got: '`${p.tok.lit}`'
143 )
144 return mod_node
145 } else {
146 p.unexpected_with_pos(n_pos,
147 prepend_msg: '`module ${name}`,'
148 got: '`${p.tok.kind}` after module name'
149 )
150 return mod_node
151 }
152 }
153 module_pos = attrs_pos.extend(name_pos)
154 }
155 full_name := util.qualify_module(p.pref, name, p.file_path)
156 p.mod = full_name
157 p.builtin_mod = p.mod == 'builtin'
158 mod_node = ast.Module{
159 name: full_name
160 short_name: name
161 attrs: module_attrs
162 is_skipped: is_skipped
163 pos: module_pos
164 name_pos: name_pos
165 }
166 if p.tok.kind == .semicolon {
167 p.check(.semicolon)
168 }
169 if !is_skipped {
170 p.table.module_attrs[p.mod] = module_attrs
171 for ma in module_attrs {
172 match ma.name {
173 'deprecated', 'deprecated_after' {
174 p.table.module_deprecated[p.mod] = true
175 }
176 'manualfree' {
177 p.is_manualfree = true
178 }
179 'generated' {
180 p.is_generated = true
181 }
182 'has_globals' {
183 p.has_globals = true
184 }
185 'strict_map_index' {}
186 'translated' {
187 p.is_translated = true
188 }
189 'wasm_import_namespace' {
190 if !p.pref.is_fmt && p.pref.backend != .wasm {
191 p.note_with_pos('@[wasm_import_namespace] is only supported by the wasm backend',
192 ma.pos)
193 }
194 }
195 else {
196 p.error_with_pos('unknown module attribute `[${ma.name}]`', ma.pos)
197 return mod_node
198 }
199 }
200 }
201 }
202 return mod_node
203}
204
205fn (mut p Parser) import_stmt() ast.Import {
206 import_pos := p.tok.pos()
207 p.check(.key_import)
208 mut pos := p.tok.pos()
209 mut import_node := ast.Import{
210 pos: import_pos.extend(pos)
211 }
212 if p.tok.kind == .lpar {
213 p.error_with_pos('`import()` has been deprecated, use `import x` instead', pos)
214 return import_node
215 }
216 mut source_name := p.check_name()
217 if source_name == '' {
218 p.error_with_pos('import name can not be empty', pos)
219 return import_node
220 }
221 mut mod_name_arr := []string{}
222 mod_name_arr << source_name
223 if import_pos.line_nr != pos.line_nr {
224 p.error_with_pos('`import` statements must be a single line', pos)
225 return import_node
226 }
227 mut mod_alias := mod_name_arr[0]
228 import_node = ast.Import{
229 source_name: source_name
230 pos: import_pos.extend(pos)
231 mod_pos: pos
232 alias_pos: pos
233 }
234 for p.tok.kind == .dot {
235 p.next()
236 submod_pos := p.tok.pos()
237 if p.tok.kind != .name {
238 p.error_with_pos('module syntax error, please use `x.y.z`', submod_pos)
239 return import_node
240 }
241 if import_pos.line_nr != submod_pos.line_nr {
242 p.error_with_pos('`import` and `submodule` must be at same line', submod_pos)
243 return import_node
244 }
245 submod_name := p.check_name()
246 mod_name_arr << submod_name
247 mod_alias = submod_name
248 pos = pos.extend(submod_pos)
249 source_name = mod_name_arr.join('.')
250 import_node = ast.Import{
251 source_name: source_name
252 pos: import_pos.extend(pos)
253 mod_pos: pos
254 alias_pos: submod_pos
255 mod: util.qualify_import(p.pref, source_name, p.file_path)
256 alias: mod_alias
257 }
258 }
259 if mod_name_arr.len == 1 {
260 import_node = ast.Import{
261 source_name: source_name
262 pos: import_node.pos
263 mod_pos: import_node.mod_pos
264 alias_pos: import_node.alias_pos
265 mod: util.qualify_import(p.pref, mod_name_arr[0], p.file_path)
266 alias: mod_alias
267 }
268 }
269 mod_name := import_node.mod
270 if p.tok.kind == .key_as {
271 p.next()
272 alias_pos := p.tok.pos()
273 mod_alias = p.check_name()
274 if mod_alias == mod_name_arr.last() {
275 p.error_with_pos('import alias `${mod_name} as ${mod_alias}` is redundant',
276 p.prev_tok.pos())
277 return import_node
278 }
279 import_node = ast.Import{
280 source_name: source_name
281 pos: import_node.pos.extend(alias_pos)
282 mod_pos: import_node.mod_pos
283 alias_pos: alias_pos
284 mod: import_node.mod
285 alias: mod_alias
286 }
287 }
288 if p.tok.kind == .lcbr { // import module { fn1, Type2 } syntax
289 mut initial_syms_pos := p.tok.pos()
290 p.import_syms(mut import_node)
291 initial_syms_pos = initial_syms_pos.extend(p.tok.pos())
292 import_node = ast.Import{
293 ...import_node
294 source_name: source_name
295 syms_pos: initial_syms_pos
296 pos: import_node.pos.extend(initial_syms_pos)
297 }
298 }
299 pos_t := p.tok.pos()
300 if import_pos.line_nr == pos_t.line_nr {
301 if p.tok.kind !in [.lcbr, .rcbr, .eof, .comment, .semicolon, .key_import] {
302 p.error_with_pos('cannot import multiple modules at a time', pos_t)
303 return import_node
304 }
305 }
306 import_node.comments = p.eat_comments(same_line: true)
307 import_node.next_comments = p.eat_comments(follow_up: true)
308 p.imports[mod_alias] = mod_name
309 if p.tok.kind == .semicolon {
310 p.check(.semicolon)
311 }
312 // if mod_name !in p.table.imports {
313 p.table.imports << mod_name
314 p.ast_imports << import_node
315 // }
316 return import_node
317}
318
319// import_syms parses the inner part of `import module { submod1, submod2 }`
320fn (mut p Parser) import_syms(mut parent ast.Import) {
321 p.next()
322 pos_t := p.tok.pos()
323 if p.tok.kind == .rcbr { // closed too early
324 p.error_with_pos('empty `${parent.mod}` import set, remove `{}`', pos_t)
325 return
326 }
327 if p.tok.kind != .name { // not a valid inner name
328 p.error_with_pos('import syntax error, please specify a valid fn or type name', pos_t)
329 return
330 }
331 for p.tok.kind == .name {
332 pos := p.tok.pos()
333 alias := p.check_name()
334 if p.is_imported_symbol(alias) && !(p.pref.is_fmt && p.inside_ct_if_expr) {
335 p.error_with_pos('cannot register symbol `${alias}`, it was already imported', pos)
336 return
337 }
338 p.imported_symbols[alias] = parent.mod + '.' + alias
339 p.rebuild_imported_symbols_matcher(alias)
340 // so we can work with this in fmt+checker
341 parent.syms << ast.ImportSymbol{
342 pos: pos
343 name: alias
344 }
345 if p.tok.kind == .comma { // go again if more than one
346 p.next()
347 continue
348 }
349 if p.tok.kind == .rcbr { // finish if closing `}` is seen
350 break
351 }
352 }
353 if p.tok.kind != .rcbr {
354 p.error_with_pos('import syntax error, no closing `}`', p.tok.pos())
355 return
356 }
357 p.next()
358}
359
360fn (mut p Parser) rebuild_imported_symbols_matcher(_ string) {
361 p.imported_symbols_trie = token.new_keywords_matcher_from_array_trie(p.imported_symbols.keys())
362}
363
364@[inline]
365fn (mut p Parser) is_imported_symbol(name string) bool {
366 return p.imported_symbols_trie.matches(name)
367}
368