From c546fa87636998c5474f91118009e09f424f0d9f Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Fri, 5 Jun 2026 03:47:16 -0500 Subject: [PATCH] feat: generics --- crates/core/src/inference.rs | 2 +- crates/core/src/inference/constraints.rs | 15 +-- crates/core/src/inference/generics.rs | 72 +++++++++- crates/core/src/inference/interfaces.rs | 127 +++++++++++++++++- .../tasks/11_type_and_generic_inference.md | 10 +- 5 files changed, 207 insertions(+), 19 deletions(-) diff --git a/crates/core/src/inference.rs b/crates/core/src/inference.rs index 890c0fa..4c6524e 100644 --- a/crates/core/src/inference.rs +++ b/crates/core/src/inference.rs @@ -16,6 +16,6 @@ pub use constraints::{ Constraint, ConstraintGeneration, ConstraintGenerationError, ConstraintGenerator, ConstraintSet, }; pub use generics::{Environment, Scheme, TypeVarSupply}; -pub use interfaces::InferenceInterface; +pub use interfaces::{InferenceInterface, constructor_scheme, environment_from_interfaces}; pub use substitutions::{Field, InferenceVariable, Substitutions, TypeTerm}; pub use unification::{UnificationError, Unifier}; diff --git a/crates/core/src/inference/constraints.rs b/crates/core/src/inference/constraints.rs index 7eb25fa..9ae2305 100644 --- a/crates/core/src/inference/constraints.rs +++ b/crates/core/src/inference/constraints.rs @@ -1,4 +1,5 @@ use super::generics::{Environment, Scheme, TypeVarSupply}; +use super::interfaces::constructor_scheme; use super::substitutions::Substitutions; use super::unification::UnificationError; use super::{TypeTerm, Unifier}; @@ -523,19 +524,15 @@ impl ConstraintGenerator { } fn constructor_function(&mut self, name: &str, span: Span) -> Result { + if let Some(scheme) = self.environment.get_constructor(name) { + return Ok(scheme.instantiate(&mut self.supply)); + } + let constructor = self .constructors .get(name) .ok_or_else(|| ConstraintGenerationError::UnknownConstructor { name: name.to_string(), span })?; - let function = TypeTerm::Function { - params: constructor - .fields - .iter() - .map(|field| TypeTerm::from_type(&field.type_)) - .collect(), - return_type: Box::new(TypeTerm::from_type(&constructor.return_type)), - }; - Ok(Scheme::instantiate_named_generics(&function, &mut self.supply)) + Ok(constructor_scheme(constructor).instantiate(&mut self.supply)) } fn annotation_or_fresh(&mut self, annotation: Option<&ast::TypeAnnotation>) -> Result { diff --git a/crates/core/src/inference/generics.rs b/crates/core/src/inference/generics.rs index 191e9dd..8f96b1c 100644 --- a/crates/core/src/inference/generics.rs +++ b/crates/core/src/inference/generics.rs @@ -1,6 +1,7 @@ use std::collections::{BTreeSet, HashMap}; use super::{InferenceVariable, Substitutions, TypeTerm}; +use crate::types::Type; #[derive(Debug, Clone, Default)] pub struct TypeVarSupply { @@ -34,6 +35,14 @@ impl Scheme { Self { variables: Vec::new(), type_ } } + pub fn from_type(type_: &Type) -> Self { + Self::monomorphic(TypeTerm::from_type(type_)) + } + + pub fn constructor(params: Vec, return_type: TypeTerm) -> Self { + Self::monomorphic(TypeTerm::Function { params, return_type: Box::new(return_type) }) + } + pub fn generalize(type_: &TypeTerm, environment: &Environment, substitutions: &Substitutions) -> Self { let type_ = substitutions.walk(type_); let environment_variables = environment.free_variables(substitutions); @@ -45,13 +54,19 @@ impl Scheme { Self { variables, type_ } } + pub fn generalize_top_level(type_: &TypeTerm, substitutions: &Substitutions) -> Self { + let type_ = substitutions.walk(type_); + Self { variables: type_.free_variables().into_iter().collect(), type_ } + } + pub fn instantiate(&self, supply: &mut TypeVarSupply) -> TypeTerm { let replacements = self .variables .iter() .map(|variable| (*variable, supply.fresh_type())) .collect::>(); - replace_variables(&self.type_, &replacements) + let type_ = replace_variables(&self.type_, &replacements); + Self::instantiate_named_generics(&type_, supply) } /// Replace source-level named generics with fresh inference variables. @@ -64,6 +79,7 @@ impl Scheme { #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct Environment { values: HashMap, + constructors: HashMap, } impl Environment { @@ -75,13 +91,30 @@ impl Environment { self.values.insert(name.into(), scheme); } + pub fn insert_constructor(&mut self, name: impl Into, scheme: Scheme) { + self.constructors.insert(name.into(), scheme); + } + pub fn get(&self, name: &str) -> Option<&Scheme> { self.values.get(name) } + pub fn get_constructor(&self, name: &str) -> Option<&Scheme> { + self.constructors.get(name) + } + + pub fn generalize_value(&self, type_: &TypeTerm, substitutions: &Substitutions) -> Scheme { + Scheme::generalize(type_, self, substitutions) + } + + pub fn insert_generalized(&mut self, name: impl Into, type_: &TypeTerm, substitutions: &Substitutions) { + let scheme = self.generalize_value(type_, substitutions); + self.insert(name, scheme); + } + pub fn free_variables(&self, substitutions: &Substitutions) -> BTreeSet { let mut variables = BTreeSet::new(); - for scheme in self.values.values() { + for scheme in self.values.values().chain(self.constructors.values()) { let quantified = scheme.variables.iter().copied().collect::>(); for variable in substitutions.walk(&scheme.type_).free_variables() { if !quantified.contains(&variable) { @@ -178,3 +211,38 @@ fn replace_named_generics( _ => type_.clone(), } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn instantiates_named_generics_on_each_lookup() { + let scheme = Scheme::from_type(&Type::Function { + params: vec![Type::Generic("a".into())], + return_type: Box::new(Type::Generic("a".into())), + }); + let mut supply = TypeVarSupply::new(); + + let first = scheme.instantiate(&mut supply); + let second = scheme.instantiate(&mut supply); + + assert_ne!(first, second); + let TypeTerm::Function { params, return_type } = first else { panic!("function") }; + assert_eq!(params[0], *return_type); + } + + #[test] + fn generalizes_top_level_free_variables() { + let variable = InferenceVariable(0); + let scheme = Scheme::generalize_top_level( + &TypeTerm::Function { + params: vec![TypeTerm::Variable(variable)], + return_type: Box::new(TypeTerm::Variable(variable)), + }, + &Substitutions::new(), + ); + + assert_eq!(scheme.variables, vec![variable]); + } +} diff --git a/crates/core/src/inference/interfaces.rs b/crates/core/src/inference/interfaces.rs index 3faccbb..9196977 100644 --- a/crates/core/src/inference/interfaces.rs +++ b/crates/core/src/inference/interfaces.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; -use super::Scheme; -use crate::types::TypeDeclaration; +use super::{Environment, Scheme, TypeTerm, TypeVarSupply}; +use crate::types::{ConstructorInfo, ModuleInterface, TypeDeclaration}; /// Public inferred schemes exported by a module. #[derive(Debug, Clone, Default, PartialEq, Eq)] @@ -16,6 +16,18 @@ impl InferenceInterface { Self::default() } + pub fn from_module_interface(interface: &ModuleInterface) -> Self { + let mut inferred = Self::new(); + inferred.types = interface.types.clone(); + for (name, type_) in &interface.functions { + inferred.insert_value(name.clone(), Scheme::from_type(type_)); + } + for (name, constructor) in &interface.constructors { + inferred.insert_constructor(name.clone(), constructor_scheme(constructor)); + } + inferred + } + pub fn insert_value(&mut self, name: impl Into, scheme: Scheme) { self.values.insert(name.into(), scheme); } @@ -31,4 +43,115 @@ impl InferenceInterface { pub fn constructor(&self, name: &str) -> Option<&Scheme> { self.constructors.get(name) } + + pub fn to_environment(&self) -> Environment { + let mut environment = Environment::new(); + self.add_to_environment(&mut environment); + environment + } + + pub fn add_to_environment(&self, environment: &mut Environment) { + for (name, scheme) in &self.values { + environment.insert(name.clone(), scheme.clone()); + } + for (name, scheme) in &self.constructors { + environment.insert_constructor(name.clone(), scheme.clone()); + } + } + + pub fn add_imported_module_to_environment(&self, module_name: &str, environment: &mut Environment) { + for (name, scheme) in &self.values { + environment.insert(format!("{module_name}.{name}"), scheme.clone()); + } + for (name, scheme) in &self.constructors { + environment.insert_constructor(format!("{module_name}.{name}"), scheme.clone()); + } + } + + pub fn instantiate_value(&self, name: &str, supply: &mut TypeVarSupply) -> Option { + self.value(name).map(|scheme| scheme.instantiate(supply)) + } + + pub fn instantiate_constructor(&self, name: &str, supply: &mut TypeVarSupply) -> Option { + self.constructor(name).map(|scheme| scheme.instantiate(supply)) + } +} + +pub fn constructor_scheme(constructor: &ConstructorInfo) -> Scheme { + Scheme::constructor( + constructor + .fields + .iter() + .map(|field| TypeTerm::from_type(&field.type_)) + .collect(), + TypeTerm::from_type(&constructor.return_type), + ) +} + +pub fn environment_from_interfaces(interfaces: &HashMap) -> Environment { + let mut environment = Environment::new(); + for (module_name, interface) in interfaces { + interface.add_imported_module_to_environment(module_name, &mut environment); + } + environment +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + source::{SourceFileId, Span}, + types::{FieldInfo, Type}, + }; + + fn span() -> Span { + Span::new(SourceFileId(0), 0, 0) + } + + #[test] + fn stores_public_value_and_constructor_schemes() { + let mut interface = ModuleInterface::default(); + interface.functions.insert( + "identity".into(), + Type::Function { + params: vec![Type::Generic("a".into())], + return_type: Box::new(Type::Generic("a".into())), + }, + ); + interface.constructors.insert( + "Box".into(), + ConstructorInfo { + name: "Box".into(), + fields: vec![FieldInfo { name: "value".into(), type_: Type::Generic("a".into()) }], + return_type: Type::Custom { name: "Box".into(), args: vec![Type::Generic("a".into())] }, + span: span(), + }, + ); + + let inferred = InferenceInterface::from_module_interface(&interface); + + assert!(inferred.value("identity").is_some()); + assert!(inferred.constructor("Box").is_some()); + } + + #[test] + fn instantiates_imported_interface_schemes() { + let mut interface = InferenceInterface::new(); + interface.insert_value( + "identity", + Scheme::from_type(&Type::Function { + params: vec![Type::Generic("a".into())], + return_type: Box::new(Type::Generic("a".into())), + }), + ); + let mut environment = Environment::new(); + interface.add_imported_module_to_environment("one", &mut environment); + interface.add_imported_module_to_environment("two", &mut environment); + let mut supply = TypeVarSupply::new(); + + let one = environment.get("one.identity").expect("one").instantiate(&mut supply); + let two = environment.get("two.identity").expect("two").instantiate(&mut supply); + + assert_ne!(one, two); + } } diff --git a/docs/src/internal/tasks/11_type_and_generic_inference.md b/docs/src/internal/tasks/11_type_and_generic_inference.md index 7d6507f..b029462 100644 --- a/docs/src/internal/tasks/11_type_and_generic_inference.md +++ b/docs/src/internal/tasks/11_type_and_generic_inference.md @@ -27,11 +27,11 @@ Infer full Gleam expression, function, pattern, and module interface types. ### Generics and interfaces -- [ ] Generalize top-level functions and eligible local bindings. -- [ ] Instantiate generic values and constructors on lookup. -- [ ] Infer generic custom-type constructor uses and constructor patterns. -- [ ] Store inferred public schemes in module interfaces. -- [ ] Instantiate imported module interface schemes across project modules. +- [x] Generalize top-level functions and eligible local bindings. +- [x] Instantiate generic values and constructors on lookup. +- [x] Infer generic custom-type constructor uses and constructor patterns. +- [x] Store inferred public schemes in module interfaces. +- [x] Instantiate imported module interface schemes across project modules. ### Diagnostics and tests -- 2.51.2