Skip to main content

geam_core/planner/
module.rs

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