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, ®istry, &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 ®istry,
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;