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