Fork of daniellemaywood.uk/gleam — Wasm codegen work
12 kB
370 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 The Gleam contributors
3
4use ecow::EcoString;
5
6use crate::{
7 ast::{Publicity, SrcSpan, UnqualifiedImport, UntypedImport},
8 build::Origin,
9 reference::{EntityKind, ReferenceKind},
10 type_::{
11 Environment, Error, ModuleInterface, Problems, ValueConstructorVariant, Warning,
12 error::InvalidImportKind,
13 },
14};
15
16use super::Imported;
17
18#[derive(Debug)]
19pub struct Importer<'context, 'problems> {
20 origin: Origin,
21 environment: Environment<'context>,
22 problems: &'problems mut Problems,
23}
24
25impl<'context, 'problems> Importer<'context, 'problems> {
26 pub fn new(
27 origin: Origin,
28 environment: Environment<'context>,
29 problems: &'problems mut Problems,
30 ) -> Self {
31 Self {
32 origin,
33 environment,
34 problems,
35 }
36 }
37
38 pub fn run<'code>(
39 origin: Origin,
40 env: Environment<'context>,
41 imports: &'code [UntypedImport],
42 problems: &'problems mut Problems,
43 ) -> Environment<'context> {
44 let mut importer = Self::new(origin, env, problems);
45 for import in imports {
46 importer.register_import(import)
47 }
48 importer.environment
49 }
50
51 fn register_import(&mut self, import: &UntypedImport) {
52 let location = import.location;
53 let name = import.module.clone();
54
55 // Find imported module
56 let Some(module_info) = self.environment.importable_modules.get(&name) else {
57 self.problems.error(Error::UnknownModule {
58 location,
59 name: name.clone(),
60 suggestions: self.environment.suggest_modules(&name, Imported::Module),
61 });
62 return;
63 };
64
65 if let Err(e) = self.check_for_invalid_imports(module_info, location) {
66 self.problems.error(e);
67 }
68
69 if let Err(e) = self.register_module(import, module_info) {
70 self.problems.error(e);
71 return;
72 }
73
74 // Insert unqualified imports into scope
75 let module_name = &module_info.name;
76 for type_ in &import.unqualified_types {
77 self.register_unqualified_type(type_, module_name.clone(), module_info);
78 }
79 for value in &import.unqualified_values {
80 self.register_unqualified_value(value, module_name.clone(), module_info);
81 }
82 }
83
84 fn register_unqualified_type(
85 &mut self,
86 import: &UnqualifiedImport,
87 module_name: EcoString,
88 module: &ModuleInterface,
89 ) {
90 let imported_name = import.as_name.as_ref().unwrap_or(&import.name);
91
92 // Register the unqualified import if it is a type constructor
93 let Some(type_info) = module.get_importable_type(&import.name) else {
94 // TODO: refine to a type specific error
95 self.problems.error(Error::UnknownModuleType {
96 location: import.location,
97 name: import.name.clone(),
98 module_name: module.name.clone(),
99 type_constructors: module.public_type_names(),
100 value_with_same_name: module.get_importable_value(&import.name).is_some(),
101 });
102 return;
103 };
104
105 let type_info = type_info.clone().with_location(import.location);
106
107 self.environment.names.type_in_scope(
108 imported_name.clone(),
109 type_info.type_.as_ref(),
110 &type_info.parameters,
111 );
112
113 self.environment.references.register_type(
114 imported_name.clone(),
115 EntityKind::ImportedType {
116 module: module_name,
117 },
118 import.location,
119 Publicity::Private,
120 );
121
122 let alias_location = SrcSpan {
123 start: import.name_location().end,
124 end: import.location.end,
125 };
126
127 self.environment.references.register_type_reference(
128 type_info.module.clone(),
129 import.name.clone(),
130 imported_name,
131 import.name_location(),
132 ReferenceKind::Import(alias_location),
133 );
134
135 if let Err(e) = self
136 .environment
137 .insert_type_constructor(imported_name.clone(), type_info)
138 {
139 self.problems.error(e);
140 }
141 }
142
143 fn register_unqualified_value(
144 &mut self,
145 import: &UnqualifiedImport,
146 module_name: EcoString,
147 module: &ModuleInterface,
148 ) {
149 let import_name = &import.name;
150 let location = import.location;
151 let used_name = import.as_name.as_ref().unwrap_or(&import.name);
152
153 // Register the unqualified import if it is a value
154 let variant = match module.get_importable_value(import_name) {
155 Some(value) => {
156 let implementations = value.variant.implementations();
157 // Check the target support of the imported value
158 if self.environment.target_support.is_enforced()
159 && !implementations.supports(self.environment.target)
160 {
161 self.problems.error(Error::UnsupportedExpressionTarget {
162 target: self.environment.target,
163 location,
164 })
165 }
166
167 self.environment.insert_variable(
168 used_name.clone(),
169 value.variant.clone(),
170 value.type_.clone(),
171 value.publicity,
172 value.deprecation.clone(),
173 );
174 &value.variant
175 }
176 None => {
177 self.problems.error(Error::UnknownModuleValue {
178 location,
179 name: import_name.clone(),
180 module_name: module.name.clone(),
181 value_constructors: module.public_value_names(),
182 type_with_same_name: module.get_importable_type(import_name).is_some(),
183 context: crate::type_::error::ModuleValueUsageContext::UnqualifiedImport,
184 });
185 return;
186 }
187 };
188
189 match variant {
190 ValueConstructorVariant::Record { name, module, .. } => {
191 self.environment.names.named_constructor_in_scope(
192 module.clone(),
193 name.clone(),
194 used_name.clone(),
195 );
196 self.environment.references.register_value(
197 used_name.clone(),
198 EntityKind::ImportedConstructor {
199 module: module_name,
200 },
201 location,
202 Publicity::Private,
203 );
204
205 let alias_location = SrcSpan {
206 start: import.name_location().end,
207 end: import.location.end,
208 };
209
210 self.environment.references.register_value_reference(
211 module.clone(),
212 import_name.clone(),
213 used_name,
214 import.name_location(),
215 ReferenceKind::Import(alias_location),
216 );
217 }
218 ValueConstructorVariant::ModuleConstant { module, .. }
219 | ValueConstructorVariant::ModuleFn { module, .. } => {
220 self.environment.references.register_value(
221 used_name.clone(),
222 EntityKind::ImportedValue {
223 module: module_name,
224 },
225 location,
226 Publicity::Private,
227 );
228
229 let alias_location = SrcSpan {
230 start: import.name_location().end,
231 end: import.location.end,
232 };
233
234 self.environment.references.register_value_reference(
235 module.clone(),
236 import_name.clone(),
237 used_name,
238 import.name_location(),
239 ReferenceKind::Import(alias_location),
240 );
241 }
242 ValueConstructorVariant::LocalVariable { .. } => {}
243 };
244
245 // Check if value already was imported
246 if let Some(previous) = self.environment.unqualified_imported_names.get(used_name) {
247 self.problems.error(Error::DuplicateImport {
248 location,
249 previous_location: *previous,
250 name: import_name.clone(),
251 });
252 return;
253 }
254
255 // Register the name as imported so it can't be imported a
256 // second time in future
257 let _ = self
258 .environment
259 .unqualified_imported_names
260 .insert(used_name.clone(), location);
261 }
262
263 /// Check for invalid imports, such as `src` importing `test` or `dev`.
264 fn check_for_invalid_imports(
265 &mut self,
266 module_info: &ModuleInterface,
267 location: SrcSpan,
268 ) -> Result<(), Error> {
269 if self.origin.is_src()
270 && self
271 .environment
272 .dev_dependencies
273 .contains(&module_info.package)
274 {
275 return Err(Error::SrcImportingDevDependency {
276 importing_module: self.environment.current_module.clone(),
277 imported_module: module_info.name.clone(),
278 package: module_info.package.clone(),
279 location,
280 });
281 }
282
283 let kind = match (self.origin, module_info.origin) {
284 // `src` cannot import `test` or `dev`
285 (Origin::Src, Origin::Test) => InvalidImportKind::SrcImportingTest,
286 (Origin::Src, Origin::Dev) => InvalidImportKind::SrcImportingDev,
287 // `dev` cannot import `test`
288 (Origin::Dev, Origin::Test) => InvalidImportKind::DevImportingTest,
289 _ => return Ok(()),
290 };
291
292 Err(Error::InvalidImport {
293 location,
294 importing_module: self.environment.current_module.clone(),
295 imported_module: module_info.name.clone(),
296 kind,
297 })
298 }
299
300 fn register_module(
301 &mut self,
302 import: &UntypedImport,
303 import_info: &'context ModuleInterface,
304 ) -> Result<(), Error> {
305 let Some(used_name) = import.used_name() else {
306 return Ok(());
307 };
308
309 self.check_not_a_duplicate_import(&used_name, import.location)?;
310
311 if let Some(alias_location) = import.alias_location() {
312 self.environment.references.register_aliased_module(
313 used_name.clone(),
314 import.module.clone(),
315 alias_location,
316 import.location,
317 import.module_location,
318 );
319 } else {
320 self.environment.references.register_module(
321 used_name.clone(),
322 import.module.clone(),
323 import.location,
324 import.module_location,
325 None,
326 );
327 }
328
329 // Insert imported module into scope
330 let _ = self
331 .environment
332 .imported_modules
333 .insert(used_name.clone(), (import.location, import_info));
334
335 // Register this module as being imported
336 //
337 // Emit a warning if the module had already been imported.
338 // This isn't an error so long as the modules have different local aliases. In Gleam v2
339 // this will likely become an error.
340 if let Some(previous) = self.environment.names.imported_module(
341 import.module.clone(),
342 used_name,
343 import.location,
344 ) {
345 self.problems.warning(Warning::ModuleImportedTwice {
346 name: import.module.clone(),
347 first: previous,
348 second: import.location,
349 });
350 }
351
352 Ok(())
353 }
354
355 fn check_not_a_duplicate_import(
356 &self,
357 used_name: &EcoString,
358 location: SrcSpan,
359 ) -> Result<(), Error> {
360 // Check if a module was already imported with this name
361 if let Some((previous_location, _)) = self.environment.imported_modules.get(used_name) {
362 return Err(Error::DuplicateImport {
363 location,
364 previous_location: *previous_location,
365 name: used_name.clone(),
366 });
367 }
368 Ok(())
369 }
370}