Skip to main content

geam_core/planner/module/
host.rs

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, &registry, 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}