Fork of daniellemaywood.uk/gleam — Wasm codegen work
9.2 kB
340 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2024 The Gleam contributors
3
4use std::time::SystemTime;
5
6use camino::Utf8PathBuf;
7use ecow::EcoString;
8use globset::GlobBuilder;
9use hexpm::version::Identifier;
10
11use crate::{
12 analyse::TargetSupport,
13 build::{Module, Origin, Package, Target},
14 config::{Docs, ErlangConfig, GleamVersion, JavaScriptConfig, PackageConfig},
15 line_numbers::LineNumbers,
16 type_::PRELUDE_MODULE_NAME,
17 uid::UniqueIdGenerator,
18 warning::{TypeWarningEmitter, WarningEmitter},
19};
20
21use super::PackageInterface;
22
23#[macro_export]
24macro_rules! assert_package_interface_with_name {
25 ($module_name:expr, $src:expr) => {
26 let output =
27 $crate::package_interface::tests::compile_package(Some($module_name), $src, None);
28 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
29 };
30}
31
32#[macro_export]
33macro_rules! assert_package_interface {
34 (($dep_package:expr, $dep_name:expr, $dep_src:expr), $src:expr $(,)?) => {{
35 let output = $crate::package_interface::tests::compile_package(
36 None,
37 $src,
38 Some(($dep_package, $dep_name, $dep_src)),
39 );
40 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
41 }};
42
43 (($dep_package:expr, $dep_name:expr, $dep_src:expr), $src:expr $(,)?) => {{
44 let output = $crate::package_interface::tests::compile_package(
45 None,
46 $src,
47 Some(($dep_package, $dep_name, $dep_src)),
48 );
49 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
50 }};
51
52 ($src:expr) => {{
53 let output = $crate::package_interface::tests::compile_package(None, $src, None);
54 insta::assert_snapshot!(insta::internals::AutoName, output, $src);
55 }};
56}
57
58pub fn compile_package(
59 module_name: Option<&str>,
60 src: &str,
61 dep: Option<(&str, &str, &str)>,
62) -> String {
63 let mut modules = im::HashMap::new();
64 let ids = UniqueIdGenerator::new();
65 // DUPE: preludeinsertion
66 // TODO: Currently we do this here and also in the tests. It would be better
67 // to have one place where we create all this required state for use in each
68 // place.
69 let _ = modules.insert(
70 PRELUDE_MODULE_NAME.into(),
71 crate::type_::build_prelude(&ids),
72 );
73 let mut direct_dependencies = std::collections::HashMap::from_iter(vec![]);
74 if let Some((dep_package, dep_name, dep_src)) = dep {
75 let parsed = crate::parse::parse_module(
76 Utf8PathBuf::from("test/path"),
77 dep_src,
78 &WarningEmitter::null(),
79 )
80 .expect("dep syntax error");
81 let mut ast = parsed.module;
82 ast.name = dep_name.into();
83 let line_numbers = LineNumbers::new(dep_src);
84 let mut config = PackageConfig::default();
85 config.name = dep_package.into();
86
87 let dep = crate::analyse::ModuleAnalyzerConstructor::<()> {
88 target: Target::Erlang,
89 ids: &ids,
90 origin: Origin::Src,
91 importable_modules: &modules,
92 warnings: &TypeWarningEmitter::null(),
93 direct_dependencies: &std::collections::HashMap::new(),
94 dev_dependencies: &std::collections::HashSet::new(),
95 target_support: TargetSupport::Enforced,
96 package_config: &config,
97 }
98 .infer_module(ast, line_numbers, "".into())
99 .expect("should successfully infer");
100 let _ = modules.insert(dep_name.into(), dep.type_info);
101 let _ = direct_dependencies.insert(dep_package.into(), ());
102 }
103 let parsed =
104 crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null())
105 .expect("syntax error");
106
107 let mut ast = parsed.module;
108 let module_name = module_name
109 .map(EcoString::from)
110 .unwrap_or("my/module".into());
111
112 ast.name = module_name.clone();
113 let mut config = PackageConfig::default();
114 config.name = "my_package".into();
115 let ast = crate::analyse::ModuleAnalyzerConstructor {
116 target: Target::Erlang,
117 ids: &ids,
118 origin: Origin::Src,
119 importable_modules: &modules,
120 warnings: &TypeWarningEmitter::null(),
121 direct_dependencies: &direct_dependencies,
122 dev_dependencies: &std::collections::HashSet::new(),
123 target_support: TargetSupport::Enforced,
124 package_config: &config,
125 }
126 .infer_module(ast, LineNumbers::new(src), "".into())
127 .expect("should successfully infer");
128
129 // TODO: all the bits above are basically copy pasted from the javascript
130 // and erlang test helpers. A refactor might be due here.
131 let mut module = Module {
132 name: module_name,
133 code: src.into(),
134 mtime: SystemTime::UNIX_EPOCH,
135 input_path: "wibble".into(),
136 origin: Origin::Src,
137 ast,
138 extra: parsed.extra,
139 dependencies: vec![],
140 };
141 module.attach_doc_and_module_comments();
142 let package: Package = package_from_module(module);
143 serde_json::to_string_pretty(&PackageInterface::from_package(
144 &package,
145 &im::HashMap::new(),
146 ))
147 .expect("to json")
148}
149
150fn package_from_module(module: Module) -> Package {
151 Package {
152 config: PackageConfig {
153 name: "my_package".into(),
154 version: hexpm::version::Version {
155 major: 11,
156 minor: 10,
157 patch: 9,
158 pre: vec![
159 Identifier::Numeric(1),
160 Identifier::AlphaNumeric("wibble".into()),
161 ],
162 build: Some("build".into()),
163 },
164 gleam_version: Some(GleamVersion::new("1.0.0".to_string()).unwrap()),
165 licences: vec![],
166 description: "description".into(),
167 documentation: Docs { pages: vec![] },
168 dependencies: std::collections::HashMap::new(),
169 dev_dependencies: std::collections::HashMap::new(),
170 repository: None,
171 links: vec![],
172 erlang: ErlangConfig::default(),
173 javascript: JavaScriptConfig::default(),
174 target: Target::Erlang,
175 internal_modules: Some(vec![
176 GlobBuilder::new("internals/*")
177 .build()
178 .expect("internals glob"),
179 ]),
180 },
181 cached_module_names: Vec::new(),
182 modules: vec![module],
183 }
184}
185
186#[test]
187pub fn package_documentation_is_included() {
188 assert_package_interface!(
189 "
190//// Some package
191//// documentation!
192
193pub fn main() { 1 }
194"
195 );
196}
197
198#[test]
199pub fn private_definitions_are_not_included() {
200 assert_package_interface!(
201 "
202const float = 1.1
203fn main() {}
204type Wibble
205type Wob = Int
206"
207 );
208}
209
210#[test]
211pub fn internal_definitions_are_not_included() {
212 assert_package_interface!(
213 "
214@internal pub const float = 1.1
215@internal pub fn main() {}
216@internal pub type Wibble
217@internal pub type Wobble = Int
218"
219 );
220}
221
222#[test]
223pub fn opaque_constructors_are_not_exposed() {
224 assert_package_interface!("pub opaque type Wibble { Wob }")
225}
226
227#[test]
228pub fn type_aliases() {
229 assert_package_interface!("pub type Wibble(a) = List(a)")
230}
231
232#[test]
233pub fn type_definition() {
234 assert_package_interface!(
235 "
236/// Wibble's documentation
237pub type Wibble(a, b) {
238 Wibble
239 Wobble
240}
241"
242 )
243}
244
245#[test]
246pub fn prelude_types() {
247 assert_package_interface!(
248 r#"
249pub const float = 1.1
250pub const string = ""
251pub const int = 1
252pub const bool = True
253"#
254 );
255}
256
257#[test]
258pub fn generic_function() {
259 assert_package_interface!(
260 r#"
261pub type Wob(a) { Wob }
262@deprecated("deprecation message")
263pub fn main() { Wob }
264"#
265 );
266}
267
268#[test]
269pub fn imported_type() {
270 assert_package_interface!(
271 ("other_package", "other_module", "pub type Element(a)"),
272 r#"
273import other_module.{type Element}
274pub fn main() -> Element(Int) {}
275"#
276 );
277}
278
279#[test]
280pub fn imported_aliased_type_keeps_original_name() {
281 assert_package_interface!(
282 ("other_package", "other_module", "pub type Element(a)"),
283 r#"
284import other_module.{type Element as Alias} as module_alias
285pub fn main() -> Alias(module_alias.Element(a)) {}
286"#
287 );
288}
289
290#[test]
291pub fn multiple_type_variables() {
292 assert_package_interface!(
293 r#"
294pub type Box(a, b)
295pub fn some_type_variables(a: a, b: b, c: Box(c, d)) -> Box(a, d) {}
296"#
297 );
298}
299
300#[test]
301pub fn type_constructors() {
302 assert_package_interface!(
303 r#"
304pub type Box(a, b) {
305 Box(b, Int)
306 OtherBox(message: String, a: a)
307}
308"#
309 );
310}
311
312#[test]
313pub fn internal_modules_are_not_exported() {
314 assert_package_interface_with_name!("internals/internal_module", "pub fn main() { 1 }");
315}
316
317#[test]
318pub fn labelled_function_parameters() {
319 assert_package_interface!(
320 r#"
321pub fn fold(list: List(a), from acc: b, with f: fn(a, b) -> b) -> b {
322 todo
323}
324"#
325 );
326}
327
328#[test]
329pub fn constructors_with_documentation() {
330 assert_package_interface!(
331 r#"
332pub type Wibble {
333 /// This is the Wibble variant. It contains some example data.
334 Wibble(Int)
335 /// This is the Wobble variant. It is a recursive type.
336 Wobble(Wibble)
337}
338"#
339 );
340}