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