+4
CHANGELOG.md
+4
CHANGELOG.md
+41
-26
compiler-core/src/analyse/imports.rs
+41
-26
compiler-core/src/analyse/imports.rs
···
5
5
build::Origin,
6
6
reference::{EntityKind, ReferenceKind},
7
7
type_::{
8
-
Environment, Error, ModuleInterface, Problems, ValueConstructorVariant,
8
+
Environment, Error, ModuleInterface, Problems, ValueConstructorVariant, Warning,
9
9
error::InvalidImportKind,
10
10
},
11
11
};
···
271
271
import: &UntypedImport,
272
272
import_info: &'context ModuleInterface,
273
273
) -> Result<(), Error> {
274
-
if let Some(used_name) = import.used_name() {
275
-
self.check_not_a_duplicate_import(&used_name, import.location)?;
274
+
let Some(used_name) = import.used_name() else {
275
+
return Ok(());
276
+
};
277
+
278
+
self.check_not_a_duplicate_import(&used_name, import.location)?;
276
279
277
-
if let Some(alias_location) = import.alias_location() {
278
-
self.environment.references.register_aliased_module(
279
-
used_name.clone(),
280
-
import.module.clone(),
281
-
alias_location,
282
-
import.location,
283
-
);
284
-
} else {
285
-
self.environment.references.register_module(
286
-
used_name.clone(),
287
-
import.module.clone(),
288
-
import.location,
289
-
);
290
-
}
280
+
if let Some(alias_location) = import.alias_location() {
281
+
self.environment.references.register_aliased_module(
282
+
used_name.clone(),
283
+
import.module.clone(),
284
+
alias_location,
285
+
import.location,
286
+
);
287
+
} else {
288
+
self.environment.references.register_module(
289
+
used_name.clone(),
290
+
import.module.clone(),
291
+
import.location,
292
+
);
293
+
}
291
294
292
-
// Insert imported module into scope
293
-
let _ = self
294
-
.environment
295
-
.imported_modules
296
-
.insert(used_name.clone(), (import.location, import_info));
295
+
// Insert imported module into scope
296
+
let _ = self
297
+
.environment
298
+
.imported_modules
299
+
.insert(used_name.clone(), (import.location, import_info));
297
300
298
-
self.environment
299
-
.names
300
-
.imported_module(import.module.clone(), used_name);
301
-
};
301
+
// Register this module as being imported
302
+
//
303
+
// Emit a warning if the module had already been imported.
304
+
// This isn't an error so long as the modules have different local aliases. In Gleam v2
305
+
// this will likely become an error.
306
+
if let Some(previous) = self.environment.names.imported_module(
307
+
import.module.clone(),
308
+
used_name,
309
+
import.location,
310
+
) {
311
+
self.problems.warning(Warning::ModuleImportedTwice {
312
+
name: import.module.clone(),
313
+
first: previous,
314
+
second: import.location,
315
+
});
316
+
}
302
317
303
318
Ok(())
304
319
}
+6
-1
compiler-core/src/exhaustiveness/printer.rs
+6
-1
compiler-core/src/exhaustiveness/printer.rs
···
193
193
use std::{collections::HashMap, sync::Arc};
194
194
195
195
use crate::{
196
+
ast::SrcSpan,
196
197
exhaustiveness::{
197
198
Variable,
198
199
missing_patterns::{Term, VariantField},
···
321
322
fn test_module_alias() {
322
323
let mut names = Names::new();
323
324
324
-
names.imported_module("mod".into(), "shapes".into());
325
+
assert!(
326
+
names
327
+
.imported_module("mod".into(), "shapes".into(), SrcSpan::new(50, 60))
328
+
.is_none()
329
+
);
325
330
326
331
let printer = Printer::new(&names);
327
332
+12
-1
compiler-core/src/type_/error.rs
+12
-1
compiler-core/src/type_/error.rs
···
983
983
truncation: BitArraySegmentTruncation,
984
984
location: SrcSpan,
985
985
},
986
+
987
+
/// In Gleam v1 it is possible to import one module twice using different aliases.
988
+
/// This is deprecated, and likely would be removed in a Gleam v2.
989
+
ModuleImportedTwice {
990
+
name: EcoString,
991
+
first: SrcSpan,
992
+
second: SrcSpan,
993
+
},
986
994
}
987
995
988
996
#[derive(Debug, Eq, Copy, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
···
1207
1215
| Warning::FeatureRequiresHigherGleamVersion { location, .. }
1208
1216
| Warning::JavaScriptIntUnsafe { location, .. }
1209
1217
| Warning::AssertLiteralValue { location, .. }
1210
-
| Warning::BitArraySegmentTruncatedValue { location, .. } => *location,
1218
+
| Warning::BitArraySegmentTruncatedValue { location, .. }
1219
+
| Warning::ModuleImportedTwice {
1220
+
second: location, ..
1221
+
} => *location,
1211
1222
}
1212
1223
}
1213
1224
+39
-11
compiler-core/src/type_/printer.rs
+39
-11
compiler-core/src/type_/printer.rs
···
3
3
use im::HashMap;
4
4
use std::{collections::HashSet, sync::Arc};
5
5
6
-
use crate::type_::{Type, TypeVar};
6
+
use crate::{
7
+
ast::SrcSpan,
8
+
type_::{Type, TypeVar},
9
+
};
7
10
8
11
/// This class keeps track of what names are used for modules in the current
9
12
/// scope, so they can be printed in errors, etc.
···
68
71
/// - key: "mod1"
69
72
/// - value: "mod1"
70
73
///
71
-
imported_modules: HashMap<EcoString, EcoString>,
74
+
imported_modules: HashMap<EcoString, (EcoString, SrcSpan)>,
72
75
73
76
/// Generic type parameters that have been annotated in the current
74
77
/// function.
···
176
179
}
177
180
178
181
/// Record an imported module in this module.
179
-
pub fn imported_module(&mut self, module_name: EcoString, module_alias: EcoString) {
180
-
_ = self.imported_modules.insert(module_name, module_alias)
182
+
///
183
+
/// Returns the location of the previous time this module was imported, if there was one.
184
+
pub fn imported_module(
185
+
&mut self,
186
+
module_name: EcoString,
187
+
module_alias: EcoString,
188
+
location: SrcSpan,
189
+
) -> Option<SrcSpan> {
190
+
self.imported_modules
191
+
.insert(module_name, (module_alias, location))
192
+
.map(|(_, location)| location)
181
193
}
182
194
183
195
/// Get the name and optional module qualifier for a named type.
···
188
200
print_mode: PrintMode,
189
201
) -> NameContextInformation<'a> {
190
202
if print_mode == PrintMode::ExpandAliases {
191
-
if let Some(module) = self.imported_modules.get(module) {
203
+
if let Some((module, _)) = self.imported_modules.get(module) {
192
204
return NameContextInformation::Qualified(module, name.as_str());
193
205
};
194
206
···
204
216
}
205
217
206
218
// This type is from a module that has been imported
207
-
if let Some(module) = self.imported_modules.get(module) {
219
+
if let Some((module, _)) = self.imported_modules.get(module) {
208
220
return NameContextInformation::Qualified(module, name.as_str());
209
221
};
210
222
···
238
250
}
239
251
240
252
// This value is from a module that has been imported
241
-
if let Some(module) = self.imported_modules.get(module) {
253
+
if let Some((module, _)) = self.imported_modules.get(module) {
242
254
return NameContextInformation::Qualified(module, name.as_str());
243
255
};
244
256
···
326
338
327
339
pub fn print_module(&self, module: &str) -> EcoString {
328
340
match self.names.imported_modules.get(module) {
329
-
Some(module) => module.clone(),
341
+
Some((module, _)) => module.clone(),
330
342
_ => module.split("/").last().unwrap_or(module).into(),
331
343
}
332
344
}
···
617
629
#[test]
618
630
fn test_module_alias() {
619
631
let mut names = Names::new();
620
-
names.imported_module("mod1".into(), "animals".into());
632
+
633
+
assert!(
634
+
names
635
+
.imported_module("mod1".into(), "animals".into(), SrcSpan::new(50, 63))
636
+
.is_none()
637
+
);
638
+
621
639
let mut printer = Printer::new(&names);
622
640
623
641
let type_ = Type::Named {
···
700
718
fn test_unqualified_import_and_module_alias() {
701
719
let mut names = Names::new();
702
720
703
-
names.imported_module("mod1".into(), "animals".into());
721
+
assert!(
722
+
names
723
+
.imported_module("mod1".into(), "animals".into(), SrcSpan::new(76, 93))
724
+
.is_none()
725
+
);
704
726
705
727
let _ = names
706
728
.local_types
···
723
745
#[test]
724
746
fn test_module_imports() {
725
747
let mut names = Names::new();
726
-
names.imported_module("mod".into(), "animals".into());
748
+
749
+
assert!(
750
+
names
751
+
.imported_module("mod".into(), "animals".into(), SrcSpan::new(76, 93))
752
+
.is_none()
753
+
);
754
+
727
755
let _ = names
728
756
.local_types
729
757
.insert(("mod2".into(), "Cat".into()), "Cat".into());
+29
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__import_module_twice.snap
+29
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__import_module_twice.snap
···
1
+
---
2
+
source: compiler-core/src/type_/tests/warnings.rs
3
+
assertion_line: 3918
4
+
expression: "import gleam/wibble as a\nimport gleam/wibble as b\n\npub fn main() {\n a.wobble() + b.wobble()\n}\n"
5
+
snapshot_kind: text
6
+
---
7
+
----- SOURCE CODE
8
+
-- gleam/wibble.gleam
9
+
pub fn wobble() { 1 }
10
+
11
+
-- main.gleam
12
+
import gleam/wibble as a
13
+
import gleam/wibble as b
14
+
15
+
pub fn main() {
16
+
a.wobble() + b.wobble()
17
+
}
18
+
19
+
20
+
----- WARNING
21
+
warning: Duplicate import
22
+
┌─ /src/warning/wrn.gleam:1:1
23
+
│
24
+
1 │ import gleam/wibble as a
25
+
│ ^^^^^^^^^^^^^^^^^^^^^^^^
26
+
2 │ import gleam/wibble as b
27
+
│ ^^^^^^^^^^^^^^^^^^^^^^^^
28
+
29
+
The gleam/wibble module has been imported twice.
+14
compiler-core/src/type_/tests/warnings.rs
+14
compiler-core/src/type_/tests/warnings.rs
···
3912
3912
"#
3913
3913
);
3914
3914
}
3915
+
3916
+
#[test]
3917
+
fn import_module_twice() {
3918
+
assert_warning!(
3919
+
("gleam/wibble", "pub fn wobble() { 1 }"),
3920
+
"import gleam/wibble as a
3921
+
import gleam/wibble as b
3922
+
3923
+
pub fn main() {
3924
+
a.wobble() + b.wobble()
3925
+
}
3926
+
"
3927
+
);
3928
+
}
+27
-1
compiler-core/src/warning.rs
+27
-1
compiler-core/src/warning.rs
···
1
1
use crate::{
2
2
ast::{BitArraySegmentTruncation, SrcSpan, TodoKind},
3
3
build::Target,
4
-
diagnostic::{self, Diagnostic, Location},
4
+
diagnostic::{self, Diagnostic, ExtraLabel, Location},
5
5
error::wrap,
6
6
type_::{
7
7
self,
···
1274
1274
span: *location,
1275
1275
},
1276
1276
extra_labels: Vec::new(),
1277
+
}),
1278
+
},
1279
+
1280
+
type_::Warning::ModuleImportedTwice {
1281
+
name,
1282
+
first,
1283
+
second,
1284
+
} => Diagnostic {
1285
+
title: "Duplicate import".into(),
1286
+
text: format!("The {name} module has been imported twice."),
1287
+
hint: None,
1288
+
level: diagnostic::Level::Warning,
1289
+
location: Some(Location {
1290
+
src: src.clone(),
1291
+
path: path.to_path_buf(),
1292
+
label: diagnostic::Label {
1293
+
text: None,
1294
+
span: *first,
1295
+
},
1296
+
extra_labels: vec![ExtraLabel {
1297
+
src_info: None,
1298
+
label: diagnostic::Label {
1299
+
text: None,
1300
+
span: *second,
1301
+
},
1302
+
}],
1277
1303
}),
1278
1304
},
1279
1305
},
+6
test-package-compiler/cases/imported_external_fns/src/three.gleam
+6
test-package-compiler/cases/imported_external_fns/src/three.gleam
+6
-6
test-package-compiler/cases/imported_external_fns/src/two.gleam
+6
-6
test-package-compiler/cases/imported_external_fns/src/two.gleam
···
1
1
import one.{
2
2
escaped_thing, escaped_thing as xescaped_thing, thing, thing as xthing,
3
3
}
4
-
import one as xone
4
+
import three as aliased
5
5
6
6
// Assigning a function to constants
7
7
8
8
const const_qualified = one.thing
9
9
10
-
const const_qualified_aliased = xone.thing
10
+
const const_qualified_aliased = aliased.thing
11
11
12
12
const const_unqualified = thing
13
13
···
15
15
16
16
const escaped_const_qualified = one.escaped_thing
17
17
18
-
const escaped_const_qualified_aliased = xone.escaped_thing
18
+
const escaped_const_qualified_aliased = aliased.escaped_thing
19
19
20
20
const escaped_const_unqualified = escaped_thing
21
21
···
47
47
}
48
48
49
49
pub fn fn_reference_qualified_aliased() {
50
-
xone.thing
50
+
aliased.thing
51
51
}
52
52
53
53
pub fn fn_reference_unqualified() {
···
65
65
}
66
66
67
67
pub fn fn_call_qualified_aliased() {
68
-
xone.thing()
68
+
aliased.thing()
69
69
}
70
70
71
71
pub fn fn_call_unqualified() {
···
83
83
}
84
84
85
85
pub fn argument_reference_qualified_aliased() {
86
-
x(xone.escaped_thing)
86
+
x(aliased.escaped_thing)
87
87
}
88
88
89
89
pub fn argument_reference_unqualified() {
+11
test-package-compiler/cases/imported_record_constructors/src/one/two.gleam
+11
test-package-compiler/cases/imported_record_constructors/src/one/two.gleam
+5
-5
test-package-compiler/cases/imported_record_constructors/src/two.gleam
+5
-5
test-package-compiler/cases/imported_record_constructors/src/two.gleam
···
1
1
import one/one.{A, A as C, B, B as D, User, User as XUser}
2
-
import one/one as xone
2
+
import one/two as aliased
3
3
4
4
/// For these statements we use the records in a qualified fashion
5
5
pub const qualified_const_a = one.A
···
14
14
qualified_const_b
15
15
}
16
16
17
-
pub const qualified_aliased_const_a = xone.A
17
+
pub const qualified_aliased_const_a = aliased.A
18
18
19
19
pub fn qualified_aliased_fn_a() {
20
20
qualified_aliased_const_a
21
21
}
22
22
23
-
pub const qualified_aliased_const_b = xone.B(xone.A, xone.A)
23
+
pub const qualified_aliased_const_b = aliased.B(aliased.A, aliased.A)
24
24
25
25
pub fn qualified_aliased_fn_b() {
26
26
qualified_aliased_const_b
···
68
68
}
69
69
70
70
pub fn destructure_qualified_aliased(user) {
71
-
let xone.User(name: name, score: score) = user
71
+
let aliased.User(name: name, score: score) = user
72
72
#(name, score)
73
73
}
74
74
···
88
88
}
89
89
90
90
pub fn update_qualified_aliased(user) {
91
-
xone.User(..user, name: "wibble")
91
+
aliased.User(..user, name: "wibble")
92
92
}
93
93
94
94
pub fn update_unqualified(user) {
+25
-1
test-package-compiler/src/snapshots/test_package_compiler__generated_tests__imported_external_fns.snap
+25
-1
test-package-compiler/src/snapshots/test_package_compiler__generated_tests__imported_external_fns.snap
···
27
27
'the.thing':'make.new'().
28
28
29
29
30
+
//// /out/lib/the_package/_gleam_artefacts/three.cache
31
+
<.cache binary>
32
+
33
+
//// /out/lib/the_package/_gleam_artefacts/three.cache_meta
34
+
<77 byte binary>
35
+
36
+
//// /out/lib/the_package/_gleam_artefacts/three.erl
37
+
-module(three).
38
+
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
39
+
-define(FILEPATH, "src/three.gleam").
40
+
-export([thing/0, escaped_thing/0]).
41
+
42
+
-file("src/three.gleam", 2).
43
+
-spec thing() -> nil.
44
+
thing() ->
45
+
thing:new().
46
+
47
+
-file("src/three.gleam", 6).
48
+
-spec escaped_thing() -> nil.
49
+
escaped_thing() ->
50
+
'the.thing':'make.new'().
51
+
52
+
30
53
//// /out/lib/the_package/_gleam_artefacts/two.cache
31
54
<.cache binary>
32
55
33
56
//// /out/lib/the_package/_gleam_artefacts/two.cache_meta
34
-
<487 byte binary>
57
+
<489 byte binary>
35
58
36
59
//// /out/lib/the_package/_gleam_artefacts/two.erl
37
60
-module(two).
···
131
154
{applications, []},
132
155
{description, ""},
133
156
{modules, [one,
157
+
three,
134
158
two]},
135
159
{registered, []}
136
160
]}.
+30
-4
test-package-compiler/src/snapshots/test_package_compiler__generated_tests__imported_record_constructors.snap
+30
-4
test-package-compiler/src/snapshots/test_package_compiler__generated_tests__imported_record_constructors.snap
···
25
25
26
26
27
27
28
+
//// /out/lib/the_package/_gleam_artefacts/one@two.cache
29
+
<.cache binary>
30
+
31
+
//// /out/lib/the_package/_gleam_artefacts/one@two.cache_meta
32
+
<97 byte binary>
33
+
34
+
//// /out/lib/the_package/_gleam_artefacts/one@two.erl
35
+
-module(one@two).
36
+
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
37
+
-define(FILEPATH, "src/one/two.gleam").
38
+
-export_type([a/0, b/0, user/0]).
39
+
40
+
-type a() :: a.
41
+
42
+
-type b() :: {b, a(), a()}.
43
+
44
+
-type user() :: {user, binary(), integer()}.
45
+
46
+
47
+
48
+
28
49
//// /out/lib/the_package/_gleam_artefacts/two.cache
29
50
<.cache binary>
30
51
···
64
85
{Name, Score}.
65
86
66
87
-file("src/two.gleam", 70).
67
-
-spec destructure_qualified_aliased(one@one:user()) -> {binary(), integer()}.
88
+
-spec destructure_qualified_aliased(one@two:user()) -> {binary(), integer()}.
68
89
destructure_qualified_aliased(User) ->
69
90
{user, Name, Score} = User,
70
91
{Name, Score}.
···
89
110
{user, <<"wibble"/utf8>>, erlang:element(3, _record)}.
90
111
91
112
-file("src/two.gleam", 90).
92
-
-spec update_qualified_aliased(one@one:user()) -> one@one:user().
113
+
-spec update_qualified_aliased(one@two:user()) -> one@two:user().
93
114
update_qualified_aliased(User) ->
94
115
_record = User,
95
116
{user, <<"wibble"/utf8>>, erlang:element(3, _record)}.
···
117
138
{b, a, a}.
118
139
119
140
-file("src/two.gleam", 19).
120
-
-spec qualified_aliased_fn_a() -> one@one:a().
141
+
-spec qualified_aliased_fn_a() -> one@two:a().
121
142
qualified_aliased_fn_a() ->
122
143
a.
123
144
124
145
-file("src/two.gleam", 25).
125
-
-spec qualified_aliased_fn_b() -> one@one:b().
146
+
-spec qualified_aliased_fn_b() -> one@two:b().
126
147
qualified_aliased_fn_b() ->
127
148
{b, a, a}.
128
149
···
153
174
{applications, []},
154
175
{description, ""},
155
176
{modules, [one@one,
177
+
one@two,
156
178
two]},
157
179
{registered, []}
158
180
]}.
···
160
182
161
183
//// /out/lib/the_package/include/one@one_User.hrl
162
184
-record(user, {name :: binary(), score :: integer()}).
185
+
186
+
187
+
//// /out/lib/the_package/include/one@two_User.hrl
188
+
-record(user, {name :: binary(), score :: integer()}).