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