Skip to main content

geam_core/planner/
module.rs

1use crate::frontend::TypedProgram;
2use crate::plan::{
3    FunctionFunctionLocalId, FunctionTemplateId, IntFunctionLocalId, ModuleId, ModulePlan,
4    ParamBinding, ParamLocal, PlannedModule, SourceContext,
5};
6use crate::planner::context::{AnonymousFunctions, FunctionInfo, FunctionParam};
7use crate::planner::error::{
8    InvalidFunctionShapeReason, InvalidTypedAstReason, PlanError, UnsupportedFunctionReason,
9};
10use crate::planner::function::{function_name, plan_function};
11use crate::planner::type_parameter::TypeParameterScope;
12use ecow::EcoString;
13use gleam_compiler_core::ast::{ArgNames, TypedFunction, TypedModule};
14use gleam_compiler_core::type_::Type;
15use std::collections::HashMap;
16use std::collections::HashSet;
17
18use constant::{ConstantBodies, plan_constant_bodies, reserve_constants};
19use registry::{ModuleRegistry, ProgramRegistry};
20mod external_type;
21
22pub use host::plan_host_program;
23
24pub fn plan_module(module: TypedModule) -> Result<ModulePlan, PlanError> {
25    plan_modules(
26        0,
27        vec![ModuleInput {
28            module,
29            source_context: None,
30        }],
31    )
32}
33
34pub fn plan_module_with_source(
35    module: TypedModule,
36    source_context: SourceContext,
37) -> Result<ModulePlan, PlanError> {
38    plan_modules(
39        0,
40        vec![ModuleInput {
41            module,
42            source_context: Some(source_context),
43        }],
44    )
45}
46
47pub fn plan_program(program: TypedProgram) -> Result<ModulePlan, PlanError> {
48    let (root_index, modules) = program.into_parts();
49    plan_modules(
50        root_index,
51        modules
52            .into_iter()
53            .map(|module| ModuleInput {
54                module: module.module,
55                source_context: Some(SourceContext::new(module.path, module.source)),
56            })
57            .collect(),
58    )
59}
60
61struct ModuleInput {
62    module: TypedModule,
63    source_context: Option<SourceContext>,
64}
65
66struct ModuleDeclarations {
67    id: ModuleId,
68    package: EcoString,
69    module_name: EcoString,
70    source_context: Option<SourceContext>,
71    custom_types: Vec<crate::plan::CustomTypeDefinition>,
72    functions: Vec<gleam_compiler_core::ast::TypedFunction>,
73    constants: Vec<gleam_compiler_core::ast::TypedModuleConstant>,
74}
75
76struct ModuleBodies {
77    id: ModuleId,
78    package: EcoString,
79    source_context: Option<SourceContext>,
80    functions: Vec<FunctionToPlan>,
81    constants: ConstantBodies,
82    anonymous_functions: AnonymousFunctions,
83}
84
85struct ModuleFunctionDeclarations {
86    id: ModuleId,
87    package: EcoString,
88    module_name: EcoString,
89    source_context: Option<SourceContext>,
90    custom_types: Vec<crate::plan::CustomTypeDefinition>,
91    functions_by_name: HashMap<EcoString, FunctionInfo>,
92    functions: Vec<FunctionToPlan>,
93    constants: Vec<gleam_compiler_core::ast::TypedModuleConstant>,
94    anonymous_functions: AnonymousFunctions,
95}
96
97struct ModuleFunctions {
98    id: ModuleId,
99    package: EcoString,
100    source_context: Option<SourceContext>,
101    functions: Vec<FunctionToPlan>,
102    constants: crate::plan::ConstantTemplates,
103    anonymous_functions: AnonymousFunctions,
104}
105
106fn plan_modules(root_index: usize, modules: Vec<ModuleInput>) -> Result<ModulePlan, PlanError> {
107    let root = ModuleId::new(root_index);
108    let mut declarations = Vec::with_capacity(modules.len());
109
110    for (index, module) in modules.into_iter().enumerate() {
111        let id = ModuleId::new(index);
112        let package = module.module.type_info.package.clone();
113        let definitions = module.module.definitions;
114        let module_name = module.module.name;
115        let custom_types =
116            custom_type::plan_custom_types(&package, &module_name, definitions.custom_types)?;
117        declarations.push(ModuleDeclarations {
118            id,
119            package,
120            module_name,
121            source_context: module.source_context,
122            custom_types,
123            functions: definitions.functions,
124            constants: definitions.constants,
125        });
126    }
127
128    let mut function_declarations = Vec::with_capacity(declarations.len());
129    for declaration in declarations {
130        let role = if declaration.id == root {
131            ModuleRole::Root
132        } else {
133            ModuleRole::Dependency
134        };
135        let FunctionTable {
136            by_name,
137            functions,
138            anonymous_functions,
139        } = function_table(declaration.id, &declaration.functions, role)?;
140        function_declarations.push(ModuleFunctionDeclarations {
141            id: declaration.id,
142            package: declaration.package,
143            module_name: declaration.module_name,
144            source_context: declaration.source_context,
145            custom_types: declaration.custom_types,
146            functions_by_name: by_name,
147            functions,
148            constants: declaration.constants,
149            anonymous_functions,
150        });
151    }
152
153    let mut registry_modules = Vec::with_capacity(function_declarations.len());
154    let mut bodies = Vec::with_capacity(function_declarations.len());
155    for declaration in function_declarations {
156        let constants = reserve_constants(declaration.id, declaration.constants)?;
157        let (constant_signatures, constant_bodies) = constants.into_parts();
158        registry_modules.push(ModuleRegistry::new(
159            declaration.module_name,
160            declaration.custom_types,
161            Vec::new(),
162            declaration.functions_by_name,
163            constant_signatures,
164        ));
165        bodies.push(ModuleBodies {
166            id: declaration.id,
167            package: declaration.package,
168            source_context: declaration.source_context,
169            functions: declaration.functions,
170            constants: constant_bodies,
171            anonymous_functions: declaration.anonymous_functions,
172        });
173    }
174
175    let registry = ProgramRegistry::new(registry_modules);
176    let mut functions_to_plan = Vec::with_capacity(bodies.len());
177    for mut module in bodies {
178        let constants =
179            plan_constant_bodies(module.constants, &registry, &mut module.anonymous_functions)?;
180        functions_to_plan.push(ModuleFunctions {
181            id: module.id,
182            package: module.package,
183            source_context: module.source_context,
184            functions: module.functions,
185            constants,
186            anonymous_functions: module.anonymous_functions,
187        });
188    }
189
190    let mut planned_modules = Vec::with_capacity(functions_to_plan.len());
191    for mut module in functions_to_plan {
192        let mut planned_functions = Vec::with_capacity(module.functions.len());
193        for function in module.functions {
194            let context = crate::planner::context::PlanContext::new_in_program(
195                module.id,
196                &registry,
197                &mut module.anonymous_functions,
198            );
199            planned_functions.push(plan_function(function.info, function.function, context)?);
200        }
201        planned_functions.sort_by_key(|function| function.id().index());
202        planned_modules.push(PlannedModule::new(
203            module.id,
204            module.package,
205            crate::plan::module::PlannedModuleParts {
206                module: registry.module_name(module.id).clone(),
207                source_context: module.source_context,
208                custom_types: registry.custom_types(module.id).to_vec(),
209                constants: module.constants,
210                functions: planned_functions,
211                anonymous_functions: module.anonymous_functions.into_functions(),
212            },
213        ));
214    }
215
216    Ok(ModulePlan::from_modules(
217        root,
218        FunctionTemplateId::in_module(root, 0),
219        planned_modules,
220    ))
221}
222
223#[derive(Clone, Copy)]
224enum ModuleRole {
225    Root,
226    Dependency,
227}
228
229struct FunctionTable {
230    by_name: HashMap<EcoString, FunctionInfo>,
231    functions: Vec<FunctionToPlan>,
232    anonymous_functions: AnonymousFunctions,
233}
234
235struct FunctionToPlan {
236    name: EcoString,
237    info: FunctionInfo,
238    function: TypedFunction,
239}
240
241fn function_table(
242    module: ModuleId,
243    functions: &[gleam_compiler_core::ast::TypedFunction],
244    role: ModuleRole,
245) -> Result<FunctionTable, PlanError> {
246    function_table_with_external_types(module, functions, role, &HashSet::new())
247}
248
249fn function_table_with_external_types(
250    module: ModuleId,
251    functions: &[gleam_compiler_core::ast::TypedFunction],
252    role: ModuleRole,
253    external_types: &HashSet<crate::plan::ExternalTypeName>,
254) -> Result<FunctionTable, PlanError> {
255    let mut seeds = Vec::new();
256
257    for function in functions {
258        let name = function_name(function)?;
259        let mut type_parameters = TypeParameterScope::default();
260        let return_shape =
261            function_return_shape_in(&function.return_type, &mut type_parameters, &|name| {
262                external_types.contains(name)
263            });
264        let params = function_params_allowing_labels_in(
265            &function.arguments,
266            &mut type_parameters,
267            &|name| external_types.contains(name),
268        );
269        let scheme = type_parameters.scheme();
270        seeds.push(FunctionSeed {
271            name,
272            definition_span: function.location.into(),
273            function: function.clone(),
274            params,
275            return_shape,
276            scheme,
277            type_parameters,
278        });
279    }
280
281    enum FunctionIndexing {
282        Root { main_index: usize },
283        Dependency,
284    }
285
286    let indexing = match role {
287        ModuleRole::Root => {
288            let main_index = seeds
289                .iter()
290                .position(|seed| seed.name == "main")
291                .ok_or_else(|| PlanError::UnsupportedFunction {
292                    name: "main".into(),
293                    reason: UnsupportedFunctionReason::MissingMain,
294                })?;
295            if !seeds[main_index].params.is_empty() {
296                return Err(PlanError::UnsupportedFunction {
297                    name: "main".into(),
298                    reason: UnsupportedFunctionReason::MainWithArguments,
299                });
300            }
301            FunctionIndexing::Root { main_index }
302        }
303        ModuleRole::Dependency => FunctionIndexing::Dependency,
304    };
305
306    let mut by_name = HashMap::with_capacity(seeds.len());
307    let mut functions_to_plan = Vec::with_capacity(seeds.len());
308    for (source_index, seed) in seeds.into_iter().enumerate() {
309        let local_index = match indexing {
310            FunctionIndexing::Root { main_index } if source_index == main_index => 0,
311            FunctionIndexing::Root { main_index } if source_index < main_index => source_index + 1,
312            FunctionIndexing::Root { .. } | FunctionIndexing::Dependency => source_index,
313        };
314        let info = function_info(module, local_index, &seed);
315        by_name.insert(seed.name.clone(), info.clone());
316        functions_to_plan.push(FunctionToPlan {
317            name: seed.name,
318            info,
319            function: seed.function,
320        });
321    }
322
323    let anonymous_functions = AnonymousFunctions::in_module(module, functions_to_plan.len());
324
325    Ok(FunctionTable {
326        by_name,
327        functions: functions_to_plan,
328        anonymous_functions,
329    })
330}
331
332fn function_info(module: ModuleId, function_index: usize, seed: &FunctionSeed) -> FunctionInfo {
333    FunctionInfo {
334        signature: crate::plan::FunctionTemplateSignature::new(
335            FunctionTemplateId::in_module(module, function_index),
336            seed.scheme.clone(),
337            crate::plan::FunctionShape::new(
338                seed.params
339                    .iter()
340                    .map(|param| param.shape().clone())
341                    .collect(),
342                seed.return_shape.clone(),
343            ),
344        ),
345        type_parameters: seed.type_parameters.clone(),
346        return_shape: seed.return_shape.clone(),
347        params: seed.params.clone(),
348        definition_span: seed.definition_span,
349    }
350}
351
352#[derive(Clone)]
353struct FunctionSeed {
354    name: EcoString,
355    definition_span: crate::plan::SourceSpan,
356    function: TypedFunction,
357    params: Vec<FunctionParam>,
358    return_shape: crate::plan::ValueShape,
359    scheme: crate::plan::TypeScheme,
360    type_parameters: TypeParameterScope,
361}
362
363pub(super) fn function_params_in(
364    function_name: EcoString,
365    arguments: &[gleam_compiler_core::ast::TypedArg],
366    parameters: &mut TypeParameterScope,
367    is_external: &impl Fn(&crate::plan::ExternalTypeName) -> bool,
368) -> Result<Vec<FunctionParam>, PlanError> {
369    if arguments.iter().any(|argument| {
370        matches!(
371            argument.names,
372            ArgNames::NamedLabelled { .. } | ArgNames::LabelledDiscard { .. }
373        )
374    }) {
375        return Err(PlanError::InvalidTypedAst {
376            reason: InvalidTypedAstReason::FunctionShape {
377                name: function_name,
378                reason: InvalidFunctionShapeReason::LabelledArgument,
379            },
380        });
381    }
382
383    Ok(function_params_allowing_labels_in(
384        arguments,
385        parameters,
386        is_external,
387    ))
388}
389
390pub(super) fn discarded_function_params(shapes: &[crate::plan::ValueShape]) -> Vec<FunctionParam> {
391    let mut locals = FunctionParamLocalCounters::default();
392    shapes
393        .iter()
394        .cloned()
395        .map(|shape| {
396            FunctionParam::new(
397                locals.next_value_shape(&shape),
398                shape,
399                ParamBinding::Discard,
400                None,
401            )
402        })
403        .collect()
404}
405
406fn function_return_shape_in(
407    type_: &Type,
408    parameters: &mut TypeParameterScope,
409    is_external: &impl Fn(&crate::plan::ExternalTypeName) -> bool,
410) -> crate::plan::ValueShape {
411    crate::plan::ValueShape::from_gleam_in_with_external(type_, parameters, is_external)
412}
413
414fn function_params_allowing_labels_in(
415    arguments: &[gleam_compiler_core::ast::TypedArg],
416    parameters: &mut TypeParameterScope,
417    is_external: &impl Fn(&crate::plan::ExternalTypeName) -> bool,
418) -> Vec<FunctionParam> {
419    let mut locals = FunctionParamLocalCounters::default();
420
421    arguments
422        .iter()
423        .map(|argument| {
424            let (binding, label) = match &argument.names {
425                ArgNames::Named { name, .. } => (ParamBinding::Named(name.clone()), None),
426                ArgNames::Discard { .. } => (ParamBinding::Discard, None),
427                ArgNames::NamedLabelled { label, name, .. } => {
428                    (ParamBinding::Named(name.clone()), Some(label.clone()))
429                }
430                ArgNames::LabelledDiscard { label, .. } => {
431                    (ParamBinding::Discard, Some(label.clone()))
432                }
433            };
434
435            let shape = crate::plan::ValueShape::from_gleam_in_with_external(
436                &argument.type_,
437                parameters,
438                is_external,
439            );
440            let local = locals.next_value_shape(&shape);
441            FunctionParam::new(local, shape, binding, label)
442        })
443        .collect()
444}
445
446#[derive(Default)]
447struct FunctionParamLocalCounters {
448    next_generic: usize,
449    next_int: usize,
450    next_float: usize,
451    next_string: usize,
452    next_bit_array: usize,
453    next_utf_codepoint: usize,
454    next_custom: usize,
455    next_external: usize,
456    next_bool: usize,
457    next_nil: usize,
458    next_tuple: usize,
459    next_generic_list: usize,
460    next_int_list: usize,
461    next_string_list: usize,
462    next_bit_array_list: usize,
463    next_utf_codepoint_list: usize,
464    next_custom_list: usize,
465    next_external_list: usize,
466    next_float_list: usize,
467    next_bool_list: usize,
468    next_nil_list: usize,
469    next_tuple_list: usize,
470    next_list_list: usize,
471    next_function_list: usize,
472    next_function: FunctionParamFunctionLocalCounters,
473}
474
475#[derive(Default)]
476struct FunctionParamFunctionLocalCounters {
477    next_generic: usize,
478    next_int: usize,
479    next_float: usize,
480    next_string: usize,
481    next_bit_array: usize,
482    next_utf_codepoint: usize,
483    next_custom: usize,
484    next_external: usize,
485    next_bool: usize,
486    next_nil: usize,
487    next_tuple: usize,
488    next_list: usize,
489    next_function: usize,
490}
491
492impl FunctionParamLocalCounters {
493    fn next_value_shape(&mut self, shape: &crate::plan::ValueShape) -> ParamLocal {
494        match shape {
495            crate::plan::ValueShape::Parameter(parameter) => {
496                let local = ParamLocal::generic(crate::plan::GenericLocal::new(
497                    crate::plan::GenericLocalId(self.next_generic),
498                    *parameter,
499                ));
500                self.next_generic += 1;
501                local
502            }
503            crate::plan::ValueShape::Int => {
504                let local = ParamLocal::int(crate::plan::IntLocalId(self.next_int));
505                self.next_int += 1;
506                local
507            }
508            crate::plan::ValueShape::Float => {
509                let local = ParamLocal::float(crate::plan::FloatLocalId(self.next_float));
510                self.next_float += 1;
511                local
512            }
513            crate::plan::ValueShape::String => {
514                let local = ParamLocal::string(crate::plan::StringLocalId(self.next_string));
515                self.next_string += 1;
516                local
517            }
518            crate::plan::ValueShape::BitArray => {
519                let local =
520                    ParamLocal::bit_array(crate::plan::BitArrayLocalId(self.next_bit_array));
521                self.next_bit_array += 1;
522                local
523            }
524            crate::plan::ValueShape::UtfCodepoint => {
525                let local = ParamLocal::utf_codepoint(crate::plan::UtfCodepointLocalId(
526                    self.next_utf_codepoint,
527                ));
528                self.next_utf_codepoint += 1;
529                local
530            }
531            crate::plan::ValueShape::Custom(custom_shape) => {
532                let local = ParamLocal::custom_shape(
533                    crate::plan::CustomLocalId(self.next_custom),
534                    custom_shape.clone(),
535                );
536                self.next_custom += 1;
537                local
538            }
539            crate::plan::ValueShape::External(external_shape) => {
540                let local = ParamLocal::external_shape(
541                    crate::plan::ExternalLocalId(self.next_external),
542                    external_shape.clone(),
543                );
544                self.next_external += 1;
545                local
546            }
547            crate::plan::ValueShape::Bool => {
548                let local = ParamLocal::bool(crate::plan::BoolLocalId(self.next_bool));
549                self.next_bool += 1;
550                local
551            }
552            crate::plan::ValueShape::Nil => {
553                let local = ParamLocal::nil(crate::plan::NilLocalId(self.next_nil));
554                self.next_nil += 1;
555                local
556            }
557            crate::plan::ValueShape::Tuple(elements) => {
558                let local = ParamLocal::tuple(
559                    crate::plan::TupleLocalId(self.next_tuple),
560                    elements
561                        .iter()
562                        .map(crate::plan::ValueShape::value_type)
563                        .collect(),
564                );
565                self.next_tuple += 1;
566                local
567            }
568            crate::plan::ValueShape::List(element_shape) => {
569                let local = match element_shape.as_ref() {
570                    crate::plan::ValueShape::Parameter(parameter) => {
571                        let local = crate::plan::ListLocal::generic(
572                            crate::plan::GenericListLocalId(self.next_generic_list),
573                            *parameter,
574                        );
575                        self.next_generic_list += 1;
576                        local
577                    }
578                    crate::plan::ValueShape::Int => {
579                        let local = crate::plan::ListLocal::int(crate::plan::IntListLocalId(
580                            self.next_int_list,
581                        ));
582                        self.next_int_list += 1;
583                        local
584                    }
585                    crate::plan::ValueShape::String => {
586                        let local = crate::plan::ListLocal::string(crate::plan::StringListLocalId(
587                            self.next_string_list,
588                        ));
589                        self.next_string_list += 1;
590                        local
591                    }
592                    crate::plan::ValueShape::BitArray => {
593                        let local = crate::plan::ListLocal::bit_array(
594                            crate::plan::BitArrayListLocalId(self.next_bit_array_list),
595                        );
596                        self.next_bit_array_list += 1;
597                        local
598                    }
599                    crate::plan::ValueShape::UtfCodepoint => {
600                        let local = crate::plan::ListLocal::utf_codepoint(
601                            crate::plan::UtfCodepointListLocalId(self.next_utf_codepoint_list),
602                        );
603                        self.next_utf_codepoint_list += 1;
604                        local
605                    }
606                    crate::plan::ValueShape::Custom(item_shape) => {
607                        let local = crate::plan::ListLocal::custom(
608                            crate::plan::CustomListLocalId(self.next_custom_list),
609                            item_shape.type_().clone(),
610                        );
611                        self.next_custom_list += 1;
612                        local
613                    }
614                    crate::plan::ValueShape::External(item_shape) => {
615                        let local = crate::plan::ListLocal::external(
616                            crate::plan::ExternalListLocalId(self.next_external_list),
617                            item_shape.type_().clone(),
618                        );
619                        self.next_external_list += 1;
620                        local
621                    }
622                    crate::plan::ValueShape::Float => {
623                        let local = crate::plan::ListLocal::float(crate::plan::FloatListLocalId(
624                            self.next_float_list,
625                        ));
626                        self.next_float_list += 1;
627                        local
628                    }
629                    crate::plan::ValueShape::Bool => {
630                        let local = crate::plan::ListLocal::bool(crate::plan::BoolListLocalId(
631                            self.next_bool_list,
632                        ));
633                        self.next_bool_list += 1;
634                        local
635                    }
636                    crate::plan::ValueShape::Nil => {
637                        let local = crate::plan::ListLocal::nil(crate::plan::NilListLocalId(
638                            self.next_nil_list,
639                        ));
640                        self.next_nil_list += 1;
641                        local
642                    }
643                    crate::plan::ValueShape::Tuple(item_shape) => {
644                        let local = crate::plan::ListLocal::tuple(
645                            crate::plan::TupleListLocalId(self.next_tuple_list),
646                            item_shape
647                                .iter()
648                                .map(crate::plan::ValueShape::value_type)
649                                .collect(),
650                        );
651                        self.next_tuple_list += 1;
652                        local
653                    }
654                    crate::plan::ValueShape::List(item_shape) => {
655                        let local = crate::plan::ListLocal::list(
656                            crate::plan::ListListLocalId(self.next_list_list),
657                            item_shape.value_type(),
658                        );
659                        self.next_list_list += 1;
660                        local
661                    }
662                    crate::plan::ValueShape::Function(item_shape) => {
663                        let local = crate::plan::ListLocal::function(
664                            crate::plan::FunctionListLocalId(self.next_function_list),
665                            item_shape.type_(),
666                        );
667                        self.next_function_list += 1;
668                        local
669                    }
670                };
671                ParamLocal::list(local)
672            }
673            crate::plan::ValueShape::Function(function_shape) => {
674                self.next_function.next_shape(function_shape)
675            }
676        }
677    }
678}
679
680impl FunctionParamFunctionLocalCounters {
681    fn next_shape(&mut self, shape: &crate::plan::FunctionShape) -> ParamLocal {
682        let type_ = shape.type_();
683        match shape.return_shape() {
684            crate::plan::ValueShape::Parameter(parameter) => {
685                let local = ParamLocal::generic_function(crate::plan::GenericFunctionLocal::new(
686                    crate::plan::GenericFunctionLocalId(self.next_generic),
687                    crate::plan::GenericFunctionType::new(
688                        shape.argument_shapes().to_vec(),
689                        *parameter,
690                    ),
691                ));
692                self.next_generic += 1;
693                local
694            }
695            crate::plan::ValueShape::Int => {
696                let local =
697                    ParamLocal::int_function(IntFunctionLocalId(self.next_int), type_.clone());
698                self.next_int += 1;
699                local
700            }
701            crate::plan::ValueShape::Float => {
702                let local = ParamLocal::float_function(
703                    crate::plan::FloatFunctionLocalId(self.next_float),
704                    type_.clone(),
705                );
706                self.next_float += 1;
707                local
708            }
709            crate::plan::ValueShape::String => {
710                let local = ParamLocal::string_function(
711                    crate::plan::StringFunctionLocalId(self.next_string),
712                    type_.clone(),
713                );
714                self.next_string += 1;
715                local
716            }
717            crate::plan::ValueShape::BitArray => {
718                let local = ParamLocal::bit_array_function(
719                    crate::plan::BitArrayFunctionLocalId(self.next_bit_array),
720                    type_.clone(),
721                );
722                self.next_bit_array += 1;
723                local
724            }
725            crate::plan::ValueShape::UtfCodepoint => {
726                let local = ParamLocal::utf_codepoint_function(
727                    crate::plan::UtfCodepointFunctionLocalId(self.next_utf_codepoint),
728                    type_.clone(),
729                );
730                self.next_utf_codepoint += 1;
731                local
732            }
733            crate::plan::ValueShape::Custom(return_shape) => {
734                let local = ParamLocal::custom_function(crate::plan::CustomFunctionLocal::new(
735                    crate::plan::CustomFunctionLocalId(self.next_custom),
736                    crate::plan::CustomFunctionType::from_shapes(
737                        shape.argument_shapes().to_vec(),
738                        return_shape.clone(),
739                    ),
740                ));
741                self.next_custom += 1;
742                local
743            }
744            crate::plan::ValueShape::External(return_shape) => {
745                let local = ParamLocal::external_function(crate::plan::ExternalFunctionLocal::new(
746                    crate::plan::ExternalFunctionLocalId(self.next_external),
747                    crate::plan::ExternalFunctionType::from_shapes(
748                        shape.argument_shapes().to_vec(),
749                        return_shape.clone(),
750                    ),
751                ));
752                self.next_external += 1;
753                local
754            }
755            crate::plan::ValueShape::Bool => {
756                let local = ParamLocal::bool_function(
757                    crate::plan::BoolFunctionLocalId(self.next_bool),
758                    type_.clone(),
759                );
760                self.next_bool += 1;
761                local
762            }
763            crate::plan::ValueShape::Nil => {
764                let local = ParamLocal::nil_function(
765                    crate::plan::NilFunctionLocalId(self.next_nil),
766                    type_.clone(),
767                );
768                self.next_nil += 1;
769                local
770            }
771            crate::plan::ValueShape::Tuple(_) => {
772                let local = ParamLocal::tuple_function(
773                    crate::plan::TupleFunctionLocalId(self.next_tuple),
774                    type_.clone(),
775                );
776                self.next_tuple += 1;
777                local
778            }
779            crate::plan::ValueShape::List(item_shape) => {
780                let local =
781                    ParamLocal::list_function(crate::plan::ListFunctionLocal::from_item_type(
782                        self.next_list,
783                        type_.clone(),
784                        item_shape.value_type(),
785                    ));
786                self.next_list += 1;
787                local
788            }
789            crate::plan::ValueShape::Function(return_shape) => {
790                let local = ParamLocal::function_function(crate::plan::FunctionFunctionLocal::new(
791                    FunctionFunctionLocalId(self.next_function),
792                    crate::plan::FunctionFunctionType::from_shapes(
793                        shape.argument_shapes().to_vec(),
794                        return_shape.as_ref().clone(),
795                    ),
796                ));
797                self.next_function += 1;
798                local
799            }
800        }
801    }
802}
803
804#[cfg(test)]
805mod tests {
806    use super::{plan_module, plan_program};
807    use crate::frontend::{
808        ModuleSource, PackageSource, compile_typed_package_program, compile_typed_program,
809    };
810    use crate::plan::module::{ReturnBodyKind, ReturnExprKind};
811    use crate::plan::{
812        BitArrayListLocalId, BoolListLocalId, ConstantTemplate, ConstantTemplateId,
813        ConstantTemplateSignature, ConstantTemplates, ConstantValue, CustomConstructorDefinition,
814        CustomFieldDefinition, CustomLocalId, CustomType, CustomTypeDefinition, CustomTypeName,
815        CustomTypePublicity, CustomTypeTemplate, Expr, ExprKind, FloatListLocalId,
816        FunctionExprKind, FunctionFunctionId, FunctionListLocalId, FunctionTemplateId,
817        FunctionType, GenericExpr, GenericFunctionLocal, GenericFunctionLocalId,
818        GenericFunctionType, GenericListLocalId, GenericLocal, GenericLocalId, IntExprKind,
819        IntFunctionExprKind, IntFunctionFunctionId, IntFunctionId, IntListLocalId, IntLocalId,
820        ListListLocalId, ListLocal, LocalId, ModuleId, NilListLocalId, PanicExpr, PanicSite, Param,
821        ParamLocal, ReturnBody, ReturnExpr, RuntimeFunctionId, SourceSpan, StringListLocalId,
822        TupleExprKind, TupleListLocalId, TypeParameterId, TypeScheme, ValueShape, ValueType,
823    };
824    use crate::planner::dsl::{
825        call_int_at, call_int_returning_function_at, function, function_ref, host_call_site, int,
826        int_arg, int_function_closure, int_return_tail_call_at, local_int, module, string,
827        string_function_ref,
828    };
829    use crate::planner::support::{compile, expect_plan_error};
830    use crate::planner::{
831        InvalidFunctionShapeReason, InvalidTypedAstReason, PlanError, UnsupportedFunctionReason,
832    };
833    use gleam_compiler_core::type_;
834
835    #[test]
836    fn plan_program_owns_dependency_first_modules_and_a_root_entry() {
837        let typed = compile_typed_program(
838            "root",
839            [
840                ModuleSource::new(
841                    "alpha",
842                    "support.gleam",
843                    r#"
844pub const answer = 1
845
846pub fn main(value: Int) {
847  value
848}
849"#,
850                ),
851                ModuleSource::new(
852                    "root",
853                    "main.gleam",
854                    r#"
855pub const answer = 2
856
857pub fn main() {
858  answer
859}
860"#,
861                ),
862            ],
863        )
864        .expect("program should compile");
865        let plan = plan_program(typed).expect("program should plan");
866
867        assert_eq!(plan.root(), crate::plan::ModuleId::new(1));
868        assert_eq!(plan.module(), "root");
869        assert_eq!(plan.entry().module(), plan.root());
870        assert_eq!(plan.entry().index(), 0);
871        assert_eq!(
872            plan.modules()
873                .iter()
874                .map(|module| module.module().as_str())
875                .collect::<Vec<_>>(),
876            ["alpha", "root"],
877        );
878        assert_eq!(plan.modules()[0].id(), crate::plan::ModuleId::new(0));
879        assert_eq!(plan.modules()[1].id(), crate::plan::ModuleId::new(1));
880        assert_eq!(plan.modules()[0].package(), "geam");
881        assert_eq!(plan.modules()[1].package(), "geam");
882        assert_eq!(
883            plan.modules()[0].functions()[0].id().module(),
884            crate::plan::ModuleId::new(0),
885        );
886        assert_eq!(
887            plan.modules()[1].functions()[0].id().module(),
888            crate::plan::ModuleId::new(1),
889        );
890        assert_eq!(
891            plan.modules()[0].constants()[0].id().module(),
892            crate::plan::ModuleId::new(0),
893        );
894        assert_eq!(
895            plan.modules()[1].constants()[0].id().module(),
896            crate::plan::ModuleId::new(1),
897        );
898        assert_eq!(plan.modules()[0].functions()[0].params().len(), 1);
899        assert_eq!(
900            plan.source_context().map(|context| context.source()),
901            Some(
902                r#"
903pub const answer = 2
904
905pub fn main() {
906  answer
907}
908"#,
909            )
910        );
911    }
912
913    #[test]
914    fn plan_program_preserves_cross_package_module_and_custom_type_ownership() {
915        let typed = compile_typed_package_program(
916            "application",
917            "main",
918            [
919                PackageSource::new(
920                    "application",
921                    ["library"],
922                    [ModuleSource::new(
923                        "main",
924                        "main.gleam",
925                        r#"
926import support.{Boxed, boxed}
927
928pub fn main() {
929  boxed(42)
930}
931"#,
932                    )],
933                ),
934                PackageSource::new(
935                    "library",
936                    Vec::<ecow::EcoString>::new(),
937                    [ModuleSource::new(
938                        "support",
939                        "support.gleam",
940                        r#"
941pub type Boxed(value) {
942  Boxed(value)
943}
944
945pub fn boxed(value) {
946  Boxed(value)
947}
948"#,
949                    )],
950                ),
951            ],
952        )
953        .expect("package program should compile");
954        let plan = plan_program(typed).expect("package program should plan");
955
956        assert_eq!(
957            plan.modules()
958                .iter()
959                .map(|module| (module.package().as_str(), module.module().as_str()))
960                .collect::<Vec<_>>(),
961            [("library", "support"), ("application", "main")],
962        );
963        let custom_name = plan.modules()[0].custom_types()[0].name();
964        assert_eq!(custom_name.package(), "library");
965        assert_eq!(custom_name.module(), "support");
966        assert_eq!(custom_name.name(), "Boxed");
967        assert_eq!(plan.root(), ModuleId::new(1));
968        assert_eq!(
969            plan.entry(),
970            FunctionTemplateId::in_module(ModuleId::new(1), 0)
971        );
972    }
973
974    #[test]
975    fn plan_program_validates_every_dependency_body() {
976        let typed = compile_typed_program(
977            "main",
978            [
979                ModuleSource::new("main", "main.gleam", "pub fn main() { 1 }"),
980                ModuleSource::new(
981                    "support",
982                    "support.gleam",
983                    "pub fn unsupported() { <<1:native>> }",
984                ),
985            ],
986        )
987        .expect("program should compile");
988
989        assert_eq!(
990            plan_program(typed),
991            Err(PlanError::UnsupportedBitArraySegment {
992                reason: crate::planner::UnsupportedBitArraySegmentReason::NativeEndianness,
993            }),
994        );
995    }
996
997    #[test]
998    fn plan_program_registries_keep_same_named_module_items_distinct() {
999        let typed = compile_typed_program(
1000            "root",
1001            [
1002                ModuleSource::new(
1003                    "alpha",
1004                    "alpha.gleam",
1005                    r#"
1006pub type Box {
1007  Box(Int)
1008}
1009
1010pub const answer = 1
1011
1012fn identity(value: Int) {
1013  value
1014}
1015
1016pub fn make() {
1017  Box(identity(answer))
1018}
1019"#,
1020                ),
1021                ModuleSource::new(
1022                    "root",
1023                    "root.gleam",
1024                    r#"
1025pub type Box {
1026  Box(Int)
1027}
1028
1029pub const answer = 2
1030
1031fn identity(value: Int) {
1032  value
1033}
1034
1035pub fn main() {
1036  Box(identity(answer))
1037}
1038"#,
1039                ),
1040            ],
1041        )
1042        .expect("program should compile");
1043        let plan = plan_program(typed).expect("same-named declarations should plan");
1044
1045        let alpha = &plan.modules()[0];
1046        let root = &plan.modules()[1];
1047        assert_eq!(alpha.custom_types()[0].name().module(), "alpha");
1048        assert_eq!(root.custom_types()[0].name().module(), "root");
1049        assert_eq!(alpha.constants()[0].id().module(), alpha.id());
1050        assert_eq!(root.constants()[0].id().module(), root.id());
1051        assert_eq!(
1052            alpha
1053                .functions()
1054                .iter()
1055                .map(|function| function.name().as_str())
1056                .collect::<Vec<_>>(),
1057            ["identity", "make"],
1058        );
1059        assert_eq!(
1060            root.functions()
1061                .iter()
1062                .map(|function| function.name().as_str())
1063                .collect::<Vec<_>>(),
1064            ["main", "identity"],
1065        );
1066        assert_eq!(alpha.functions()[0].id().module(), alpha.id());
1067        assert_eq!(root.functions()[1].id().module(), root.id());
1068    }
1069
1070    #[test]
1071    fn plan_program_resolves_qualified_and_unqualified_imports_to_dependency_ids() {
1072        let dependency_source = r#"
1073pub const answer = 42
1074
1075pub fn identity(value: Int) {
1076  value
1077}
1078"#;
1079        let main_sources = [
1080            r#"
1081import support
1082
1083pub fn main() {
1084  #(
1085    support.answer,
1086    support.identity(1),
1087    support.identity,
1088  )
1089}
1090"#,
1091            r#"
1092import support.{answer, identity}
1093
1094pub fn main() {
1095  #(
1096    answer,
1097    identity(1),
1098    identity,
1099  )
1100}
1101"#,
1102        ];
1103
1104        for main_source in main_sources {
1105            let typed = compile_typed_program(
1106                "main",
1107                [
1108                    ModuleSource::new("support", "support.gleam", dependency_source),
1109                    ModuleSource::new("main", "main.gleam", main_source),
1110                ],
1111            )
1112            .expect("imported references should compile");
1113            let plan = plan_program(typed).expect("imported references should plan");
1114            let dependency = ModuleId::new(0);
1115            let elements = imported_tuple_elements(&plan);
1116
1117            assert_eq!(imported_constant_module(&elements[0]), dependency);
1118            assert_eq!(
1119                imported_call_template(&elements[1]),
1120                FunctionTemplateId::in_module(dependency, 0),
1121            );
1122            assert_eq!(
1123                imported_function_template(&elements[2]),
1124                FunctionTemplateId::in_module(dependency, 0),
1125            );
1126        }
1127    }
1128
1129    #[test]
1130    #[should_panic(expected = "main should return a tuple")]
1131    fn imported_tuple_elements_rejects_non_tuple_returns() {
1132        let plan = plan_module(compile("pub fn main() { 1 }")).expect("source should plan");
1133
1134        imported_tuple_elements(&plan);
1135    }
1136
1137    #[test]
1138    #[should_panic(expected = "main should directly return its tuple")]
1139    fn imported_tuple_elements_rejects_control_flow_bodies() {
1140        let plan = plan_module(compile(
1141            r#"
1142pub fn main() {
1143  case True {
1144    True -> #(1)
1145    False -> #(2)
1146  }
1147}
1148"#,
1149        ))
1150        .expect("source should plan");
1151
1152        imported_tuple_elements(&plan);
1153    }
1154
1155    #[test]
1156    #[should_panic(expected = "main should construct its tuple")]
1157    fn imported_tuple_elements_rejects_tuple_locals() {
1158        let plan = plan_module(compile(
1159            r#"
1160pub fn main() {
1161  let value = #(1)
1162  value
1163}
1164"#,
1165        ))
1166        .expect("source should plan");
1167
1168        imported_tuple_elements(&plan);
1169    }
1170
1171    #[test]
1172    #[should_panic(expected = "imported constant should be an Int expression")]
1173    fn imported_constant_module_rejects_other_families() {
1174        imported_constant_module(&Expr::from(string("wrong")));
1175    }
1176
1177    #[test]
1178    #[should_panic(expected = "imported constant should retain a constant reference")]
1179    fn imported_constant_module_rejects_int_literals() {
1180        imported_constant_module(&Expr::from(int(1)));
1181    }
1182
1183    #[test]
1184    #[should_panic(expected = "imported call should be an Int expression")]
1185    fn imported_call_template_rejects_other_families() {
1186        imported_call_template(&Expr::from(string("wrong")));
1187    }
1188
1189    #[test]
1190    #[should_panic(expected = "imported function call should remain direct")]
1191    fn imported_call_template_rejects_int_literals() {
1192        imported_call_template(&Expr::from(int(1)));
1193    }
1194
1195    #[test]
1196    #[should_panic(expected = "imported function value should be a function expression")]
1197    fn imported_function_template_rejects_non_functions() {
1198        imported_function_template(&Expr::from(int(1)));
1199    }
1200
1201    #[test]
1202    #[should_panic(expected = "imported function should return Int")]
1203    fn imported_function_template_rejects_other_return_families() {
1204        imported_function_template(&Expr::from(string_function_ref(
1205            0,
1206            Vec::<ParamLocal>::new(),
1207        )));
1208    }
1209
1210    #[test]
1211    #[should_panic(expected = "imported function value should remain a reference")]
1212    fn imported_function_template_rejects_closures() {
1213        imported_function_template(&Expr::from(int_function_closure(
1214            0,
1215            Vec::<ParamLocal>::new(),
1216            Vec::<crate::plan::CaptureArg>::new(),
1217        )));
1218    }
1219
1220    #[test]
1221    fn plan_program_validates_every_dependency_constant_body() {
1222        let typed = compile_typed_program(
1223            "main",
1224            [
1225                ModuleSource::new("main", "main.gleam", "pub fn main() { 1 }"),
1226                ModuleSource::new(
1227                    "support",
1228                    "support.gleam",
1229                    "const unsupported = <<1:native>>",
1230                ),
1231            ],
1232        )
1233        .expect("program should compile");
1234
1235        assert_eq!(
1236            plan_program(typed),
1237            Err(PlanError::UnsupportedBitArraySegment {
1238                reason: crate::planner::UnsupportedBitArraySegmentReason::NativeEndianness,
1239            }),
1240        );
1241    }
1242
1243    #[test]
1244    fn plan_integer_return() {
1245        let actual = plan_module(compile(
1246            r#"
1247pub fn main() {
1248  1
1249}
1250"#,
1251        ))
1252        .expect("source should plan");
1253        let expected = module("main", function("main", int(1)), []);
1254
1255        assert_eq!(actual, expected);
1256    }
1257
1258    #[test]
1259    fn plan_functions_before_and_after_main() {
1260        let source = r#"
1261fn before() {
1262  1
1263}
1264
1265pub fn main() {
1266  before() + after()
1267}
1268
1269fn after() {
1270  2
1271}
1272"#;
1273        let actual = plan_module(compile(source)).expect("source should plan");
1274        let expected = module(
1275            "main",
1276            function(
1277                "main",
1278                call_int_at(1, Vec::new(), host_call_site(source, "main", "before()")).add_int(
1279                    call_int_at(2, Vec::new(), host_call_site(source, "main", "after()")),
1280                ),
1281            ),
1282            [function("before", int(1)), function("after", int(2))],
1283        );
1284
1285        assert_eq!(actual, expected);
1286    }
1287
1288    #[test]
1289    fn plan_type_alias_function_signature_as_underlying_type() {
1290        let source = r#"
1291pub type UserId =
1292  Int
1293
1294fn identity(value: UserId) -> UserId {
1295  value
1296}
1297
1298pub fn main() {
1299  identity(41)
1300}
1301"#;
1302        let actual = plan_module(compile(source)).expect("source should plan");
1303        let expected = module(
1304            "main",
1305            function(
1306                "main",
1307                int_return_tail_call_at(
1308                    1,
1309                    [int_arg(int(41))],
1310                    host_call_site(source, "main", "identity(41)"),
1311                ),
1312            ),
1313            [function("identity", local_int(0, "value")).param_int(0, "value")],
1314        );
1315
1316        assert_eq!(actual, expected);
1317    }
1318
1319    #[test]
1320    fn plan_constant_definition() {
1321        let actual = plan_module(compile(
1322            r#"
1323const answer = 42
1324
1325pub fn main() {
1326  answer
1327}
1328"#,
1329        ))
1330        .expect("source should plan");
1331        let signature =
1332            ConstantTemplateSignature::int(ConstantTemplateId::new(0), 0, TypeScheme::new(0));
1333        let instantiation = signature
1334            .try_instantiate(Vec::new())
1335            .expect("a monomorphic constant should instantiate");
1336        let constants = ConstantTemplates::from_entries(vec![(
1337            ConstantTemplate::new(signature, "answer".into()),
1338            ConstantValue::int(42.into()),
1339        )]);
1340        let expected = module(
1341            "main",
1342            function(
1343                "main",
1344                crate::plan::IntReturn::expr(
1345                    ConstantTemplates::reference(instantiation)
1346                        .into_int()
1347                        .expect("an Int constant reference should retain its family"),
1348                ),
1349            ),
1350            [],
1351        )
1352        .with_constants(constants);
1353
1354        assert_eq!(actual, expected);
1355    }
1356
1357    #[test]
1358    fn reject_profile_missing_main_function() {
1359        assert_eq!(
1360            expect_plan_error(
1361                r#"
1362pub fn other() {
1363  1
1364}
1365"#,
1366            ),
1367            PlanError::UnsupportedFunction {
1368                name: "main".into(),
1369                reason: UnsupportedFunctionReason::MissingMain,
1370            },
1371        );
1372    }
1373
1374    #[test]
1375    fn reject_profile_main_function_with_arguments() {
1376        assert_eq!(
1377            expect_plan_error(
1378                r#"
1379pub fn main(value: Int) {
1380  value
1381}
1382"#,
1383            ),
1384            PlanError::UnsupportedFunction {
1385                name: "main".into(),
1386                reason: UnsupportedFunctionReason::MainWithArguments,
1387            },
1388        );
1389    }
1390
1391    #[test]
1392    fn reject_margin_function_table_name_shape() {
1393        let mut module = compile(
1394            r#"
1395pub fn main() {
1396  1
1397}
1398"#,
1399        );
1400        module.definitions.functions[0].name = None;
1401
1402        assert_eq!(
1403            super::function_table(
1404                crate::plan::ModuleId::root(),
1405                &module.definitions.functions,
1406                super::ModuleRole::Root,
1407            )
1408            .err(),
1409            Some(PlanError::InvalidTypedAst {
1410                reason: InvalidTypedAstReason::FunctionShape {
1411                    name: "<anonymous>".into(),
1412                    reason: InvalidFunctionShapeReason::Anonymous,
1413                },
1414            }),
1415        );
1416    }
1417
1418    #[test]
1419    fn plan_empty_source_body_as_parametric_generated_todo() {
1420        let actual = plan_module(compile(
1421            r#"
1422pub fn main() {
1423}
1424"#,
1425        ))
1426        .expect("source should plan");
1427        assert_eq!(
1428            actual.main_function().return_(),
1429            &crate::plan::ReturnExpr::generic_body(
1430                TypeParameterId(0),
1431                ReturnBody::expr(GenericExpr::panic(
1432                    TypeParameterId(0),
1433                    PanicExpr::empty_function_at(PanicSite::new(
1434                        "main".into(),
1435                        "main".into(),
1436                        SourceSpan::new(1, 14),
1437                    )),
1438                )),
1439            ),
1440        );
1441    }
1442
1443    #[test]
1444    fn function_return_type_preserves_custom_non_source_stop_shapes() {
1445        let result_type = result_type();
1446        assert_eq!(
1447            ValueShape::from_gleam(type_::result(type_::int(), type_::nil()).as_ref())
1448                .map(|shape| shape.value_type()),
1449            Some(result_type.clone()),
1450        );
1451
1452        assert_eq!(
1453            ValueShape::from_gleam(type_::result(type_::int(), type_::nil()).as_ref())
1454                .map(|shape| shape.value_type()),
1455            Some(result_type),
1456        );
1457    }
1458
1459    #[test]
1460    fn preserve_unbound_return_type_as_parameter() {
1461        let mut parameters = super::TypeParameterScope::default();
1462        assert_eq!(
1463            super::function_return_shape_in(
1464                type_::unbound_var(0).as_ref(),
1465                &mut parameters,
1466                &|_| false,
1467            )
1468            .value_type(),
1469            ValueType::Parameter(TypeParameterId(0)),
1470        );
1471        assert_eq!(parameters.scheme(), TypeScheme::new(1));
1472    }
1473
1474    #[test]
1475    fn parametric_function_return_shapes_preserve_inferred_results() {
1476        let mut concrete_parameters = super::TypeParameterScope::default();
1477        assert_eq!(
1478            super::function_return_shape_in(
1479                type_::int().as_ref(),
1480                &mut concrete_parameters,
1481                &|_| false,
1482            ),
1483            ValueShape::Int,
1484        );
1485
1486        let mut source_stop_parameters = super::TypeParameterScope::default();
1487        assert_eq!(
1488            super::function_return_shape_in(
1489                type_::unbound_var(41).as_ref(),
1490                &mut source_stop_parameters,
1491                &|_| false,
1492            ),
1493            ValueShape::Parameter(TypeParameterId(0)),
1494        );
1495        assert_eq!(source_stop_parameters.scheme(), TypeScheme::new(1));
1496
1497        let mut inferred_parameters = super::TypeParameterScope::default();
1498        assert_eq!(
1499            super::function_return_shape_in(
1500                type_::unbound_var(41).as_ref(),
1501                &mut inferred_parameters,
1502                &|_| false,
1503            ),
1504            ValueShape::Parameter(TypeParameterId(0)),
1505        );
1506        assert_eq!(inferred_parameters.scheme(), TypeScheme::new(1));
1507    }
1508
1509    #[test]
1510    fn preserve_generic_return_without_template_scope() {
1511        let mut parameters = super::TypeParameterScope::default();
1512        assert_eq!(
1513            super::function_return_shape_in(
1514                type_::generic_var(0).as_ref(),
1515                &mut parameters,
1516                &|_| false,
1517            )
1518            .value_type(),
1519            ValueType::Parameter(TypeParameterId(0)),
1520        );
1521        assert_eq!(parameters.scheme(), TypeScheme::new(1));
1522    }
1523
1524    #[test]
1525    fn plan_source_stop_generic_function_as_template() {
1526        let actual = plan_module(compile(
1527            r#"
1528fn fail() -> a {
1529  panic
1530}
1531
1532pub fn main() {
1533  1
1534}
1535"#,
1536        ))
1537        .expect("generic source-stop function should plan as a template");
1538        let fail = &actual.functions()[0];
1539        let parameter = TypeParameterId(0);
1540        assert_eq!(fail.scheme(), &TypeScheme::new(1));
1541        assert_eq!(
1542            fail.return_(),
1543            &ReturnExpr::generic_body(
1544                parameter,
1545                ReturnBody::expr(GenericExpr::panic(
1546                    parameter,
1547                    PanicExpr::panic_at(
1548                        None,
1549                        PanicSite::new("main".into(), "fail".into(), SourceSpan::new(20, 25),),
1550                    ),
1551                )),
1552            ),
1553        );
1554    }
1555
1556    #[test]
1557    fn plan_representable_unresolved_generic_main_as_specialization_root() {
1558        let actual = plan_module(compile(
1559            r#"
1560pub fn main() {
1561  []
1562}
1563"#,
1564        ))
1565        .expect("an empty generic list has a runtime representation");
1566
1567        assert_eq!(actual.main_function().scheme(), &TypeScheme::new(1));
1568        assert_eq!(
1569            actual.main_function().signature().shape().return_shape(),
1570            &ValueShape::List(Box::new(ValueShape::Parameter(TypeParameterId(0)))),
1571        );
1572    }
1573
1574    #[test]
1575    fn function_return_type_preserves_explicit_custom_source_stop_shapes() {
1576        let result_type = result_type();
1577        let main = compile(
1578            r#"
1579pub fn main() -> Result(Int, Nil) {
1580  panic
1581}
1582"#,
1583        );
1584        assert_eq!(
1585            ValueShape::from_gleam(main.definitions.functions[0].return_type.as_ref())
1586                .map(|shape| shape.value_type()),
1587            Some(result_type.clone()),
1588        );
1589
1590        let helper = compile(
1591            r#"
1592pub fn main() {
1593  1
1594}
1595
1596fn helper() -> Result(Int, Nil) {
1597  panic
1598}
1599"#,
1600        );
1601        let helper = &helper.definitions.functions[1];
1602        assert_eq!(
1603            ValueShape::from_gleam(helper.return_type.as_ref()).map(|shape| shape.value_type()),
1604            Some(result_type),
1605        );
1606    }
1607
1608    #[test]
1609    fn plan_custom_returning_functions_before_and_after_main() {
1610        let actual = plan_module(compile(
1611            r#"
1612fn before() -> Result(Int, Nil) {
1613  Ok(1)
1614}
1615
1616pub fn main() {
1617  1
1618}
1619
1620fn after() -> Result(Int, Nil) {
1621  Ok(2)
1622}
1623"#,
1624        ))
1625        .expect("concrete custom return types should plan");
1626        let functions = actual
1627            .functions()
1628            .iter()
1629            .map(|function| {
1630                (
1631                    function.name().clone(),
1632                    function.id(),
1633                    function.return_().value_type(),
1634                )
1635            })
1636            .collect::<Vec<_>>();
1637
1638        assert_eq!(
1639            functions,
1640            vec![
1641                (
1642                    "before".into(),
1643                    crate::plan::FunctionTemplateId::new(1),
1644                    result_type(),
1645                ),
1646                (
1647                    "after".into(),
1648                    crate::plan::FunctionTemplateId::new(2),
1649                    result_type(),
1650                ),
1651            ],
1652        );
1653    }
1654
1655    #[test]
1656    fn reject_profile_function_body_before_main() {
1657        assert_eq!(
1658            expect_plan_error(
1659                r#"
1660fn helper() -> Int {
1661  <<1:native>>
1662  1
1663}
1664
1665pub fn main() {
1666  1
1667}
1668"#,
1669            ),
1670            PlanError::UnsupportedBitArraySegment {
1671                reason: crate::planner::UnsupportedBitArraySegmentReason::NativeEndianness,
1672            },
1673        );
1674    }
1675
1676    #[test]
1677    fn reject_profile_function_body_after_main() {
1678        assert_eq!(
1679            expect_plan_error(
1680                r#"
1681pub fn main() {
1682  1
1683}
1684
1685fn helper() -> Int {
1686  <<1:native>>
1687  1
1688}
1689"#,
1690            ),
1691            PlanError::UnsupportedBitArraySegment {
1692                reason: crate::planner::UnsupportedBitArraySegmentReason::NativeEndianness,
1693            },
1694        );
1695    }
1696
1697    #[test]
1698    fn plan_function_returning_function_after_main_reference() {
1699        let actual = plan_module(compile(
1700            r#"
1701pub fn main() {
1702  get
1703  1
1704}
1705
1706fn add_one(value: Int) {
1707  value + 1
1708}
1709
1710fn get() {
1711  add_one
1712}
1713"#,
1714        ))
1715        .expect("source should plan");
1716        let returned_function_type = FunctionType::new(vec![ValueType::Int], ValueType::Int);
1717        let expected = module(
1718            "main",
1719            function("main", int(1)).evaluate(function_ref(
1720                RuntimeFunctionId::Function {
1721                    id: FunctionFunctionId::Int(IntFunctionFunctionId(2)),
1722                    return_type: returned_function_type.clone(),
1723                },
1724                Vec::<ParamLocal>::new(),
1725            )),
1726            [
1727                function("add_one", local_int(0, "value").add_int(int(1))).param_int(0, "value"),
1728                function(
1729                    "get",
1730                    function_ref(
1731                        RuntimeFunctionId::Int(IntFunctionId(1)),
1732                        [LocalId::Int(IntLocalId(0))],
1733                    ),
1734                ),
1735            ],
1736        );
1737
1738        assert_eq!(actual, expected);
1739    }
1740
1741    #[test]
1742    fn plan_function_returning_function_after_main_call() {
1743        let source = r#"
1744pub fn main() {
1745  get()
1746  1
1747}
1748
1749fn add_one(value: Int) {
1750  value + 1
1751}
1752
1753fn get() {
1754  add_one
1755}
1756"#;
1757        let actual = plan_module(compile(source)).expect("source should plan");
1758        let returned_function_type = FunctionType::new(vec![ValueType::Int], ValueType::Int);
1759        let expected = module(
1760            "main",
1761            function("main", int(1)).evaluate(call_int_returning_function_at(
1762                2,
1763                [],
1764                returned_function_type,
1765                host_call_site(source, "main", "get()"),
1766            )),
1767            [
1768                function("add_one", local_int(0, "value").add_int(int(1))).param_int(0, "value"),
1769                function(
1770                    "get",
1771                    function_ref(
1772                        RuntimeFunctionId::Int(IntFunctionId(1)),
1773                        [LocalId::Int(IntLocalId(0))],
1774                    ),
1775                ),
1776            ],
1777        );
1778
1779        assert_eq!(actual, expected);
1780    }
1781
1782    #[test]
1783    fn plan_function_argument_with_function_argument_type() {
1784        let actual = plan_module(compile(
1785            r#"
1786pub fn main() {
1787  1
1788}
1789
1790fn higher(callback: fn(fn(Int) -> Int) -> Int) {
1791  1
1792}
1793
1794fn getter(callback: fn() -> fn(Int) -> Int) {
1795  1
1796}
1797
1798fn tuple_getter(callback: fn(#(Int)) -> #(String)) {
1799  1
1800}
1801"#,
1802        ))
1803        .expect("source should plan");
1804        let returned_function_type = FunctionType::new(vec![ValueType::Int], ValueType::Int);
1805        let expected = module(
1806            "main",
1807            function("main", int(1)),
1808            [
1809                function("higher", int(1)).param_int_function(
1810                    0,
1811                    "callback",
1812                    [ValueType::Function(Box::new(
1813                        returned_function_type.clone(),
1814                    ))],
1815                ),
1816                function("getter", int(1)).param_function_function(
1817                    0,
1818                    "callback",
1819                    crate::plan::FunctionFunctionType::new(Vec::new(), returned_function_type),
1820                ),
1821                function("tuple_getter", int(1)).param_tuple_function(
1822                    0,
1823                    "callback",
1824                    [ValueType::Tuple(vec![ValueType::Int])],
1825                    [ValueType::String],
1826                ),
1827            ],
1828        );
1829
1830        assert_eq!(actual, expected);
1831    }
1832
1833    #[test]
1834    fn plan_discard_function_argument_slots() {
1835        let source = r#"
1836fn pick(_: Int, value: Int) {
1837  value
1838}
1839
1840pub fn main() {
1841  pick(1, 42)
1842}
1843"#;
1844        let actual = plan_module(compile(source)).expect("source should plan");
1845        let expected = module(
1846            "main",
1847            function(
1848                "main",
1849                int_return_tail_call_at(
1850                    1,
1851                    [int_arg(int(1)), int_arg(int(42))],
1852                    host_call_site(source, "main", "pick(1, 42)"),
1853                ),
1854            ),
1855            [function("pick", local_int(1, "value"))
1856                .discard_int_param(0)
1857                .param_int(1, "value")],
1858        );
1859
1860        assert_eq!(actual, expected);
1861    }
1862
1863    #[test]
1864    fn plan_custom_function_argument_type() {
1865        let actual = plan_module(compile(
1866            r#"
1867pub fn main() {
1868  1
1869}
1870
1871fn count(values: Result(Int, Nil)) {
1872  1
1873}
1874"#,
1875        ))
1876        .expect("concrete custom arguments should plan");
1877        assert_eq!(
1878            actual.functions()[0].params(),
1879            &[Param::named(
1880                ParamLocal::custom(CustomLocalId(0), result_custom_type()),
1881                "values".into(),
1882            )],
1883        );
1884    }
1885
1886    #[test]
1887    fn reject_margin_custom_function_argument_with_mismatched_generic_return() {
1888        let mut module = compile(
1889            r#"
1890fn count(value: Int) { value }
1891pub fn main() { count(1) }
1892"#,
1893        );
1894        module.definitions.functions[0].arguments[0].type_ = type_::generic_var(0);
1895
1896        assert_eq!(
1897            plan_module(module),
1898            Err(PlanError::InvalidTypedAst {
1899                reason: InvalidTypedAstReason::FunctionShape {
1900                    name: "count".into(),
1901                    reason: InvalidFunctionShapeReason::ReturnTypeMismatch,
1902                },
1903            }),
1904        );
1905    }
1906
1907    #[test]
1908    fn plan_type_alias_resolved_to_custom_argument_type() {
1909        let actual = plan_module(compile(
1910            r#"
1911pub type Outcome =
1912  Result(Int, Nil)
1913
1914pub fn main() {
1915  1
1916}
1917
1918fn count(values: Outcome) {
1919  1
1920}
1921"#,
1922        ))
1923        .expect("aliases to concrete custom arguments should plan");
1924        assert_eq!(
1925            actual.functions()[0].params(),
1926            &[Param::named(
1927                ParamLocal::custom(CustomLocalId(0), result_custom_type()),
1928                "values".into(),
1929            )],
1930        );
1931    }
1932
1933    #[test]
1934    fn plan_labelled_function_argument_uses_local_name() {
1935        let source = r#"
1936fn identity(value local: Int) {
1937  local
1938}
1939
1940pub fn main() {
1941  identity(value: 1)
1942}
1943"#;
1944        let actual = plan_module(compile(source)).expect("source should plan");
1945        let expected = module(
1946            "main",
1947            function(
1948                "main",
1949                int_return_tail_call_at(
1950                    1,
1951                    [int_arg(int(1))],
1952                    host_call_site(source, "main", "identity(value: 1)"),
1953                ),
1954            ),
1955            [function("identity", local_int(0, "local")).param_int(0, "local")],
1956        );
1957
1958        assert_eq!(actual, expected);
1959    }
1960
1961    #[test]
1962    fn plan_function_list_params_preserve_item_family_boundaries() {
1963        let actual = plan_module(compile(
1964            r#"
1965fn collect(
1966  ints: List(Int),
1967  strings: List(String),
1968  bit_arrays: List(BitArray),
1969  floats: List(Float),
1970  bools: List(Bool),
1971  nils: List(Nil),
1972  tuples: List(#(Int, String)),
1973  lists: List(List(Float)),
1974  functions: List(fn(Int) -> String),
1975) {
1976  Nil
1977}
1978
1979pub fn main() {
1980  Nil
1981}
1982"#,
1983        ))
1984        .expect("source should plan");
1985        let collect = actual
1986            .functions()
1987            .iter()
1988            .find(|function| function.name() == "collect")
1989            .expect("collect function should be planned");
1990        let nested_function_type = FunctionType::new(vec![ValueType::Int], ValueType::String);
1991
1992        assert_eq!(
1993            collect.params(),
1994            &[
1995                Param::named(
1996                    ParamLocal::list(ListLocal::int(IntListLocalId(0))),
1997                    "ints".into(),
1998                ),
1999                Param::named(
2000                    ParamLocal::list(ListLocal::string(StringListLocalId(0))),
2001                    "strings".into(),
2002                ),
2003                Param::named(
2004                    ParamLocal::list(ListLocal::bit_array(BitArrayListLocalId(0))),
2005                    "bit_arrays".into(),
2006                ),
2007                Param::named(
2008                    ParamLocal::list(ListLocal::float(FloatListLocalId(0))),
2009                    "floats".into(),
2010                ),
2011                Param::named(
2012                    ParamLocal::list(ListLocal::bool(BoolListLocalId(0))),
2013                    "bools".into(),
2014                ),
2015                Param::named(
2016                    ParamLocal::list(ListLocal::nil(NilListLocalId(0))),
2017                    "nils".into(),
2018                ),
2019                Param::named(
2020                    ParamLocal::list(ListLocal::tuple(
2021                        TupleListLocalId(0),
2022                        vec![ValueType::Int, ValueType::String],
2023                    )),
2024                    "tuples".into(),
2025                ),
2026                Param::named(
2027                    ParamLocal::list(ListLocal::list(ListListLocalId(0), ValueType::Float)),
2028                    "lists".into(),
2029                ),
2030                Param::named(
2031                    ParamLocal::list(ListLocal::function(
2032                        FunctionListLocalId(0),
2033                        nested_function_type,
2034                    )),
2035                    "functions".into(),
2036                ),
2037            ],
2038        );
2039    }
2040
2041    #[test]
2042    fn plan_generic_params_preserve_scheme_owned_local_shapes() {
2043        let actual = plan_module(compile(
2044            r#"
2045fn apply(
2046  function: fn(value) -> value,
2047  value: value,
2048  values: List(value),
2049) -> value {
2050  function(value)
2051}
2052
2053pub fn main() {
2054  apply(fn(value) { value }, 1, [1])
2055}
2056"#,
2057        ))
2058        .expect("concretely called generic params should plan as one template");
2059        let apply = actual
2060            .functions()
2061            .iter()
2062            .find(|function| function.name() == "apply")
2063            .expect("apply template should be planned");
2064        let parameter = TypeParameterId(0);
2065        let callable = GenericFunctionType::new(vec![ValueShape::Parameter(parameter)], parameter);
2066
2067        assert_eq!(apply.scheme(), &TypeScheme::new(1));
2068        assert_eq!(
2069            apply.params(),
2070            &[
2071                Param::named(
2072                    ParamLocal::generic_function(GenericFunctionLocal::new(
2073                        GenericFunctionLocalId(0),
2074                        callable,
2075                    )),
2076                    "function".into(),
2077                ),
2078                Param::named(
2079                    ParamLocal::generic(GenericLocal::new(GenericLocalId(0), parameter)),
2080                    "value".into(),
2081                ),
2082                Param::named(
2083                    ParamLocal::list(ListLocal::generic(GenericListLocalId(0), parameter)),
2084                    "values".into(),
2085                ),
2086            ],
2087        );
2088    }
2089
2090    #[test]
2091    fn plan_local_custom_type_definition() {
2092        let plan = plan_module(compile(
2093            r#"
2094pub type Boxed {
2095  Boxed(Int)
2096}
2097
2098pub fn main() {
2099  1
2100}
2101"#,
2102        ))
2103        .expect("custom type should plan");
2104
2105        assert_eq!(
2106            plan.custom_types(),
2107            &[CustomTypeDefinition::new(
2108                CustomTypeName::new("geam".into(), "main".into(), "Boxed".into()),
2109                CustomTypePublicity::Public,
2110                false,
2111                Vec::new(),
2112                vec![CustomConstructorDefinition::new(
2113                    "Boxed".into(),
2114                    0,
2115                    vec![CustomFieldDefinition::new(None, CustomTypeTemplate::Int)],
2116                )],
2117            )],
2118        );
2119    }
2120
2121    #[test]
2122    fn reject_profile_module_propagates_external_custom_type_owner_error() {
2123        let module = crate::frontend::compile_typed_module(
2124            "main",
2125            "main.gleam",
2126            r#"
2127@external(erlang, "external", "thing")
2128pub type Thing
2129
2130pub fn main() { 1 }
2131"#,
2132        )
2133        .expect("an external custom type should analyse");
2134
2135        assert_eq!(
2136            plan_module(module),
2137            Err(PlanError::UnsupportedTopLevel {
2138                kind: crate::planner::UnsupportedTopLevelKind::ExternalCustomType,
2139            }),
2140        );
2141    }
2142
2143    fn imported_tuple_elements(plan: &crate::plan::ModulePlan) -> &[Expr] {
2144        let ReturnExprKind::Tuple { body, .. } = plan.main_function().return_().kind() else {
2145            panic!("main should return a tuple");
2146        };
2147        let ReturnBodyKind::Expr(tuple) = body.kind() else {
2148            panic!("main should directly return its tuple");
2149        };
2150        let TupleExprKind::Value(elements) = tuple.kind() else {
2151            panic!("main should construct its tuple");
2152        };
2153        elements
2154    }
2155
2156    fn imported_constant_module(expression: &Expr) -> ModuleId {
2157        let ExprKind::Int(value) = expression.kind() else {
2158            panic!("imported constant should be an Int expression");
2159        };
2160        let IntExprKind::Constant(reference) = value.kind() else {
2161            panic!("imported constant should retain a constant reference");
2162        };
2163        reference.instantiation().module()
2164    }
2165
2166    fn imported_call_template(expression: &Expr) -> FunctionTemplateId {
2167        let ExprKind::Int(value) = expression.kind() else {
2168            panic!("imported call should be an Int expression");
2169        };
2170        let IntExprKind::Call { function, .. } = value.kind() else {
2171            panic!("imported function call should remain direct");
2172        };
2173        function.template()
2174    }
2175
2176    fn imported_function_template(expression: &Expr) -> FunctionTemplateId {
2177        let ExprKind::Function(function) = expression.kind() else {
2178            panic!("imported function value should be a function expression");
2179        };
2180        let FunctionExprKind::Int(function) = function.kind() else {
2181            panic!("imported function should return Int");
2182        };
2183        let IntFunctionExprKind::Reference(reference) = function.kind() else {
2184            panic!("imported function value should remain a reference");
2185        };
2186        reference.instantiation().template()
2187    }
2188
2189    fn result_type() -> ValueType {
2190        ValueType::Custom(result_custom_type())
2191    }
2192
2193    fn result_custom_type() -> CustomType {
2194        CustomType::new(
2195            CustomTypeName::new("".into(), "gleam".into(), "Result".into()),
2196            vec![ValueType::Int, ValueType::Nil],
2197        )
2198    }
2199}
2200mod constant;
2201mod custom_type;
2202mod host;
2203pub(in crate::planner) mod registry;