1mod body;
2mod constant;
3mod declaration;
4mod link;
5
6use crate::frontend::{HostedTypedProgram, HostedTypedProgramModule};
7use crate::host::{
8 HostProfile, RegisteredHostConstructions, RegisteredHostImplementationId,
9 RegisteredHostImplementations, RegisteredHostProviderModule,
10};
11use crate::plan::{HostImplementationBinding, HostedLibraryModulePlan, HostedModulePlan, ModuleId};
12use crate::planner::error::PlanError;
13
14pub fn plan_host_program<Profile: HostProfile>(
15 program: HostedTypedProgram<Profile>,
16) -> Result<HostedModulePlan<Profile>, PlanError> {
17 let (root_index, modules, providers, implementations) = program.into_parts();
18 plan_host_program_schema(root_index, modules, providers, super::ModuleRole::Root).map(
19 |planned| {
20 let implementation_bindings =
21 bind_implementations(planned.implementations, &implementations);
22 HostedModulePlan::new(
23 planned.root,
24 crate::plan::FunctionTemplateId::in_module(planned.root, 0),
25 planned.modules,
26 implementation_bindings,
27 )
28 },
29 )
30}
31
32pub(crate) fn plan_host_library_program<Profile: HostProfile>(
33 program: HostedTypedProgram<Profile>,
34) -> Result<HostedLibraryModulePlan<Profile>, PlanError> {
35 let (root_index, modules, providers, implementations) = program.into_parts();
36 plan_host_program_schema(root_index, modules, providers, super::ModuleRole::Library).map(
37 |planned| {
38 let implementation_bindings =
39 bind_implementations(planned.implementations, &implementations);
40 HostedLibraryModulePlan::new(planned.root, planned.modules, implementation_bindings)
41 },
42 )
43}
44
45fn bind_implementations<Profile: HostProfile>(
46 planned: Vec<(
47 crate::plan::FunctionTemplateId,
48 RegisteredHostConstructions,
49 RegisteredHostImplementationId,
50 )>,
51 implementations: &RegisteredHostImplementations<Profile>,
52) -> Vec<HostImplementationBinding<Profile>> {
53 planned
54 .into_iter()
55 .map(|(template, constructions, implementation)| {
56 HostImplementationBinding::new(
57 template,
58 constructions,
59 implementations.implementation(implementation),
60 )
61 })
62 .collect()
63}
64
65fn plan_host_program_schema(
66 root_index: usize,
67 modules: Vec<HostedTypedProgramModule>,
68 providers: Vec<RegisteredHostProviderModule>,
69 root_role: super::ModuleRole,
70) -> Result<body::PlannedHostedProgram, PlanError> {
71 let root = ModuleId::new(root_index);
72 declaration::collect_hosted_module_declarations(modules, providers)
73 .and_then(|declarations| link::link_hosted_modules(root, root_role, declarations))
74 .and_then(constant::reserve_hosted_constants)
75 .and_then(|(registry, modules)| constant::plan_hosted_constant_bodies(registry, modules))
76 .and_then(|(registry, modules)| body::plan_hosted_modules(root, ®istry, modules))
77}
78
79#[cfg(test)]
80mod tests {
81 use super::plan_host_program;
82 use crate::frontend::{ModuleSource, PackageSource, compile_typed_host_program};
83 use crate::host::{HostModule, HostParameter, HostProviderSet};
84 use crate::plan::{
85 FunctionShape, FunctionTemplateId, FunctionType, ModuleId, ValueShape, ValueType,
86 };
87 use crate::planner::{ExternalTypeProviderLinkReason, PlanError, UnsupportedFunctionReason};
88 use ecow::EcoString;
89 use num_bigint::BigInt;
90
91 #[test]
92 fn plan_host_program_bodyless_templates_with_module_qualified_ids() {
93 let choose = |condition: bool, left: BigInt, right: BigInt| {
94 if condition { left } else { right }
95 };
96 assert_eq!(
97 choose(false, BigInt::from(10), BigInt::from(20)),
98 BigInt::from(20),
99 );
100 assert_eq!(
101 choose(true, BigInt::from(10), BigInt::from(20)),
102 BigInt::from(10),
103 );
104 let all = |a: bool, b: bool, c: bool, d: bool, e: bool, f: bool, g: bool| {
105 a && b && c && d && e && f && g
106 };
107 assert!(all(true, true, true, true, true, true, true));
108
109 let hosts = HostProviderSet::new([HostModule::new("host_support", "host/math")
110 .expect("host module should be valid")
111 .with_function("add", <BigInt as std::ops::Add>::add)
112 .expect("host function should be valid")
113 .with_function("subtract", <BigInt as std::ops::Sub>::sub)
114 .expect("host function should be valid")
115 .with_function("ready", <bool as Default>::default)
116 .expect("host function should be valid")
117 .with_function("choose", choose)
118 .expect("host function should be valid")
119 .with_function("all", all)
120 .expect("host function should be valid")
121 .with_function(
122 "consume",
123 |_: BigInt,
124 _: f64,
125 _: EcoString,
126 _: crate::BitArrayValue,
127 _: char,
128 _: bool,
129 (): ()| (),
130 )
131 .expect("host function should be valid")])
132 .expect("host modules should be unique");
133 let typed = compile_typed_host_program(
134 "application",
135 "main",
136 [PackageSource::new(
137 "application",
138 ["host_support"],
139 [ModuleSource::new(
140 "main",
141 "main.gleam",
142 r#"
143import host/math.{add}
144
145pub fn main() {
146 add(1, 2)
147}
148"#,
149 )],
150 )],
151 hosts,
152 )
153 .expect("host program should compile");
154 let plan = plan_host_program(typed).expect("host program should plan");
155
156 assert_eq!(plan.root(), ModuleId::new(1));
157 assert_eq!(
158 plan.entry(),
159 FunctionTemplateId::in_module(ModuleId::new(1), 0)
160 );
161 assert_eq!(
162 plan.modules()
163 .iter()
164 .map(|module| (module.package().as_str(), module.module().as_str()))
165 .collect::<Vec<_>>(),
166 [("host_support", "host/math"), ("application", "main")],
167 );
168 assert_eq!(plan.modules()[0].id(), ModuleId::new(0));
169 assert_eq!(plan.modules()[1].id(), ModuleId::new(1));
170 assert!(plan.modules()[0].source_context().is_none());
171 assert!(plan.modules()[1].source_context().is_some());
172 let host = &plan.modules()[0];
173 assert_eq!(host.id(), ModuleId::new(0));
174 assert_eq!(host.functions().len(), 6);
175 let functions = host
176 .functions()
177 .iter()
178 .map(|function| {
179 function
180 .host_template()
181 .expect("source-less module should retain host templates")
182 })
183 .collect::<Vec<_>>();
184 assert_eq!(functions[0].name(), "add");
185 assert_eq!(
186 functions[0].id(),
187 FunctionTemplateId::in_module(ModuleId::new(0), 0),
188 );
189 assert_eq!(functions[0].package(), "host_support");
190 assert_eq!(functions[0].module(), "host/math");
191 assert_eq!(functions[0].scheme().parameters(), &[]);
192 assert_eq!(
193 functions[0].signature().shape(),
194 &FunctionShape::new(vec![ValueShape::Int, ValueShape::Int], ValueShape::Int,),
195 );
196 assert_eq!(
197 functions[0].type_(),
198 &FunctionType::new(vec![ValueType::Int, ValueType::Int], ValueType::Int),
199 );
200 assert!(matches!(
201 functions[0].layout(),
202 [HostParameter::Int(left), HostParameter::Int(right)]
203 if left.index() == 0 && right.index() == 1
204 ));
205 assert_eq!(functions[1].name(), "subtract");
206 assert_eq!(functions[2].name(), "ready");
207 assert_eq!(
208 functions[2].signature().shape(),
209 &FunctionShape::new(Vec::new(), ValueShape::Bool),
210 );
211 assert_eq!(
212 functions[2].type_(),
213 &FunctionType::new(Vec::new(), ValueType::Bool),
214 );
215 assert_eq!(functions[3].name(), "choose");
216 assert_eq!(
217 functions[3].signature().shape(),
218 &FunctionShape::new(
219 vec![ValueShape::Bool, ValueShape::Int, ValueShape::Int],
220 ValueShape::Int,
221 ),
222 );
223 assert_eq!(
224 functions[3].type_(),
225 &FunctionType::new(
226 vec![ValueType::Bool, ValueType::Int, ValueType::Int],
227 ValueType::Int,
228 ),
229 );
230 assert!(matches!(
231 functions[3].layout(),
232 [
233 HostParameter::Bool(condition),
234 HostParameter::Int(left),
235 HostParameter::Int(right),
236 ] if condition.index() == 0 && left.index() == 0 && right.index() == 1
237 ));
238 assert_eq!(functions[4].name(), "all");
239 assert_eq!(
240 functions[4].signature().shape(),
241 &FunctionShape::new(vec![ValueShape::Bool; 7], ValueShape::Bool),
242 );
243 assert_eq!(
244 functions[4].type_(),
245 &FunctionType::new(vec![ValueType::Bool; 7], ValueType::Bool),
246 );
247 assert!(matches!(
248 functions[4].layout(),
249 [
250 HostParameter::Bool(first),
251 HostParameter::Bool(second),
252 HostParameter::Bool(third),
253 HostParameter::Bool(fourth),
254 HostParameter::Bool(fifth),
255 HostParameter::Bool(sixth),
256 HostParameter::Bool(seventh),
257 ] if first.index() == 0
258 && second.index() == 1
259 && third.index() == 2
260 && fourth.index() == 3
261 && fifth.index() == 4
262 && sixth.index() == 5
263 && seventh.index() == 6
264 ));
265 assert_eq!(functions[5].name(), "consume");
266 assert_eq!(
267 functions[5].signature().shape(),
268 &FunctionShape::new(
269 vec![
270 ValueShape::Int,
271 ValueShape::Float,
272 ValueShape::String,
273 ValueShape::BitArray,
274 ValueShape::UtfCodepoint,
275 ValueShape::Bool,
276 ValueShape::Nil,
277 ],
278 ValueShape::Nil,
279 ),
280 );
281 assert_eq!(
282 functions[5].type_(),
283 &FunctionType::new(
284 vec![
285 ValueType::Int,
286 ValueType::Float,
287 ValueType::String,
288 ValueType::BitArray,
289 ValueType::UtfCodepoint,
290 ValueType::Bool,
291 ValueType::Nil,
292 ],
293 ValueType::Nil,
294 ),
295 );
296 assert!(matches!(
297 functions[5].layout(),
298 [
299 HostParameter::Int(int),
300 HostParameter::Float(float),
301 HostParameter::String(string),
302 HostParameter::BitArray(bit_array),
303 HostParameter::UtfCodepoint(utf_codepoint),
304 HostParameter::Bool(bool_),
305 HostParameter::Nil(nil),
306 ] if int.index() == 0
307 && float.index() == 0
308 && string.index() == 0
309 && bit_array.index() == 0
310 && utf_codepoint.index() == 0
311 && bool_.index() == 0
312 && nil.index() == 0
313 ));
314 let source = plan.modules()[1].functions()[0]
315 .gleam_body()
316 .expect("root module should retain its source function");
317 assert_eq!(source.name(), "main");
318 }
319
320 #[test]
321 fn plan_host_program_source_dependencies_as_dependency_modules() {
322 let typed = compile_typed_host_program(
323 "application",
324 "main",
325 [
326 PackageSource::new(
327 "application",
328 ["library"],
329 [ModuleSource::new(
330 "main",
331 "main.gleam",
332 "pub fn main() { 1 }",
333 )],
334 ),
335 PackageSource::new(
336 "library",
337 Vec::<EcoString>::new(),
338 [ModuleSource::new(
339 "support",
340 "support.gleam",
341 "pub fn unused() { 2 }",
342 )],
343 ),
344 ],
345 HostProviderSet::new(Vec::<HostModule>::new())
346 .expect("empty host modules should be valid"),
347 )
348 .expect("hosted source program should compile");
349 let plan = plan_host_program(typed).expect("hosted source program should plan");
350
351 assert_eq!(plan.root(), ModuleId::new(1));
352 assert_eq!(
353 plan.modules()
354 .iter()
355 .map(|module| (module.package().as_str(), module.module().as_str()))
356 .collect::<Vec<_>>(),
357 [("library", "support"), ("application", "main")],
358 );
359 assert_eq!(
360 plan.modules()[0].functions()[0]
361 .gleam_body()
362 .expect("dependency should remain a source function")
363 .name(),
364 "unused",
365 );
366 }
367
368 #[test]
369 fn reject_profile_host_program_source_owner_boundaries() {
370 let cases = [
371 (
372 "pub fn other() { 1 }",
373 PlanError::UnsupportedFunction {
374 name: "main".into(),
375 reason: UnsupportedFunctionReason::MissingMain,
376 },
377 ),
378 (
379 r#"
380@external(erlang, "external", "thing")
381pub type Thing
382
383pub fn main() { 1 }
384"#,
385 PlanError::ExternalTypeProviderLink {
386 package: "application".into(),
387 module: "main".into(),
388 type_: "Thing".into(),
389 reason: Box::new(ExternalTypeProviderLinkReason::MissingRegistration),
390 },
391 ),
392 (
393 r#"
394const unsupported = <<1:native>>
395
396pub fn main() { 1 }
397"#,
398 PlanError::UnsupportedBitArraySegment {
399 reason: crate::planner::UnsupportedBitArraySegmentReason::NativeEndianness,
400 },
401 ),
402 (
403 r#"
404fn unsupported() { <<1:native>> }
405
406pub fn main() { 1 }
407"#,
408 PlanError::UnsupportedBitArraySegment {
409 reason: crate::planner::UnsupportedBitArraySegmentReason::NativeEndianness,
410 },
411 ),
412 ];
413
414 for (source, expected) in cases {
415 let typed = compile_typed_host_program(
416 "application",
417 "main",
418 [PackageSource::new(
419 "application",
420 Vec::<EcoString>::new(),
421 [ModuleSource::new("main", "main.gleam", source)],
422 )],
423 HostProviderSet::new(Vec::<HostModule>::new())
424 .expect("empty host modules should be valid"),
425 )
426 .expect("profile-out source should still compile");
427 assert_eq!(plan_host_program(typed).err(), Some(expected));
428 }
429 }
430}