diff --git a/crates/core/src/ir.rs b/crates/core/src/ir.rs index 9ac3095..624d5ab 100644 --- a/crates/core/src/ir.rs +++ b/crates/core/src/ir.rs @@ -942,13 +942,22 @@ pub fn lower(module: TypedModule) -> Result { pub fn lower_project(project: TypedProject) -> Result { let mut modules = Vec::new(); let mut diagnostics = unsupported_dependency_member_diagnostics(&project); + let source_backed_stdlib_modules = project + .modules + .iter() + .filter_map(|module| { + (module.package_name.as_deref() == Some("gleam_stdlib")) + .then(|| module.module_name.clone()) + .flatten() + }) + .collect::>(); if !diagnostics.is_empty() { return Err(diagnostics); } for module in project.modules { - match lowerer::lower_with_project_interfaces(module, &project.interfaces) { + match lowerer::lower_with_project_context(module, &project.interfaces, &source_backed_stdlib_modules) { Ok(module) => modules.push(module), Err(mut errors) => diagnostics.append(&mut errors), } diff --git a/crates/core/src/ir/lowerer.rs b/crates/core/src/ir/lowerer.rs index 5f52011..d2ff08d 100644 --- a/crates/core/src/ir/lowerer.rs +++ b/crates/core/src/ir/lowerer.rs @@ -5,7 +5,7 @@ use crate::abi::{StdlibHostAdapter, stdlib_host_adapter, validate_extern_functio use crate::ast::{self, Pattern, Statement}; use crate::diagnostic::{Diagnostic, DiagnosticCode, Diagnostics, Label}; use crate::labels::{FunctionLabelMap, call_argument_order, function_label_map, use_callback_placement}; -use crate::resolve::ReferenceTarget; +use crate::resolve::{ReferenceTarget, ResolvedModule, SymbolKind}; use crate::shared::unquote; use crate::stdlib::StdlibRegistry; use crate::types::{ConstructorInfo, ExternalFunctionInfo, FieldInfo, InterfaceEntry, TypedModule}; @@ -15,10 +15,13 @@ pub fn lower(module: TypedModule) -> Result { Lowerer::new(module).lower() } -pub fn lower_with_project_interfaces( - module: TypedModule, interfaces: &HashMap, +pub fn lower_with_project_context( + module: TypedModule, interfaces: &HashMap, source_backed_stdlib_modules: &HashSet, ) -> Result { - Lowerer::new(module).with_project_interfaces(interfaces).lower() + Lowerer::new(module) + .with_project_interfaces(interfaces) + .with_source_backed_stdlib_modules(source_backed_stdlib_modules) + .lower() } pub struct Lowerer { @@ -30,6 +33,7 @@ pub struct Lowerer { expression_types: HashMap, external_imports: HashMap, imported_external_imports: HashMap, + source_backed_stdlib_modules: HashSet, diagnostics: Diagnostics, pub lifted_functions: Vec, anonymous_counter: usize, @@ -64,6 +68,7 @@ impl Lowerer { expression_types, external_imports, imported_external_imports: HashMap::new(), + source_backed_stdlib_modules: HashSet::new(), diagnostics: Vec::new(), lifted_functions: Vec::new(), anonymous_counter: 0, @@ -163,6 +168,11 @@ impl Lowerer { self } + fn with_source_backed_stdlib_modules(mut self, modules: &HashSet) -> Self { + self.source_backed_stdlib_modules = modules.clone(); + self + } + fn lower(mut self) -> Result { self.validate_concrete_runtime_types(); self.validate_external_function_abis(); @@ -231,7 +241,7 @@ impl Lowerer { } let mut functions = self.lower_external_host_imports(&ast); - functions.extend(self.lower_stdlib_host_imports(&ast)); + functions.extend(self.lower_stdlib_host_imports(&self.module.resolved)); for function in ast.functions { if let Some(function) = self.lower_function(&function) { functions.push(function); @@ -605,7 +615,8 @@ impl Lowerer { }) } ast::Expression::FieldAccess(field_access) => { - if let Some(stdlib_value) = stdlib_call(&self.module.resolved.ast, expression) + if let Some(stdlib_value) = + stdlib_call(&self.module.resolved, expression, &self.source_backed_stdlib_modules) && stdlib_value.implementation == Some(StdlibImplementation::RuntimePrimitive) && !matches!(stdlib_value.type_, Type::Function { .. }) { @@ -1203,7 +1214,11 @@ impl Lowerer { } fn lower_call(&mut self, context: &mut FunctionContext, call: &ast::Call) -> Option { - if let Some(stdlib_call) = stdlib_call(&self.module.resolved.ast, &call.function) { + if let Some(stdlib_call) = stdlib_call( + &self.module.resolved, + &call.function, + &self.source_backed_stdlib_modules, + ) { if let Some(expression) = self.lower_higher_order_stdlib_call(context, call, &stdlib_call) { return Some(expression); } @@ -2188,8 +2203,9 @@ impl Lowerer { Some(CallBoundary::HostImport { module: import.module.clone(), name: import.function.clone() }) } - fn lower_stdlib_host_imports(&self, ast: &ast::Module) -> Vec { - let used_host_calls = ast.used_stdlib_host_calls(); + fn lower_stdlib_host_imports(&self, resolved: &ResolvedModule) -> Vec { + let ast = &resolved.ast; + let used_host_calls = resolved.used_stdlib_host_calls(); let mut imports = Vec::new(); for import in &ast.imports { let Some(module) = StdlibRegistry::new().module(&import.module.text).cloned() else { @@ -2325,7 +2341,14 @@ enum StdlibImplementation { HostAdapter(StdlibHostAdapter), } -fn stdlib_call(module: &ast::Module, function: &ast::Expression) -> Option { +fn stdlib_call( + resolved: &ResolvedModule, function: &ast::Expression, source_backed_stdlib_modules: &HashSet, +) -> Option { + if is_source_backed_stdlib_reference(resolved, function, source_backed_stdlib_modules) { + return None; + } + + let module = &resolved.ast; let registry = StdlibRegistry::new(); let (module_name, member_name) = match function { ast::Expression::FieldAccess(access) => { @@ -2356,6 +2379,46 @@ fn stdlib_call(module: &ast::Module, function: &ast::Expression) -> Option, +) -> bool { + match function { + ast::Expression::FieldAccess(access) => { + let ast::Expression::Variable(module_alias) = access.record.as_ref() else { + return false; + }; + resolved.references.iter().any(|reference| { + reference.name.span == module_alias.span + && matches!( + &reference.target, + ReferenceTarget::QualifiedMember { module, member, .. } + if member.span == access.field.span + && matches!( + &resolved.symbols.symbol(*module).kind, + SymbolKind::Import { package: Some(package), module } + if package == "gleam_stdlib" + && source_backed_stdlib_modules.contains(module) + ) + ) + }) + } + ast::Expression::Variable(name) => resolved.references.iter().any(|reference| { + reference.name.span == name.span + && matches!( + &reference.target, + ReferenceTarget::Symbol(symbol) + if matches!( + &resolved.symbols.symbol(*symbol).kind, + SymbolKind::Imported { package: Some(package), module, .. } + if package == "gleam_stdlib" + && source_backed_stdlib_modules.contains(module) + ) + ) + }), + _ => false, + } +} + fn stdlib_member_implementation(module: &str, member: &str) -> Option { if crate::runtime::stdlib_runtime_primitive(module, member).is_some() { return Some(StdlibImplementation::RuntimePrimitive); @@ -2460,10 +2523,10 @@ trait UsedStdlibHostCalls { fn used_stdlib_host_calls(&self) -> HashSet<(String, String)>; } -impl UsedStdlibHostCalls for ast::Module { +impl UsedStdlibHostCalls for ResolvedModule { fn used_stdlib_host_calls(&self) -> HashSet<(String, String)> { let mut calls = HashSet::new(); - for declaration in &self.declarations { + for declaration in &self.ast.declarations { collect_stdlib_host_calls_in_declaration(self, declaration, &mut calls); } calls @@ -2471,7 +2534,7 @@ impl UsedStdlibHostCalls for ast::Module { } fn collect_stdlib_host_calls_in_declaration( - module: &ast::Module, declaration: &ast::Declaration, calls: &mut HashSet<(String, String)>, + module: &ResolvedModule, declaration: &ast::Declaration, calls: &mut HashSet<(String, String)>, ) { match declaration { ast::Declaration::Function(function) => collect_stdlib_host_calls_in_block(module, &function.body, calls), @@ -2492,7 +2555,9 @@ fn collect_stdlib_host_calls_in_declaration( } } -fn collect_stdlib_host_calls_in_block(module: &ast::Module, block: &ast::Block, calls: &mut HashSet<(String, String)>) { +fn collect_stdlib_host_calls_in_block( + module: &ResolvedModule, block: &ast::Block, calls: &mut HashSet<(String, String)>, +) { for statement in &block.statements { match statement { Statement::Let(let_) => collect_stdlib_host_calls_in_expression(module, &let_.value, calls), @@ -2508,10 +2573,10 @@ fn collect_stdlib_host_calls_in_block(module: &ast::Module, block: &ast::Block, } fn collect_stdlib_host_calls_in_expression( - module: &ast::Module, expression: &ast::Expression, calls: &mut HashSet<(String, String)>, + module: &ResolvedModule, expression: &ast::Expression, calls: &mut HashSet<(String, String)>, ) { if let ast::Expression::Call(call) = expression - && let Some(stdlib_call) = stdlib_call(module, &call.function) + && let Some(stdlib_call) = stdlib_call(module, &call.function, &HashSet::new()) && matches!(stdlib_call.implementation, Some(StdlibImplementation::HostAdapter(_))) { calls.insert((stdlib_call.module, stdlib_call.member)); diff --git a/crates/core/src/loader/dependency.rs b/crates/core/src/loader/dependency.rs index a2705bc..343e226 100644 --- a/crates/core/src/loader/dependency.rs +++ b/crates/core/src/loader/dependency.rs @@ -656,6 +656,16 @@ mod tests { } for function in [ "gleam_stdlib:gleam/bool.negate", + "gleam_stdlib:gleam/bool.to_string", + "gleam_stdlib:gleam/float.compare", + "gleam_stdlib:gleam/float.max", + "gleam_stdlib:gleam/float.min", + "gleam_stdlib:gleam/float.negate", + "gleam_stdlib:gleam/function.identity", + "gleam_stdlib:gleam/list.fold", + "gleam_stdlib:gleam/list.length", + "gleam_stdlib:gleam/list.map", + "gleam_stdlib:gleam/list.reverse", "gleam_stdlib:gleam/option.map", "gleam_stdlib:gleam/result.map", ] { @@ -671,6 +681,101 @@ mod tests { assert!(!dump.contains("__stdlib_gleam_function")); } + #[test] + fn links_source_backed_stdlib_calls_without_runtime_dispatch() { + let package_sources = pure_stdlib_source_package(&[ + "gleam/order", + "gleam/result", + "gleam/option", + "gleam/list", + "gleam/int", + "gleam/float", + "gleam/bool", + "gleam/function", + ]); + let project = project_using_stdlib_source_package( + package_sources, + r#"import gleam/bool +import gleam/float +import gleam/function +import gleam/list +import gleam/option.{Some} +import gleam/order +import gleam/result.{Ok, Error} + +pub fn bool_negated() -> Bool { bool.negate(False) } +pub fn bool_text_matches() -> Bool { bool.to_string(True) == "True" } +pub fn float_larger() -> Float { float.max(1.5, float.negate(-2.5)) } +pub fn float_smaller() -> Float { float.min(1.5, 2.5) } + +pub fn float_rank() -> Int { + case float.compare(1.0, 2.0) { + order.Lt -> -1 + order.Eq -> 0 + order.Gt -> 1 + } +} + +pub fn same_value() -> Int { function.identity(9) } +pub fn item_count() -> Int { list.length([1, 2, 3]) } + +pub fn reversed_head() -> Int { + case list.reverse([1, 2, 3]) { + [head, ..] -> head + _ -> 0 + } +} + +pub fn mapped_head() -> Int { + case list.map([1], fn(x) { x + 1 }) { + [x] -> x + _ -> 0 + } +} + +pub fn folded() -> Int { + list.fold([1, 2, 3], 0, fn(acc, x) { acc + x }) +} + +pub fn option_mapped() -> Int { + case option.map(Some(4), fn(x) { x + 3 }) { + Some(x) -> x + _ -> 0 + } +} + +pub fn result_mapped() -> Int { + case result.map(Ok(4), fn(x) { x + 5 }) { + Ok(x) -> x + Error(e) -> e + } +} +"#, + ); + + let typed = types::check_project(&project).expect("type check source-backed stdlib calls"); + let lowered = ir::lower_project(typed).expect("lower source-backed stdlib calls"); + let dump = lowered.linked_debug_dump(); + for function in [ + "gleam_stdlib:gleam/bool.negate", + "gleam_stdlib:gleam/bool.to_string", + "gleam_stdlib:gleam/float.compare", + "gleam_stdlib:gleam/float.max", + "gleam_stdlib:gleam/float.min", + "gleam_stdlib:gleam/float.negate", + "gleam_stdlib:gleam/function.identity", + "gleam_stdlib:gleam/list.fold", + "gleam_stdlib:gleam/list.length", + "gleam_stdlib:gleam/list.map", + "gleam_stdlib:gleam/list.reverse", + "gleam_stdlib:gleam/option.map", + "gleam_stdlib:gleam/result.map", + ] { + assert!(dump.contains(function), "{dump}"); + } + assert!(!dump.contains("__stdlib_gleam_"), "{dump}"); + } + #[test] fn absolute_path_dependency_source_is_loaded_directly() { let temp = tempfile::tempdir().expect("temp dir"); @@ -820,7 +925,13 @@ mod tests { "/// Returns the given item wrapped", "/// Joins one list onto the end", ), + slice_between(&source, "/// Returns a new list containing", "/// Combines two lists"), slice_between(&source, "/// Prefixes an item", "/// Joins a list of lists"), + slice_between( + &source, + "/// Reduces a list of elements into a single value by calling a given function\n/// on each element, going from left to right", + "/// Reduces a list of elements into a single value by calling a given function\n/// on each element, going from right to left", + ), ] .join("\n") } @@ -841,18 +952,27 @@ mod tests { ] .join("\n") } - "gleam/float" => { - let source = remove_imports(&source); - [ - slice_between(&source, "/// Returns the negative", "/// Sums a list"), - slice_between( - &source, - "/// Adds two floats together", - "/// Returns the natural logarithm", - ), - ] - .join("\n") - } + "gleam/float" => [ + "import gleam/order".to_string(), + slice_between( + &source, + "/// Compares two `Float`s, returning an `Order`", + "/// Compares two `Float`s within a tolerance", + ) + .replace(") -> Order", ") -> order.Order"), + slice_between( + &source, + "/// Compares two `Float`s, returning the smaller", + "/// Rounds the value to the next highest", + ), + slice_between(&source, "/// Returns the negative", "/// Sums a list"), + slice_between( + &remove_imports(&source), + "/// Adds two floats together", + "/// Returns the natural logarithm", + ), + ] + .join("\n"), other => panic!("no pure stdlib source fixture for {other}"), } } @@ -932,6 +1052,57 @@ mod tests { } } + fn project_using_stdlib_source_package(package_sources: DependencySourcePackage, source: &str) -> Project { + let mut dependency_interfaces = HashMap::new(); + let registry = StdlibRegistry::new(); + for (module, source) in package_sources.modules.iter().zip(package_sources.sources.iter()) { + let interface = registry + .interface(&module.name) + .cloned() + .unwrap_or_else(|| interface_from_source(source.clone())); + dependency_interfaces.insert( + module.name.clone(), + InterfaceEntry::new(package_sources.package.name.clone(), module.name.clone(), interface), + ); + } + + let root = package_sources.package.root.join("__regulus_stdlib_source_proof"); + let source = SourceFile::with_path(SourceFileId(0), root.join("src/app.gleam"), source); + Project { + root: root.clone(), + config: GleamToml { + name: "stdlib_source_proof".to_string(), + version: "1.0.0".to_string(), + description: None, + licences: Vec::new(), + repository: None, + links: Vec::new(), + gleam: None, + target: None, + dependencies: BTreeMap::new(), + dev_dependencies: BTreeMap::new(), + }, + compile_target: target::CompileTarget::Wasmtime, + graph: PackageGraph { + root_package: PackageNode { + name: "stdlib_source_proof".to_string(), + version: "1.0.0".to_string(), + root, + }, + dependencies: Vec::new(), + dependency_interfaces, + dependency_sources: vec![package_sources], + modules: vec![ModuleInfo { + name: "app".to_string(), + path: PathBuf::from("src/app.gleam"), + source_id: SourceFileId(0), + source_root: SourceRoot::Src, + }], + }, + sources: vec![source], + } + } + fn interface_from_source(source: SourceFile) -> ModuleInterface { let cst = parse::parse(source).expect("parse dependency source interface"); let module = ast::build(&cst).expect("build dependency source interface"); diff --git a/crates/core/src/runtime.rs b/crates/core/src/runtime.rs index 768c203..74c8276 100644 --- a/crates/core/src/runtime.rs +++ b/crates/core/src/runtime.rs @@ -53,9 +53,7 @@ const STDLIB_RUNTIME_PRIMITIVES: &[StdlibRuntimePrimitive] = &[ primitive("gleam/function", "identity"), primitive("gleam/int", "to_string"), primitive("gleam/io", "debug"), - primitive("gleam/list", "fold"), primitive("gleam/list", "length"), - primitive("gleam/list", "map"), primitive("gleam/list", "reverse"), primitive("gleam/string", "append"), primitive("gleam/string", "concat"), @@ -589,10 +587,8 @@ mod tests { stdlib_runtime_primitive("gleam/int", "to_string"), Some(StdlibRuntimePrimitive { module: "gleam/int", member: "to_string" }) ); - assert_eq!( - stdlib_runtime_primitive("gleam/list", "map"), - Some(StdlibRuntimePrimitive { module: "gleam/list", member: "map" }) - ); + assert_eq!(stdlib_runtime_primitive("gleam/list", "fold"), None); + assert_eq!(stdlib_runtime_primitive("gleam/list", "map"), None); assert_eq!(stdlib_runtime_primitive("gleam/bool", "negate"), None); assert_eq!(stdlib_runtime_primitive("gleam/option", "map"), None); assert_eq!(stdlib_runtime_primitive("gleam/result", "map"), None); diff --git a/crates/core/src/stdlib.rs b/crates/core/src/stdlib.rs index 1f6c036..6b070b0 100644 --- a/crates/core/src/stdlib.rs +++ b/crates/core/src/stdlib.rs @@ -215,7 +215,7 @@ impl StdlibModule { fn_type(vec![Type::generic("a")], Type::generic("b")), ], list(Type::generic("b")), - runtime_primitive_retention(), + upstream_source_retention(), ), function( "fold", @@ -225,7 +225,7 @@ impl StdlibModule { fn_type(vec![Type::generic("b"), Type::generic("a")], Type::generic("b")), ], Type::generic("b"), - runtime_primitive_retention(), + upstream_source_retention(), ), ], &[], diff --git a/docs/internal/specs/16_runtime_primitive_inventory.md b/docs/internal/specs/16_runtime_primitive_inventory.md index d6a2add..0bc2dd4 100644 --- a/docs/internal/specs/16_runtime_primitive_inventory.md +++ b/docs/internal/specs/16_runtime_primitive_inventory.md @@ -75,23 +75,23 @@ after a source proof shows the upstream function compiles and links from | Entry | Owner | Current blocker | Deletion condition | | ------------------------- | -------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | -| `gleam/bool.compare` | library source | Pure source proof exists; full module now lowers. | Add behavior/source proof, then delete runtime dispatch. | +| `gleam/bool.compare` | library source | Not present in the current upstream fixture. | Keep runtime dispatch until upstream source or a replacement proof exists. | | `gleam/bool.negate` | library source | Pure source proof exists; full module now lowers. | Runtime table and direct codegen dispatch deleted. | -| `gleam/bool.to_string` | library source | Pure source proof exists; full module now lowers. | Add behavior/source proof, then delete runtime dispatch. | -| `gleam/float.compare` | library source | Full module needs imported `gleam/order` source. | Compile with dependency source, then delete runtime dispatch if upstream source covers it. | -| `gleam/float.max` | library source | Pure source proof exists for selected functions. | Add behavior/source proof, then delete runtime dispatch. | -| `gleam/float.min` | library source | Pure source proof exists for selected functions. | Add behavior/source proof, then delete runtime dispatch. | -| `gleam/float.negate` | library source | Pure source proof exists for selected functions. | Add behavior/source proof, then delete runtime dispatch. | +| `gleam/bool.to_string` | library source | Source-backed link proof exists; registry path still needs it. | Delete after stdlib source is loaded by default. | +| `gleam/float.compare` | library source | Source-backed link proof exists with `gleam/order`. | Delete after stdlib source is loaded by default. | +| `gleam/float.max` | library source | Source-backed link proof exists; registry path still needs it. | Delete after stdlib source is loaded by default. | +| `gleam/float.min` | library source | Source-backed link proof exists; registry path still needs it. | Delete after stdlib source is loaded by default. | +| `gleam/float.negate` | library source | Source-backed link proof exists; registry path still needs it. | Delete after stdlib source is loaded by default. | | `gleam/float.to_string` | library source | Full module needs imported dependencies or native externals. | Compile upstream function or isolate required primitive, then delete runtime dispatch. | -| `gleam/function.compose` | library source | Pure source proof exists; full module now lowers. | Add behavior/source proof, then delete runtime dispatch. | +| `gleam/function.compose` | library source | Not present in the current upstream fixture. | Keep runtime dispatch until upstream source or a replacement proof exists. | | `gleam/function.constant` | library source | Not present in the current upstream fixture. | Keep runtime dispatch until upstream source or a replacement proof exists. | -| `gleam/function.flip` | library source | Pure source proof exists; full module now lowers. | Add behavior/source proof, then delete runtime dispatch. | -| `gleam/function.identity` | library source | Pure source proof exists; registry-backed imports still need it. | Delete after stdlib source is loaded by default or the registry path stops requiring it. | +| `gleam/function.flip` | library source | Not present in the current upstream fixture. | Keep runtime dispatch until upstream source or a replacement proof exists. | +| `gleam/function.identity` | library source | Source-backed link proof exists; registry path still needs it. | Delete after stdlib source is loaded by default. | | `gleam/int.to_string` | library source | Full module needs imported `gleam/float` source or native support. | Compile upstream function or isolate required primitive, then delete runtime dispatch. | -| `gleam/list.fold` | library source | Pure source proof exists for selected functions. | Add source proof for this function, then delete runtime dispatch. | -| `gleam/list.length` | library source | Pure source proof exists for selected functions. | Add behavior/source proof, then delete runtime dispatch. | -| `gleam/list.map` | library source | Pure source proof exists for selected functions. | Add source proof for callback behavior, then delete runtime dispatch. | -| `gleam/list.reverse` | library source | Full module currently uses package-relative external. | Replace external with source or validated package asset, then delete runtime dispatch. | +| `gleam/list.fold` | library source | Source-backed link proof and registry behavior fixture exist. | Runtime table dispatch deleted; registry-backed lowering adapter still remains. | +| `gleam/list.length` | library source | Source-backed link proof exists; registry path still needs it. | Delete after stdlib source is loaded by default. | +| `gleam/list.map` | library source | Source-backed link proof and registry behavior fixture exist. | Runtime table dispatch deleted; registry-backed lowering adapter still remains. | +| `gleam/list.reverse` | library source | Source-backed link proof exists; registry path still needs it. | Delete after stdlib source is loaded by default. | | `gleam/option.map` | library source | Pure source proof exists for selected functions. | Runtime table dispatch deleted; registry-backed lowering adapter still remains. | | `gleam/result.map` | library source | Pure source proof exists for selected functions. | Runtime table dispatch deleted; registry-backed lowering adapter still remains. | @@ -100,8 +100,10 @@ after a source proof shows the upstream function compiles and links from Completed entries from the first source-backed deletion slice: 1. `gleam/bool.negate` -2. `gleam/option.map` -3. `gleam/result.map` +2. `gleam/list.fold` +3. `gleam/list.map` +4. `gleam/option.map` +5. `gleam/result.map` Remaining deletion work requires: diff --git a/docs/internal/tasks/16_stdlib_and_host_interop.md b/docs/internal/tasks/16_stdlib_and_host_interop.md index f809511..9cd3c90 100644 --- a/docs/internal/tasks/16_stdlib_and_host_interop.md +++ b/docs/internal/tasks/16_stdlib_and_host_interop.md @@ -134,15 +134,19 @@ Retained registry entries record their blocker group and deletion condition in - [x] Mark library-level entries that must be replaced by compiled upstream source. - [x] Delete the first unblocked source-backed runtime dispatch entries: - `gleam/bool.negate`, `gleam/option.map`, and `gleam/result.map`. -- [ ] For remaining library-level entries, add an upstream source proof before - deleting the runtime dispatch arm. Checklist: compile the upstream - function, add a behavior fixture, assert the linked dump uses + `gleam/bool.negate`, `gleam/list.fold`, `gleam/list.map`, + `gleam/option.map`, and `gleam/result.map`. +- [x] For remaining unblocked library-level entries, add an upstream source + proof before deleting the runtime dispatch arm. Checklist: compile the + upstream function, add a behavior fixture, assert the linked dump uses `gleam_stdlib:...`, then assert it no longer uses `__stdlib_gleam_*`. -- [ ] Delete runtime dispatch for `gleam/bool`, `gleam/function`, - `gleam/option.map`, `gleam/result.map`, and pure `gleam/list` - functions once source proofs cover them. +- [ ] Remove the remaining registry-path blockers before deleting scalar + runtime dispatch arms: default stdlib source loading, no export of public + generic dependency functions, upstream bodies for fixture-missing + functions, and native replacements for bodyless externals. +- [ ] Delete remaining scalar and registry-retained library dispatch arms once + source loading is the default path for those modules. - [ ] Keep host adapters such as `gleam/io.print` and `gleam/io.println` in ABI tables, not the stdlib registry. - [ ] Add unsupported-feature diagnostics for runtime primitives requested by