Fork of daniellemaywood.uk/gleam — Wasm codegen work
12 kB
351 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 self.environment.references.register_type_reference(
123 type_info.module.clone(),
124 import.name.clone(),
125 imported_name,
126 import.imported_name_location,
127 ReferenceKind::Import,
128 );
129
130 if let Err(e) = self
131 .environment
132 .insert_type_constructor(imported_name.clone(), type_info)
133 {
134 self.problems.error(e);
135 }
136 }
137
138 fn register_unqualified_value(
139 &mut self,
140 import: &UnqualifiedImport,
141 module_name: EcoString,
142 module: &ModuleInterface,
143 ) {
144 let import_name = &import.name;
145 let location = import.location;
146 let used_name = import.as_name.as_ref().unwrap_or(&import.name);
147
148 // Register the unqualified import if it is a value
149 let variant = match module.get_importable_value(import_name) {
150 Some(value) => {
151 let implementations = value.variant.implementations();
152 // Check the target support of the imported value
153 if self.environment.target_support.is_enforced()
154 && !implementations.supports(self.environment.target)
155 {
156 self.problems.error(Error::UnsupportedExpressionTarget {
157 target: self.environment.target,
158 location,
159 })
160 }
161
162 self.environment.insert_variable(
163 used_name.clone(),
164 value.variant.clone(),
165 value.type_.clone(),
166 value.publicity,
167 value.deprecation.clone(),
168 );
169 &value.variant
170 }
171 None => {
172 self.problems.error(Error::UnknownModuleValue {
173 location,
174 name: import_name.clone(),
175 module_name: module.name.clone(),
176 value_constructors: module.public_value_names(),
177 type_with_same_name: module.get_importable_type(import_name).is_some(),
178 context: crate::type_::error::ModuleValueUsageContext::UnqualifiedImport,
179 });
180 return;
181 }
182 };
183
184 match variant {
185 ValueConstructorVariant::Record { name, module, .. } => {
186 self.environment.names.named_constructor_in_scope(
187 module.clone(),
188 name.clone(),
189 used_name.clone(),
190 );
191 self.environment.references.register_value(
192 used_name.clone(),
193 EntityKind::ImportedConstructor {
194 module: module_name,
195 },
196 location,
197 Publicity::Private,
198 );
199
200 self.environment.references.register_value_reference(
201 module.clone(),
202 import_name.clone(),
203 used_name,
204 import.imported_name_location,
205 ReferenceKind::Import,
206 );
207 }
208 ValueConstructorVariant::ModuleConstant { module, .. }
209 | ValueConstructorVariant::ModuleFn { module, .. } => {
210 self.environment.references.register_value(
211 used_name.clone(),
212 EntityKind::ImportedValue {
213 module: module_name,
214 },
215 location,
216 Publicity::Private,
217 );
218 self.environment.references.register_value_reference(
219 module.clone(),
220 import_name.clone(),
221 used_name,
222 import.imported_name_location,
223 ReferenceKind::Import,
224 );
225 }
226 ValueConstructorVariant::LocalVariable { .. } => {}
227 };
228
229 // Check if value already was imported
230 if let Some(previous) = self.environment.unqualified_imported_names.get(used_name) {
231 self.problems.error(Error::DuplicateImport {
232 location,
233 previous_location: *previous,
234 name: import_name.clone(),
235 });
236 return;
237 }
238
239 // Register the name as imported so it can't be imported a
240 // second time in future
241 let _ = self
242 .environment
243 .unqualified_imported_names
244 .insert(used_name.clone(), location);
245 }
246
247 /// Check for invalid imports, such as `src` importing `test` or `dev`.
248 fn check_for_invalid_imports(
249 &mut self,
250 module_info: &ModuleInterface,
251 location: SrcSpan,
252 ) -> Result<(), Error> {
253 if self.origin.is_src()
254 && self
255 .environment
256 .dev_dependencies
257 .contains(&module_info.package)
258 {
259 return Err(Error::SrcImportingDevDependency {
260 importing_module: self.environment.current_module.clone(),
261 imported_module: module_info.name.clone(),
262 package: module_info.package.clone(),
263 location,
264 });
265 }
266
267 let kind = match (self.origin, module_info.origin) {
268 // `src` cannot import `test` or `dev`
269 (Origin::Src, Origin::Test) => InvalidImportKind::SrcImportingTest,
270 (Origin::Src, Origin::Dev) => InvalidImportKind::SrcImportingDev,
271 // `dev` cannot import `test`
272 (Origin::Dev, Origin::Test) => InvalidImportKind::DevImportingTest,
273 _ => return Ok(()),
274 };
275
276 Err(Error::InvalidImport {
277 location,
278 importing_module: self.environment.current_module.clone(),
279 imported_module: module_info.name.clone(),
280 kind,
281 })
282 }
283
284 fn register_module(
285 &mut self,
286 import: &UntypedImport,
287 import_info: &'context ModuleInterface,
288 ) -> Result<(), Error> {
289 let Some(used_name) = import.used_name() else {
290 return Ok(());
291 };
292
293 self.check_not_a_duplicate_import(&used_name, import.location)?;
294
295 if let Some(alias_location) = import.alias_location() {
296 self.environment.references.register_aliased_module(
297 used_name.clone(),
298 import.module.clone(),
299 alias_location,
300 import.location,
301 );
302 } else {
303 self.environment.references.register_module(
304 used_name.clone(),
305 import.module.clone(),
306 import.location,
307 );
308 }
309
310 // Insert imported module into scope
311 let _ = self
312 .environment
313 .imported_modules
314 .insert(used_name.clone(), (import.location, import_info));
315
316 // Register this module as being imported
317 //
318 // Emit a warning if the module had already been imported.
319 // This isn't an error so long as the modules have different local aliases. In Gleam v2
320 // this will likely become an error.
321 if let Some(previous) = self.environment.names.imported_module(
322 import.module.clone(),
323 used_name,
324 import.location,
325 ) {
326 self.problems.warning(Warning::ModuleImportedTwice {
327 name: import.module.clone(),
328 first: previous,
329 second: import.location,
330 });
331 }
332
333 Ok(())
334 }
335
336 fn check_not_a_duplicate_import(
337 &self,
338 used_name: &EcoString,
339 location: SrcSpan,
340 ) -> Result<(), Error> {
341 // Check if a module was already imported with this name
342 if let Some((previous_location, _)) = self.environment.imported_modules.get(used_name) {
343 return Err(Error::DuplicateImport {
344 location,
345 previous_location: *previous_location,
346 name: used_name.clone(),
347 });
348 }
349 Ok(())
350 }
351}