From 2ab08ef42e0934a1ca83598f54f01e419d488dec Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 7 Jan 2026 01:46:14 +0800 Subject: [PATCH 01/62] Parse json output from cargo insta --- Cargo.lock | 2 ++ compiler-scripts/Cargo.toml | 2 ++ compiler-scripts/src/bin/test-checking.rs | 24 +++++++++++++++++------ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f37fdf9f..606acdd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,6 +366,8 @@ dependencies = [ "clap", "console", "md-5", + "serde", + "serde_json", "similar", "walkdir", ] diff --git a/compiler-scripts/Cargo.toml b/compiler-scripts/Cargo.toml index 25efb461..6880cb04 100644 --- a/compiler-scripts/Cargo.toml +++ b/compiler-scripts/Cargo.toml @@ -6,6 +6,8 @@ edition = "2024" [dependencies] clap = { version = "4", features = ["derive"] } console = "0.15" +serde = { version = "1", features = ["derive"] } +serde_json = "1" similar = { version = "2", features = ["inline"] } md-5 = "0.10" walkdir = "2" diff --git a/compiler-scripts/src/bin/test-checking.rs b/compiler-scripts/src/bin/test-checking.rs index dac2364c..27fcfe94 100644 --- a/compiler-scripts/src/bin/test-checking.rs +++ b/compiler-scripts/src/bin/test-checking.rs @@ -7,6 +7,12 @@ use clap::Parser; use compiler_scripts::console::style; use compiler_scripts::fixtures::fixture_env; use compiler_scripts::snapshots::{print_diff, strip_frontmatter}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct PendingSnapshot { + path: String, +} #[derive(Parser)] #[command(about = "Run type checker integration tests with snapshot diffing")] @@ -88,6 +94,7 @@ fn main() { let pending_output = Command::new("cargo") .arg("insta") .arg("pending-snapshots") + .arg("--as-json") .stderr(Stdio::null()) .output() .expect("Failed to run cargo insta"); @@ -96,7 +103,7 @@ fn main() { let pending = pending.trim(); if pending.is_empty() { - println!("{}", style("No pending snapshots!")); + println!("{}", style("No pending snapshots.").dim()); return; } @@ -104,18 +111,23 @@ fn main() { let cwd = env::current_dir().unwrap(); - for snap_path in pending.lines() { - let snap_path = snap_path.trim(); - if snap_path.is_empty() { + for line in pending.lines() { + let line = line.trim(); + if line.is_empty() { continue; } + let snap_path = match serde_json::from_str::(line) { + Ok(snapshot) => snapshot.path, + Err(_) => continue, + }; + let short_path = snap_path .strip_prefix(cwd.to_str().unwrap_or("")) - .unwrap_or(snap_path) + .unwrap_or(&snap_path) .trim_start_matches('/'); - let snap = Path::new(snap_path); + let snap = Path::new(&snap_path); let snap_new = format!("{}.new", snap_path); if snap.exists() { From 4800657199b0218afb39cf250169ec349faf55f2 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 7 Jan 2026 04:25:10 +0800 Subject: [PATCH 02/62] Move class globalization to quantify --- compiler-core/checking/src/algorithm/state.rs | 9 +++++++++ compiler-core/checking/src/algorithm/type_item.rs | 5 +---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index 3a6011e0..e5de5937 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -672,6 +672,15 @@ impl CheckState { if let Some((quantified_type, quantified_count)) = quantify::quantify(self, type_id) { if let Some(mut class) = classes.remove(&item_id) { class.quantified_variables = quantified_count; + class.superclasses = class + .superclasses + .iter() + .map(|&(t, k)| { + let t = transfer::globalize(self, context, t); + let k = transfer::globalize(self, context, k); + (t, k) + }) + .collect(); self.checked.classes.insert(item_id, class); } let type_id = transfer::globalize(self, context, quantified_type); diff --git a/compiler-core/checking/src/algorithm/type_item.rs b/compiler-core/checking/src/algorithm/type_item.rs index d311b045..fbf6204e 100644 --- a/compiler-core/checking/src/algorithm/type_item.rs +++ b/compiler-core/checking/src/algorithm/type_item.rs @@ -10,7 +10,7 @@ use smol_str::SmolStr; use crate::ExternalQueries; use crate::algorithm::state::{CheckContext, CheckState, CheckedConstructor, CheckedDataLike}; -use crate::algorithm::{inspect, kind, transfer, unification}; +use crate::algorithm::{inspect, kind, unification}; use crate::core::{Class, ForallBinder, Operator, Synonym, Type, TypeId, Variable, debruijn}; use crate::error::{ErrorKind, ErrorStep}; @@ -328,12 +328,9 @@ where return Ok(()); }; - // Elaborate and globalize superclass constraints let superclasses = constraints.iter().map(|&constraint| { let (constraint_type, constraint_kind) = kind::check_surface_kind(state, context, constraint, context.prim.constraint)?; - let constraint_type = transfer::globalize(state, context, constraint_type); - let constraint_kind = transfer::globalize(state, context, constraint_kind); Ok((constraint_type, constraint_kind)) }); From dffdba3881023555819b64ab78b8ace6b981cc10 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 7 Jan 2026 07:03:10 +0800 Subject: [PATCH 03/62] Generalise class and instance declarations --- .../checking/src/algorithm/constraint.rs | 11 +- .../checking/src/algorithm/quantify.rs | 135 +++++++++++++++++- compiler-core/checking/src/algorithm/state.rs | 31 ++-- .../checking/src/algorithm/substitute.rs | 54 +++++-- .../checking/src/algorithm/term_item.rs | 62 +++++--- .../checking/src/algorithm/type_item.rs | 3 +- compiler-core/checking/src/core.rs | 2 + .../checking/010_class_basic/Main.snap | 3 + .../checking/011_class_functor/Main.snap | 3 + .../checking/012_class_monad_state/Main.snap | 4 + .../checking/013_class_phantom/Main.snap | 3 + .../014_class_with_signature/Main.snap | 3 + .../checking/015_class_superclass/Main.snap | 4 + .../checking/027_type_constrained/Main.snap | 4 + .../076_inspect_constraints/Main.snap | 5 + .../checking/083_instance_basic/Main.snap | 7 + .../checking/084_instance_eq/Main.snap | 7 + .../Main.snap | 7 + .../Main.snap | 7 + .../Main.snap | 9 ++ .../088_given_constraint_matching/Main.snap | 3 + .../checking/089_no_instance_found/Main.snap | 3 + .../checking/090_instance_improve/Main.snap | 7 + .../091_superclass_elaboration/Main.snap | 5 +- .../092_ambiguous_constraint/Main.snap | 4 + .../093_constraint_generalization/Main.snap | 4 + .../Main.snap | 3 + .../095_given_constraint_arityless/Main.snap | 5 +- .../096_given_functional_dependency/Main.snap | 4 + .../checking/097_instance_chains/Main.snap | 9 ++ .../checking/098_fundep_propagation/Main.snap | 18 +++ .../checking/117_do_ado_constrained/Main.snap | 8 ++ .../118_instance_member_type_match/Main.snap | 7 + .../Main.snap | 7 + .../Main.snap | 9 ++ .../Main.snap | 7 + .../Main.snap | 10 ++ .../123_incomplete_instance_head/Main.snap | 9 ++ .../Main.snap | 8 ++ .../Main.snap | 7 + .../checking/126_instance_phantom/Main.purs | 11 ++ .../checking/126_instance_phantom/Main.snap | 19 +++ tests-integration/src/generated/basic.rs | 90 +++++++++++- tests-integration/tests/checking/generated.rs | 2 + 44 files changed, 577 insertions(+), 46 deletions(-) create mode 100644 tests-integration/fixtures/checking/126_instance_phantom/Main.purs create mode 100644 tests-integration/fixtures/checking/126_instance_phantom/Main.snap diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index 9c43d56a..4db7ca33 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -270,8 +270,17 @@ where let k = transfer::localize(state, context, k); (t, k) }); + + let superclasses = superclasses.collect(); + + let type_variable_kinds = + class.type_variable_kinds.iter().map(|&kind| transfer::localize(state, context, kind)); + + let type_variable_kinds = type_variable_kinds.collect(); + Class { - superclasses: superclasses.collect(), + superclasses, + type_variable_kinds, quantified_variables: class.quantified_variables, kind_variables: class.kind_variables, } diff --git a/compiler-core/checking/src/algorithm/quantify.rs b/compiler-core/checking/src/algorithm/quantify.rs index 4c12f956..50a18324 100644 --- a/compiler-core/checking/src/algorithm/quantify.rs +++ b/compiler-core/checking/src/algorithm/quantify.rs @@ -15,8 +15,8 @@ use crate::algorithm::constraint::{ }; use crate::algorithm::fold::Zonk; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::substitute::{ShiftLevels, SubstituteUnification, UniToLevel}; -use crate::core::{ForallBinder, RowType, Type, TypeId, debruijn}; +use crate::algorithm::substitute::{ShiftBound, ShiftImplicit, SubstituteUnification, UniToLevel}; +use crate::core::{Class, ForallBinder, Instance, RowType, Type, TypeId, debruijn}; pub fn quantify(state: &mut CheckState, id: TypeId) -> Option<(TypeId, debruijn::Size)> { let graph = collect_unification(state, id); @@ -33,12 +33,12 @@ pub fn quantify(state: &mut CheckState, id: TypeId) -> Option<(TypeId, debruijn: }; // Shift existing bound variable levels to make room for new quantifiers - let mut quantified = ShiftLevels::on(state, id, size.0); + let mut quantified = ShiftBound::on(state, id, size.0); let mut substitutions = UniToLevel::default(); for (index, &id) in unsolved.iter().rev().enumerate() { let kind = state.unification.get(id).kind; - let kind = ShiftLevels::on(state, kind, size.0); + let kind = ShiftBound::on(state, kind, size.0); let name = generate_type_name(id); let index = debruijn::Index(index as u32); @@ -194,6 +194,118 @@ fn generate_type_name(id: u32) -> smol_str::SmolStr { builder.finish() } +/// Quantifies unification variables in class type variable kinds. +/// +/// When a class type variable does not have an explicit kind, a unification +/// variable is created and potentially generalised if left unsolved. This +/// function applies the same generalisation algorithm used in the kind signature. +/// +/// This function returns the number of additional kind variables introduced. +pub fn quantify_class(state: &mut CheckState, class: &mut Class) -> Option { + let mut graph = UniGraph::default(); + for &kind in &class.type_variable_kinds { + collect_unification_into(&mut graph, state, kind); + } + + for (t, k) in class.superclasses.iter() { + collect_unification_into(&mut graph, state, *t); + collect_unification_into(&mut graph, state, *k); + } + + if graph.node_count() == 0 { + return Some(debruijn::Size(0)); + } + + let unsolved = ordered_toposort(&graph, state)?; + let size = debruijn::Size(unsolved.len() as u32); + + let mut substitutions = UniToLevel::default(); + for (index, &id) in unsolved.iter().rev().enumerate() { + let index = debruijn::Index(index as u32); + let level = index.to_level(size)?; + substitutions.insert(id, level); + } + + let type_variable_kinds = class.type_variable_kinds.iter().map(|&kind| { + let kind = ShiftBound::on(state, kind, size.0); + SubstituteUnification::on(&substitutions, state, kind) + }); + + class.type_variable_kinds = type_variable_kinds.collect(); + + let superclasses = class.superclasses.iter().map(|&(t, k)| { + let t = ShiftBound::on(state, t, size.0); + let t = SubstituteUnification::on(&substitutions, state, t); + let k = ShiftBound::on(state, k, size.0); + let k = SubstituteUnification::on(&substitutions, state, k); + (t, k) + }); + + class.superclasses = superclasses.collect(); + + Some(size) +} + +/// Quantifies unification variables in instance argument and constraint kinds. +/// +/// When an instance type variable does not have an explicit kind, a unification +/// variable is created and potentially generalised if left unsolved. This function +/// applies the same generalisation algorithm as [`quantify`]. +/// +/// This function returns the number of additional kind variables introduced. +pub fn quantify_instance( + state: &mut CheckState, + instance: &mut Instance, +) -> Option { + let mut graph = UniGraph::default(); + + for (t, k) in &instance.arguments { + collect_unification_into(&mut graph, state, *t); + collect_unification_into(&mut graph, state, *k); + } + + for (t, k) in &instance.constraints { + collect_unification_into(&mut graph, state, *t); + collect_unification_into(&mut graph, state, *k); + } + + if graph.node_count() == 0 { + return Some(debruijn::Size(0)); + } + + let unsolved = ordered_toposort(&graph, state)?; + let size = debruijn::Size(unsolved.len() as u32); + + let mut substitutions = UniToLevel::default(); + for (index, &id) in unsolved.iter().rev().enumerate() { + let index = debruijn::Index(index as u32); + let level = index.to_level(size)?; + substitutions.insert(id, level); + } + + let arguments = instance.arguments.iter().map(|&(t, k)| { + let t = ShiftImplicit::on(state, t, size.0); + let t = SubstituteUnification::on(&substitutions, state, t); + let k = ShiftImplicit::on(state, k, size.0); + let k = SubstituteUnification::on(&substitutions, state, k); + (t, k) + }); + + instance.arguments = arguments.collect(); + + let constraints = instance.constraints.iter().map(|&(t, k)| { + let t = ShiftImplicit::on(state, t, size.0); + let t = SubstituteUnification::on(&substitutions, state, t); + let k = ShiftImplicit::on(state, k, size.0); + let k = SubstituteUnification::on(&substitutions, state, k); + (t, k) + }); + + instance.constraints = constraints.collect(); + + Some(size) +} + /// Builds a topological sort of the [`UniGraph`]. /// /// This function uses the domain-based sorting of the unification variables @@ -235,12 +347,12 @@ fn ordered_toposort(graph: &UniGraph, state: &CheckState) -> Option; -/// Collects unification variables in a [`Type`]. +/// Collects unification variables from a [`Type`] into an existing graph. /// /// This function also tracks the dependencies between unification /// variables such as when unification variables appear in another /// unification variable's kind. -fn collect_unification(state: &mut CheckState, id: TypeId) -> UniGraph { +pub fn collect_unification_into(graph: &mut UniGraph, state: &mut CheckState, id: TypeId) { fn aux(graph: &mut UniGraph, state: &mut CheckState, id: TypeId, dependent: Option) { let id = state.normalize_type(id); match state.storage[id] { @@ -306,8 +418,17 @@ fn collect_unification(state: &mut CheckState, id: TypeId) -> UniGraph { } } + aux(graph, state, id, None); +} + +/// Collects unification variables in a [`Type`]. +/// +/// This function also tracks the dependencies between unification +/// variables such as when unification variables appear in another +/// unification variable's kind. +fn collect_unification(state: &mut CheckState, id: TypeId) -> UniGraph { let mut graph = UniGraph::default(); - aux(&mut graph, state, id, None); + collect_unification_into(&mut graph, state, id); graph } diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index e5de5937..f5ab4ea4 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -671,16 +671,31 @@ impl CheckState { for (item_id, type_id) in mem::take(&mut self.binding_group.types) { if let Some((quantified_type, quantified_count)) = quantify::quantify(self, type_id) { if let Some(mut class) = classes.remove(&item_id) { + let class_quantified_count = + quantify::quantify_class(self, &mut class).unwrap_or(debruijn::Size(0)); + + debug_assert_eq!( + quantified_count, class_quantified_count, + "critical violation: class type signature and declaration should have the same number of variables" + ); + class.quantified_variables = quantified_count; - class.superclasses = class - .superclasses + + let superclasses = class.superclasses.iter().map(|&(t, k)| { + let t = transfer::globalize(self, context, t); + let k = transfer::globalize(self, context, k); + (t, k) + }); + + class.superclasses = superclasses.collect(); + + let type_variable_kinds = class + .type_variable_kinds .iter() - .map(|&(t, k)| { - let t = transfer::globalize(self, context, t); - let k = transfer::globalize(self, context, k); - (t, k) - }) - .collect(); + .map(|&kind| transfer::globalize(self, context, kind)); + + class.type_variable_kinds = type_variable_kinds.collect(); + self.checked.classes.insert(item_id, class); } let type_id = transfer::globalize(self, context, quantified_type); diff --git a/compiler-core/checking/src/algorithm/substitute.rs b/compiler-core/checking/src/algorithm/substitute.rs index e5a8262b..3ef52bae 100644 --- a/compiler-core/checking/src/algorithm/substitute.rs +++ b/compiler-core/checking/src/algorithm/substitute.rs @@ -35,11 +35,11 @@ impl TypeFold for SubstituteBound { } } -pub struct ShiftLevels { +pub struct ShiftBound { offset: u32, } -impl ShiftLevels { +impl ShiftBound { /// Shifts all bound variable levels in a type by a given offset. /// /// This is needed when adding new forall binders at the front of a type, @@ -49,19 +49,55 @@ impl ShiftLevels { if offset == 0 { return id; } - fold_type(state, id, &mut ShiftLevels { offset }) + fold_type(state, id, &mut ShiftBound { offset }) } } -impl TypeFold for ShiftLevels { +impl TypeFold for ShiftBound { fn transform(&mut self, state: &mut CheckState, _id: TypeId, t: &Type) -> FoldAction { if let Type::Variable(Variable::Bound(level)) = t { - let shifted = debruijn::Level(level.0 + self.offset); - return FoldAction::Replace( - state.storage.intern(Type::Variable(Variable::Bound(shifted))), - ); + let level = debruijn::Level(level.0 + self.offset); + FoldAction::Replace(state.storage.intern(Type::Variable(Variable::Bound(level)))) + } else { + FoldAction::Continue + } + } + + fn transform_binder(&mut self, binder: &mut ForallBinder) { + binder.level = debruijn::Level(binder.level.0 + self.offset); + } +} + +pub struct ShiftImplicit { + offset: u32, +} + +impl ShiftImplicit { + /// Shifts all bound AND implicit variable levels in a type by a given offset. + /// + /// This is needed when adding new kind binders to instance heads, where + /// implicit variables also need their levels adjusted. + pub fn on(state: &mut CheckState, id: TypeId, offset: u32) -> TypeId { + if offset == 0 { + return id; + } + fold_type(state, id, &mut ShiftImplicit { offset }) + } +} + +impl TypeFold for ShiftImplicit { + fn transform(&mut self, state: &mut CheckState, _id: TypeId, t: &Type) -> FoldAction { + match t { + Type::Variable(Variable::Bound(level)) => { + let level = debruijn::Level(level.0 + self.offset); + FoldAction::Replace(state.storage.intern(Type::Variable(Variable::Bound(level)))) + } + Type::Variable(Variable::Implicit(level)) => { + let level = debruijn::Level(level.0 + self.offset); + FoldAction::Replace(state.storage.intern(Type::Variable(Variable::Implicit(level)))) + } + _ => FoldAction::Continue, } - FoldAction::Continue } fn transform_binder(&mut self, binder: &mut ForallBinder) { diff --git a/compiler-core/checking/src/algorithm/term_item.rs b/compiler-core/checking/src/algorithm/term_item.rs index 882419f9..6b412d1a 100644 --- a/compiler-core/checking/src/algorithm/term_item.rs +++ b/compiler-core/checking/src/algorithm/term_item.rs @@ -5,6 +5,7 @@ use files::FileId; use indexing::{TermItemId, TermItemKind, TypeItemId}; use itertools::Itertools; use lowering::TermItemIr; +use rustc_hash::FxHashMap; use crate::ExternalQueries; use crate::algorithm::kind::synonym; @@ -12,7 +13,7 @@ use crate::algorithm::state::{CheckContext, CheckState, InstanceHeadBinding}; use crate::algorithm::{ constraint, inspect, kind, quantify, substitute, term, transfer, unification, }; -use crate::core::{Instance, Type, TypeId, Variable, debruijn}; +use crate::core::{Instance, Type, TypeId, Variable, debruijn, pretty}; use crate::error::{ErrorKind, ErrorStep}; /// Checks signature declarations for terms. @@ -130,13 +131,25 @@ where }); } + let mut implicit_kinds = FxHashMap::default(); + let mut core_arguments = vec![]; for (argument, expected_kind) in arguments.iter().zip(expected_kinds) { let (inferred_type, inferred_kind) = kind::check_surface_kind(state, context, *argument, expected_kind)?; - let inferred_type = transfer::globalize(state, context, inferred_type); - let inferred_kind = transfer::globalize(state, context, inferred_kind); + // Unify kinds for Implicit and Bound variables, this instance + // declarations like `097_instance_chains` to infer with the + // correct kind annotations for the same variables. + if let Type::Variable(Variable::Implicit(level) | Variable::Bound(level)) = + state.storage[inferred_type] + { + if let Some(bound_kind) = implicit_kinds.get(&level) { + let _ = unification::unify(state, context, inferred_kind, *bound_kind)?; + } else { + implicit_kinds.insert(level, inferred_kind); + } + } core_arguments.push((inferred_type, inferred_kind)); } @@ -145,23 +158,38 @@ where for constraint in constraints.iter() { let (inferred_type, inferred_kind) = kind::infer_surface_kind(state, context, *constraint)?; - - let inferred_type = transfer::globalize(state, context, inferred_type); - let inferred_kind = transfer::globalize(state, context, inferred_kind); - core_constraints.push((inferred_type, inferred_kind)); } - state.checked.instances.insert( - instance_id, - Instance { - arguments: core_arguments, - constraints: core_constraints, - resolution: (class_file, class_item), - chain_id, - chain_position, - }, - ); + let mut instance = Instance { + arguments: core_arguments, + constraints: core_constraints, + resolution: (class_file, class_item), + chain_id, + chain_position, + kind_variables: debruijn::Size(0), + }; + + instance.kind_variables = + quantify::quantify_instance(state, &mut instance).unwrap_or(debruijn::Size(0)); + + let arguments = instance.arguments.iter().map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }); + + instance.arguments = arguments.collect(); + + let constraints = instance.constraints.iter().map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }); + + instance.constraints = constraints.collect(); + + state.checked.instances.insert(instance_id, instance); // Capture implicit variables from the instance head before unbinding. let implicits = state.type_scope.unbind_implicits(debruijn::Level(size.0)); diff --git a/compiler-core/checking/src/algorithm/type_item.rs b/compiler-core/checking/src/algorithm/type_item.rs index fbf6204e..153f91a9 100644 --- a/compiler-core/checking/src/algorithm/type_item.rs +++ b/compiler-core/checking/src/algorithm/type_item.rs @@ -339,7 +339,8 @@ where let class = { let quantified_variables = debruijn::Size(0); let kind_variables = debruijn::Size(kind_variables.len() as u32); - Class { superclasses, quantified_variables, kind_variables } + let type_variable_kinds = type_variables.iter().map(|binder| binder.kind).collect(); + Class { superclasses, type_variable_kinds, quantified_variables, kind_variables } }; state.binding_group.classes.insert(item_id, class); diff --git a/compiler-core/checking/src/core.rs b/compiler-core/checking/src/core.rs index b413f6d9..36585197 100644 --- a/compiler-core/checking/src/core.rs +++ b/compiler-core/checking/src/core.rs @@ -120,11 +120,13 @@ pub struct Instance { pub resolution: (FileId, TypeItemId), pub chain_id: InstanceChainId, pub chain_position: u32, + pub kind_variables: debruijn::Size, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Class { pub superclasses: Arc<[(TypeId, TypeId)]>, + pub type_variable_kinds: Vec, pub quantified_variables: debruijn::Size, pub kind_variables: debruijn::Size, } diff --git a/tests-integration/fixtures/checking/010_class_basic/Main.snap b/tests-integration/fixtures/checking/010_class_basic/Main.snap index 0206abd0..92cd9023 100644 --- a/tests-integration/fixtures/checking/010_class_basic/Main.snap +++ b/tests-integration/fixtures/checking/010_class_basic/Main.snap @@ -7,3 +7,6 @@ show :: forall (a :: Type). Show a => a -> String Types Show :: Type -> Constraint + +Classes +class Show (&0 :: Type) diff --git a/tests-integration/fixtures/checking/011_class_functor/Main.snap b/tests-integration/fixtures/checking/011_class_functor/Main.snap index a9f71ac0..5c055dd4 100644 --- a/tests-integration/fixtures/checking/011_class_functor/Main.snap +++ b/tests-integration/fixtures/checking/011_class_functor/Main.snap @@ -7,3 +7,6 @@ map :: forall (f :: Type -> Type) (a :: Type) (b :: Type). Functor f => (a -> b) Types Functor :: (Type -> Type) -> Constraint + +Classes +class Functor (&0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/012_class_monad_state/Main.snap b/tests-integration/fixtures/checking/012_class_monad_state/Main.snap index 42aa4f50..314a03e9 100644 --- a/tests-integration/fixtures/checking/012_class_monad_state/Main.snap +++ b/tests-integration/fixtures/checking/012_class_monad_state/Main.snap @@ -10,3 +10,7 @@ modify :: forall (s :: Type) (m :: Type -> Type). MonadState s m => (s -> s) -> Types Monad :: (Type -> Type) -> Constraint MonadState :: Type -> (Type -> Type) -> Constraint + +Classes +class Monad (&0 :: Type -> Type) +class Monad &1 <= MonadState (&0 :: Type) (&1 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/013_class_phantom/Main.snap b/tests-integration/fixtures/checking/013_class_phantom/Main.snap index 83ccb5d8..6f8fcf7c 100644 --- a/tests-integration/fixtures/checking/013_class_phantom/Main.snap +++ b/tests-integration/fixtures/checking/013_class_phantom/Main.snap @@ -7,3 +7,6 @@ value :: forall (t1 :: Type) (a :: t1). Phantom a => Int Types Phantom :: forall (t1 :: Type). t1 -> Constraint + +Classes +class Phantom (&1 :: &0) diff --git a/tests-integration/fixtures/checking/014_class_with_signature/Main.snap b/tests-integration/fixtures/checking/014_class_with_signature/Main.snap index a9f71ac0..5c055dd4 100644 --- a/tests-integration/fixtures/checking/014_class_with_signature/Main.snap +++ b/tests-integration/fixtures/checking/014_class_with_signature/Main.snap @@ -7,3 +7,6 @@ map :: forall (f :: Type -> Type) (a :: Type) (b :: Type). Functor f => (a -> b) Types Functor :: (Type -> Type) -> Constraint + +Classes +class Functor (&0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/015_class_superclass/Main.snap b/tests-integration/fixtures/checking/015_class_superclass/Main.snap index bd766f79..37d521b1 100644 --- a/tests-integration/fixtures/checking/015_class_superclass/Main.snap +++ b/tests-integration/fixtures/checking/015_class_superclass/Main.snap @@ -9,3 +9,7 @@ pure :: forall (f :: Type -> Type) (a :: Type). Applicative f => a -> f a Types Functor :: (Type -> Type) -> Constraint Applicative :: (Type -> Type) -> Constraint + +Classes +class Functor (&0 :: Type -> Type) +class Functor &0 <= Applicative (&0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/027_type_constrained/Main.snap b/tests-integration/fixtures/checking/027_type_constrained/Main.snap index 30aa1ebf..1ba397fd 100644 --- a/tests-integration/fixtures/checking/027_type_constrained/Main.snap +++ b/tests-integration/fixtures/checking/027_type_constrained/Main.snap @@ -14,3 +14,7 @@ Showable = Show Int => Int Quantified = :0 Kind = :0 Type = :0 + + +Classes +class Show (&0 :: Type) diff --git a/tests-integration/fixtures/checking/076_inspect_constraints/Main.snap b/tests-integration/fixtures/checking/076_inspect_constraints/Main.snap index 452e3ac1..49472a01 100644 --- a/tests-integration/fixtures/checking/076_inspect_constraints/Main.snap +++ b/tests-integration/fixtures/checking/076_inspect_constraints/Main.snap @@ -33,3 +33,8 @@ NestedConstraint = forall (a :: Type) (b :: Type). Show b => a -> b -> String Quantified = :0 Kind = :0 Type = :1 + + +Classes +class Show (&0 :: Type) +class Eq (&0 :: Type) diff --git a/tests-integration/fixtures/checking/083_instance_basic/Main.snap b/tests-integration/fixtures/checking/083_instance_basic/Main.snap index ff7d3f12..b1f67aa7 100644 --- a/tests-integration/fixtures/checking/083_instance_basic/Main.snap +++ b/tests-integration/fixtures/checking/083_instance_basic/Main.snap @@ -7,3 +7,10 @@ eq :: forall (a :: Type). Eq a => a -> a -> Boolean Types Eq :: Type -> Constraint + +Classes +class Eq (&0 :: Type) + +Instances +instance Eq &0 => Eq (Array &0 :: Type) + chain: 0 diff --git a/tests-integration/fixtures/checking/084_instance_eq/Main.snap b/tests-integration/fixtures/checking/084_instance_eq/Main.snap index 4c0aa8cb..895ded76 100644 --- a/tests-integration/fixtures/checking/084_instance_eq/Main.snap +++ b/tests-integration/fixtures/checking/084_instance_eq/Main.snap @@ -9,5 +9,12 @@ test :: Array Boolean Types Eq :: Type -> Constraint +Classes +class Eq (&0 :: Type) + +Instances +instance Eq (Int :: Type) + chain: 0 + Errors NoInstanceFound { Eq String } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/085_instance_functional_dependency/Main.snap b/tests-integration/fixtures/checking/085_instance_functional_dependency/Main.snap index c886923c..19b9c663 100644 --- a/tests-integration/fixtures/checking/085_instance_functional_dependency/Main.snap +++ b/tests-integration/fixtures/checking/085_instance_functional_dependency/Main.snap @@ -8,3 +8,10 @@ test :: String Types Convert :: Type -> Type -> Constraint + +Classes +class Convert (&0 :: Type) (&1 :: Type) + +Instances +instance Convert (Int :: Type) (String :: Type) + chain: 0 diff --git a/tests-integration/fixtures/checking/086_instance_functional_dependency_transitive/Main.snap b/tests-integration/fixtures/checking/086_instance_functional_dependency_transitive/Main.snap index bf85bee9..e4350fb1 100644 --- a/tests-integration/fixtures/checking/086_instance_functional_dependency_transitive/Main.snap +++ b/tests-integration/fixtures/checking/086_instance_functional_dependency_transitive/Main.snap @@ -8,3 +8,10 @@ test :: Boolean Types Chain :: forall (t4 :: Type). Type -> t4 -> Type -> Constraint + +Classes +class Chain (&1 :: Type) (&2 :: &0) (&3 :: Type) + +Instances +instance Chain (Int :: Type) (String :: Type) (Boolean :: Type) + chain: 0 diff --git a/tests-integration/fixtures/checking/087_instance_functional_dependency_multiple/Main.snap b/tests-integration/fixtures/checking/087_instance_functional_dependency_multiple/Main.snap index 5311ef66..f041f4f6 100644 --- a/tests-integration/fixtures/checking/087_instance_functional_dependency_multiple/Main.snap +++ b/tests-integration/fixtures/checking/087_instance_functional_dependency_multiple/Main.snap @@ -8,3 +8,12 @@ test :: Boolean Types TypeEq :: Type -> Type -> Type -> Constraint + +Classes +class TypeEq (&0 :: Type) (&1 :: Type) (&2 :: Type) + +Instances +instance TypeEq (Int :: Type) (Int :: Type) (Boolean :: Type) + chain: 0 +instance TypeEq (String :: Type) (String :: Type) (Boolean :: Type) + chain: 0 diff --git a/tests-integration/fixtures/checking/088_given_constraint_matching/Main.snap b/tests-integration/fixtures/checking/088_given_constraint_matching/Main.snap index 38c11fd7..e98cb00f 100644 --- a/tests-integration/fixtures/checking/088_given_constraint_matching/Main.snap +++ b/tests-integration/fixtures/checking/088_given_constraint_matching/Main.snap @@ -8,3 +8,6 @@ test :: forall (a :: Type). Eq a => a -> Boolean Types Eq :: Type -> Constraint + +Classes +class Eq (&0 :: Type) diff --git a/tests-integration/fixtures/checking/089_no_instance_found/Main.snap b/tests-integration/fixtures/checking/089_no_instance_found/Main.snap index 3e2fe865..f9ed950f 100644 --- a/tests-integration/fixtures/checking/089_no_instance_found/Main.snap +++ b/tests-integration/fixtures/checking/089_no_instance_found/Main.snap @@ -11,5 +11,8 @@ Types Eq :: Type -> Constraint Foo :: Type +Classes +class Eq (&0 :: Type) + Errors NoInstanceFound { Eq Foo } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/090_instance_improve/Main.snap b/tests-integration/fixtures/checking/090_instance_improve/Main.snap index 5ca0693f..2ae9ab29 100644 --- a/tests-integration/fixtures/checking/090_instance_improve/Main.snap +++ b/tests-integration/fixtures/checking/090_instance_improve/Main.snap @@ -10,3 +10,10 @@ Types True :: Type False :: Type TypeEq :: forall (t7 :: Type). Type -> Type -> t7 -> Constraint + +Classes +class TypeEq (&1 :: Type) (&2 :: Type) (&3 :: &0) + +Instances +instance TypeEq (&0 :: Type) (&0 :: Type) (True :: Type) + chain: 0 diff --git a/tests-integration/fixtures/checking/091_superclass_elaboration/Main.snap b/tests-integration/fixtures/checking/091_superclass_elaboration/Main.snap index 6473c2eb..72b0f69c 100644 --- a/tests-integration/fixtures/checking/091_superclass_elaboration/Main.snap +++ b/tests-integration/fixtures/checking/091_superclass_elaboration/Main.snap @@ -1,6 +1,5 @@ --- source: tests-integration/tests/checking/generated.rs -assertion_line: 12 expression: report --- Terms @@ -16,3 +15,7 @@ Types Eq :: Type -> Constraint Ord :: Type -> Constraint Ordering :: Type + +Classes +class Eq (&0 :: Type) +class Eq &0 <= Ord (&0 :: Type) diff --git a/tests-integration/fixtures/checking/092_ambiguous_constraint/Main.snap b/tests-integration/fixtures/checking/092_ambiguous_constraint/Main.snap index f60842db..6e909327 100644 --- a/tests-integration/fixtures/checking/092_ambiguous_constraint/Main.snap +++ b/tests-integration/fixtures/checking/092_ambiguous_constraint/Main.snap @@ -11,6 +11,10 @@ Types Read :: Type -> Constraint Show :: Type -> Constraint +Classes +class Read (&0 :: Type) +class Show (&0 :: Type) + Errors AmbiguousConstraint { Show ??? } at [TermDeclaration(Idx::(2))] AmbiguousConstraint { Read ??? } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/093_constraint_generalization/Main.snap b/tests-integration/fixtures/checking/093_constraint_generalization/Main.snap index 87c8bc7d..7b3e4421 100644 --- a/tests-integration/fixtures/checking/093_constraint_generalization/Main.snap +++ b/tests-integration/fixtures/checking/093_constraint_generalization/Main.snap @@ -11,3 +11,7 @@ test2 :: forall (t13 :: Type). Ord t13 => t13 -> t13 -> Int Types Eq :: Type -> Constraint Ord :: Type -> Constraint + +Classes +class Eq (&0 :: Type) +class Ord (&0 :: Type) diff --git a/tests-integration/fixtures/checking/094_let_binding_constraint_error/Main.snap b/tests-integration/fixtures/checking/094_let_binding_constraint_error/Main.snap index 02ac2668..abb15d7a 100644 --- a/tests-integration/fixtures/checking/094_let_binding_constraint_error/Main.snap +++ b/tests-integration/fixtures/checking/094_let_binding_constraint_error/Main.snap @@ -11,5 +11,8 @@ Types Foo :: Type Eq :: Type -> Constraint +Classes +class Eq (&0 :: Type) + Errors NoInstanceFound { Eq Foo } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/095_given_constraint_arityless/Main.snap b/tests-integration/fixtures/checking/095_given_constraint_arityless/Main.snap index bd1fa4f4..4a1a3d14 100644 --- a/tests-integration/fixtures/checking/095_given_constraint_arityless/Main.snap +++ b/tests-integration/fixtures/checking/095_given_constraint_arityless/Main.snap @@ -1,6 +1,5 @@ --- source: tests-integration/tests/checking/generated.rs -assertion_line: 12 expression: report --- Terms @@ -13,3 +12,7 @@ eqBoth :: forall (a :: Type) (b :: Type). Eq a => Eq b => a -> b -> Boolean Types Eq :: Type -> Constraint Coercible :: Type -> Type -> Constraint + +Classes +class Eq (&0 :: Type) +class Coercible (&0 :: Type) (&1 :: Type) diff --git a/tests-integration/fixtures/checking/096_given_functional_dependency/Main.snap b/tests-integration/fixtures/checking/096_given_functional_dependency/Main.snap index c7739aa0..704b0ff4 100644 --- a/tests-integration/fixtures/checking/096_given_functional_dependency/Main.snap +++ b/tests-integration/fixtures/checking/096_given_functional_dependency/Main.snap @@ -11,3 +11,7 @@ useRelate :: forall (y :: Type). Relate Int String y => y Types Convert :: Type -> Type -> Constraint Relate :: Type -> Type -> Type -> Constraint + +Classes +class Convert (&0 :: Type) (&1 :: Type) +class Relate (&0 :: Type) (&1 :: Type) (&2 :: Type) diff --git a/tests-integration/fixtures/checking/097_instance_chains/Main.snap b/tests-integration/fixtures/checking/097_instance_chains/Main.snap index ccea2a9c..a79609f3 100644 --- a/tests-integration/fixtures/checking/097_instance_chains/Main.snap +++ b/tests-integration/fixtures/checking/097_instance_chains/Main.snap @@ -11,3 +11,12 @@ test :: { testDiff :: Proxy @Boolean False, testSame :: Proxy @Boolean True } Types Proxy :: forall (t1 :: Type). t1 -> Type TypeEq :: forall (t3 :: Type) (t6 :: Type) (t7 :: Type). t3 -> t6 -> t7 -> Constraint + +Classes +class TypeEq (&3 :: &0) (&4 :: &1) (&5 :: &2) + +Instances +instance TypeEq (&1 :: &0) (&1 :: &0) (True :: Boolean) + chain: 0 +instance TypeEq (&2 :: &0) (&3 :: &1) (False :: Boolean) + chain: 1 diff --git a/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap b/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap index 11bce7ca..a9b3f96a 100644 --- a/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap +++ b/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap @@ -15,3 +15,21 @@ IsZero :: forall (t3 :: Type) (t5 :: Type). t3 -> t5 -> Constraint Z :: Type S :: Type -> Type And :: forall (t7 :: Type) (t10 :: Type) (t11 :: Type). t7 -> t10 -> t11 -> Constraint + +Classes +class IsZero (&2 :: &0) (&3 :: &1) +class And (&3 :: &0) (&4 :: &1) (&5 :: &2) + +Instances +instance IsZero (Z :: Type) (True :: Boolean) + chain: 0 +instance IsZero (S &0 :: Type) (False :: Boolean) + chain: 0 +instance And (True :: Boolean) (True :: Boolean) (True :: Boolean) + chain: 0 +instance And (True :: Boolean) (False :: Boolean) (False :: Boolean) + chain: 0 +instance And (False :: Boolean) (True :: Boolean) (False :: Boolean) + chain: 0 +instance And (False :: Boolean) (False :: Boolean) (False :: Boolean) + chain: 0 diff --git a/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap b/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap index 16eb89be..e0becbc4 100644 --- a/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap +++ b/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap @@ -25,5 +25,13 @@ Discard :: (Type -> Type) -> Constraint Bind :: (Type -> Type) -> Constraint Monad :: (Type -> Type) -> Constraint +Classes +class Functor (&0 :: Type -> Type) +class Functor &0 <= Apply (&0 :: Type -> Type) +class Apply &0 <= Applicative (&0 :: Type -> Type) +class Applicative &0 <= Discard (&0 :: Type -> Type) +class Applicative &0 <= Bind (&0 :: Type -> Type) +class Bind &0 <= Monad (&0 :: Type -> Type) + Errors NoInstanceFound { Discard &0 } at [TermDeclaration(Idx::(10))] diff --git a/tests-integration/fixtures/checking/118_instance_member_type_match/Main.snap b/tests-integration/fixtures/checking/118_instance_member_type_match/Main.snap index 0206abd0..62c9da09 100644 --- a/tests-integration/fixtures/checking/118_instance_member_type_match/Main.snap +++ b/tests-integration/fixtures/checking/118_instance_member_type_match/Main.snap @@ -7,3 +7,10 @@ show :: forall (a :: Type). Show a => a -> String Types Show :: Type -> Constraint + +Classes +class Show (&0 :: Type) + +Instances +instance Show (Int :: Type) + chain: 0 diff --git a/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap b/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap index cfa8efab..dcb2c56d 100644 --- a/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap +++ b/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap @@ -8,6 +8,13 @@ show :: forall (a :: Type). Show a => a -> String Types Show :: Type -> Constraint +Classes +class Show (&0 :: Type) + +Instances +instance Show (Int :: Type) + chain: 0 + Errors CannotUnify { Int, String } at [TermDeclaration(Idx::(1))] CannotUnify { Int -> Int, Int -> String } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/120_class_explicit_kind_variable/Main.snap b/tests-integration/fixtures/checking/120_class_explicit_kind_variable/Main.snap index 38fee5f1..751b0aa1 100644 --- a/tests-integration/fixtures/checking/120_class_explicit_kind_variable/Main.snap +++ b/tests-integration/fixtures/checking/120_class_explicit_kind_variable/Main.snap @@ -7,3 +7,12 @@ identity :: forall (k :: Type) (a :: Type) (b :: Type) (r :: k). TypeEq a b r => Types TypeEq :: forall (k :: Type). Type -> Type -> k -> Constraint + +Classes +class TypeEq (&1 :: Type) (&2 :: Type) (&3 :: &0) + +Instances +instance TypeEq (Int :: Type) (Int :: Type) (Int :: Type) + chain: 0 +instance TypeEq (Boolean :: Type) (Boolean :: Type) (Boolean :: Type) + chain: 0 diff --git a/tests-integration/fixtures/checking/121_instance_member_inner_forall/Main.snap b/tests-integration/fixtures/checking/121_instance_member_inner_forall/Main.snap index 2183f442..0f14d53f 100644 --- a/tests-integration/fixtures/checking/121_instance_member_inner_forall/Main.snap +++ b/tests-integration/fixtures/checking/121_instance_member_inner_forall/Main.snap @@ -9,3 +9,10 @@ Box :: forall (a :: Type). a -> Box a Types Functor :: (Type -> Type) -> Constraint Box :: Type -> Type + +Classes +class Functor (&0 :: Type -> Type) + +Instances +instance Functor (Box :: Type -> Type) + chain: 0 diff --git a/tests-integration/fixtures/checking/122_instance_member_inner_forall_constraint/Main.snap b/tests-integration/fixtures/checking/122_instance_member_inner_forall_constraint/Main.snap index 84565467..1509291d 100644 --- a/tests-integration/fixtures/checking/122_instance_member_inner_forall_constraint/Main.snap +++ b/tests-integration/fixtures/checking/122_instance_member_inner_forall_constraint/Main.snap @@ -15,3 +15,13 @@ Show :: Type -> Constraint Functor :: (Type -> Type) -> Constraint Box :: Type -> Type Maybe :: Type -> Type + +Classes +class Show (&0 :: Type) +class Functor (&0 :: Type -> Type) + +Instances +instance Functor (Box :: Type -> Type) + chain: 0 +instance Functor (Maybe :: Type -> Type) + chain: 0 diff --git a/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap b/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap index 028d0b2d..4769eab6 100644 --- a/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap +++ b/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap @@ -8,5 +8,14 @@ pair :: forall (a :: Type) (b :: Type). Pair a b => a -> b -> { a :: a, b :: b } Types Pair :: Type -> Type -> Constraint +Classes +class Pair (&0 :: Type) (&1 :: Type) + +Instances +instance Pair (Int :: Type) (String :: Type) + chain: 0 +instance Pair (Int :: Type) + chain: 0 + Errors InstanceHeadMismatch { class_file: Idx::(9), class_item: Idx::(0), expected: 2, actual: 1 } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/124_instance_member_missing_constraint/Main.snap b/tests-integration/fixtures/checking/124_instance_member_missing_constraint/Main.snap index 7a38f8d9..7171b982 100644 --- a/tests-integration/fixtures/checking/124_instance_member_missing_constraint/Main.snap +++ b/tests-integration/fixtures/checking/124_instance_member_missing_constraint/Main.snap @@ -12,5 +12,13 @@ Show :: Type -> Constraint Functor :: (Type -> Type) -> Constraint Box :: Type -> Type +Classes +class Show (&0 :: Type) +class Functor (&0 :: Type -> Type) + +Instances +instance Functor (Box :: Type -> Type) + chain: 0 + Errors NoInstanceFound { Show ~&1 } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap b/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap index 651697ab..963d1011 100644 --- a/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap +++ b/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap @@ -8,6 +8,13 @@ show :: forall (a :: Type). Show a => a -> String Types Show :: Type -> Constraint +Classes +class Show (&0 :: Type) + +Instances +instance Show (Boolean :: Type) + chain: 0 + Errors CannotUnify { forall (a :: Type). a -> String, Boolean -> String } at [TermDeclaration(Idx::(1))] InstanceMemberTypeMismatch { expected: Id(32), actual: Id(35) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/126_instance_phantom/Main.purs b/tests-integration/fixtures/checking/126_instance_phantom/Main.purs new file mode 100644 index 00000000..58f47fb6 --- /dev/null +++ b/tests-integration/fixtures/checking/126_instance_phantom/Main.purs @@ -0,0 +1,11 @@ +module Main where + +class Phantom a where + identity :: a -> a + +data Proxy a = Proxy + +instance Phantom (Proxy a) where + identity a = a + +forceSolve = { solution: identity Proxy } diff --git a/tests-integration/fixtures/checking/126_instance_phantom/Main.snap b/tests-integration/fixtures/checking/126_instance_phantom/Main.snap new file mode 100644 index 00000000..73828956 --- /dev/null +++ b/tests-integration/fixtures/checking/126_instance_phantom/Main.snap @@ -0,0 +1,19 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +identity :: forall (a :: Type). Phantom a => a -> a +Proxy :: forall (t3 :: Type) (a :: t3). Proxy @t3 a +forceSolve :: forall (t10 :: Type) (t11 :: t10). { solution :: Proxy @t10 t11 } + +Types +Phantom :: Type -> Constraint +Proxy :: forall (t3 :: Type). t3 -> Type + +Classes +class Phantom (&0 :: Type) + +Instances +instance Phantom (Proxy @&0 &1 :: Type) + chain: 0 diff --git a/tests-integration/src/generated/basic.rs b/tests-integration/src/generated/basic.rs index b6f7aa2f..993ed69a 100644 --- a/tests-integration/src/generated/basic.rs +++ b/tests-integration/src/generated/basic.rs @@ -3,7 +3,7 @@ use std::fmt::Write; use analyzer::{QueryEngine, locate}; use checking::core::pretty; use files::FileId; -use indexing::{ImportKind, TermItem, TypeItem}; +use indexing::{ImportKind, TermItem, TypeItem, TypeItemKind}; use lowering::{ ExpressionKind, GraphNode, ImplicitTypeVariable, TermVariableResolution, TypeKind, TypeVariableResolution, @@ -310,6 +310,94 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { writeln!(snapshot).unwrap(); } + if !checked.classes.is_empty() { + writeln!(snapshot, "\nClasses").unwrap(); + } + for (type_id, TypeItem { name, kind, .. }) in indexed.items.iter_types() { + let TypeItemKind::Class { .. } = kind else { continue }; + let Some(name) = name else { continue }; + let Some(class) = checked.lookup_class(type_id) else { continue }; + + let mut class_line = String::new(); + + // Print superclasses first (before <=) + if !class.superclasses.is_empty() { + for (i, (superclass_type, _)) in class.superclasses.iter().enumerate() { + if i > 0 { + class_line.push_str(", "); + } + let type_str = pretty::print_global(engine, *superclass_type); + class_line.push_str(&type_str); + } + class_line.push_str(" <= "); + } + + class_line.push_str(name); + + // Print class type variables with their kinds + // level = quantified_variables + kind_variables + index (matches localize_class) + for (index, &kind) in class.type_variable_kinds.iter().enumerate() { + let level = class.quantified_variables.0 + class.kind_variables.0 + index as u32; + let kind_str = pretty::print_global(engine, kind); + class_line.push_str(&format!(" (&{level} :: {kind_str})")); + } + + writeln!(snapshot, "class {class_line}").unwrap(); + } + + if !checked.instances.is_empty() { + writeln!(snapshot, "\nInstances").unwrap(); + } + let mut instance_entries: Vec<_> = checked.instances.iter().collect(); + instance_entries.sort_by_key(|(id, _)| format!("{:?}", id)); + for (_instance_id, instance) in instance_entries { + // Build instance head: constraints => ClassName (arg :: kind)... + let (class_file, class_type_id) = instance.resolution; + let class_name: String = if class_file == id { + indexed.items[class_type_id] + .name + .as_ref() + .map(|s| s.to_string()) + .unwrap_or_else(|| "".to_string()) + } else { + engine + .indexed(class_file) + .ok() + .and_then(|idx| idx.items[class_type_id].name.as_ref().map(|s| s.to_string())) + .unwrap_or_else(|| "".to_string()) + }; + + let mut head = String::new(); + + // Print constraints (without kinds) + if !instance.constraints.is_empty() { + let constraints: Vec<_> = instance + .constraints + .iter() + .map(|(t, _)| pretty::print_global(engine, *t)) + .collect(); + if constraints.len() == 1 { + head.push_str(&constraints[0]); + } else { + head.push('('); + head.push_str(&constraints.join(", ")); + head.push(')'); + } + head.push_str(" => "); + } + + // Print class name and arguments with kinds + head.push_str(&class_name); + for (arg_type, arg_kind) in &instance.arguments { + let type_str = pretty::print_global(engine, *arg_type); + let kind_str = pretty::print_global(engine, *arg_kind); + head.push_str(&format!(" ({type_str} :: {kind_str})")); + } + + writeln!(snapshot, "instance {head}").unwrap(); + writeln!(snapshot, " chain: {}", instance.chain_position).unwrap(); + } + if !checked.errors.is_empty() { writeln!(snapshot, "\nErrors").unwrap(); } diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 3a484515..771bca05 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -261,3 +261,5 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_124_instance_member_missing_constraint_main() { run_test("124_instance_member_missing_constraint", "Main"); } #[rustfmt::skip] #[test] fn test_125_instance_member_overly_general_main() { run_test("125_instance_member_overly_general", "Main"); } + +#[rustfmt::skip] #[test] fn test_126_instance_phantom_main() { run_test("126_instance_phantom", "Main"); } From 66baf5bd4abe6524533dee50a7de2b30665145fd Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Thu, 8 Jan 2026 02:31:51 +0800 Subject: [PATCH 04/62] Add newtype_token to DeriveDeclaration --- compiler-core/syntax/src/cst.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler-core/syntax/src/cst.rs b/compiler-core/syntax/src/cst.rs index 71f91ab5..a7658851 100644 --- a/compiler-core/syntax/src/cst.rs +++ b/compiler-core/syntax/src/cst.rs @@ -421,6 +421,11 @@ has_child!( | instance_statements() -> InstanceStatements ); +has_token!( + DeriveDeclaration + | newtype_token() -> NEWTYPE +); + has_children!( InstanceConstraints | children() -> Type From d70eecbf38a71d069caae1352c73919bf72de1f0 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Thu, 8 Jan 2026 02:35:46 +0800 Subject: [PATCH 05/62] Add newtype field to TermItemIr::Derive --- compiler-core/lowering/src/algorithm.rs | 4 +++- compiler-core/lowering/src/intermediate.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler-core/lowering/src/algorithm.rs b/compiler-core/lowering/src/algorithm.rs index abae08d7..8192984f 100644 --- a/compiler-core/lowering/src/algorithm.rs +++ b/compiler-core/lowering/src/algorithm.rs @@ -365,6 +365,8 @@ fn lower_term_item(state: &mut State, context: &Context, item_id: TermItemId, it TermItemKind::Derive { id } => { let cst = context.stabilized.ast_ptr(*id).and_then(|cst| cst.try_to_node(context.root)); + let newtype = cst.as_ref().map(|cst| cst.newtype_token().is_some()).unwrap_or(false); + let arguments = recover! { let head = cst.as_ref()?.instance_head()?; state.push_implicit_scope(); @@ -384,7 +386,7 @@ fn lower_term_item(state: &mut State, context: &Context, item_id: TermItemId, it .collect() }; - let kind = TermItemIr::Derive { constraints, arguments }; + let kind = TermItemIr::Derive { newtype, constraints, arguments }; state.info.term_item.insert(item_id, kind); } diff --git a/compiler-core/lowering/src/intermediate.rs b/compiler-core/lowering/src/intermediate.rs index 74ca93f9..55a6cc68 100644 --- a/compiler-core/lowering/src/intermediate.rs +++ b/compiler-core/lowering/src/intermediate.rs @@ -317,6 +317,7 @@ pub enum TermItemIr { arguments: Arc<[TypeId]>, }, Derive { + newtype: bool, constraints: Arc<[TypeId]>, arguments: Arc<[TypeId]>, }, From 9292b251267ea020cb6e5e68aa2a259e07f0c772 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Thu, 8 Jan 2026 02:48:00 +0800 Subject: [PATCH 06/62] Add resolution field to TermItemIr::Derive --- compiler-core/lowering/src/algorithm.rs | 21 ++++++++++++--------- compiler-core/lowering/src/intermediate.rs | 1 + 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/compiler-core/lowering/src/algorithm.rs b/compiler-core/lowering/src/algorithm.rs index 8192984f..851402c5 100644 --- a/compiler-core/lowering/src/algorithm.rs +++ b/compiler-core/lowering/src/algorithm.rs @@ -367,6 +367,14 @@ fn lower_term_item(state: &mut State, context: &Context, item_id: TermItemId, it let newtype = cst.as_ref().map(|cst| cst.newtype_token().is_some()).unwrap_or(false); + let resolution = cst.as_ref().and_then(|cst| { + let head = cst.instance_head()?; + let qualified = head.qualified()?; + let (qualifier, name) = + recursive::lower_qualified_name(&qualified, cst::QualifiedName::upper)?; + state.resolve_type_reference(context, qualifier.as_deref(), &name) + }); + let arguments = recover! { let head = cst.as_ref()?.instance_head()?; state.push_implicit_scope(); @@ -386,7 +394,7 @@ fn lower_term_item(state: &mut State, context: &Context, item_id: TermItemId, it .collect() }; - let kind = TermItemIr::Derive { newtype, constraints, arguments }; + let kind = TermItemIr::Derive { newtype, constraints, resolution, arguments }; state.info.term_item.insert(item_id, kind); } @@ -405,7 +413,7 @@ fn lower_term_item(state: &mut State, context: &Context, item_id: TermItemId, it TermItemKind::Instance { id } => { let cst = context.stabilized.ast_ptr(*id).and_then(|cst| cst.try_to_node(context.root)); - let class_resolution = cst.as_ref().and_then(|cst| { + let resolution = cst.as_ref().and_then(|cst| { let head = cst.instance_head()?; let qualified = head.qualified()?; let (qualifier, name) = @@ -434,15 +442,10 @@ fn lower_term_item(state: &mut State, context: &Context, item_id: TermItemId, it let members = recover! { let statements = cst.as_ref()?.instance_statements()?; - lower_instance_statements(state, context, &statements, class_resolution) + lower_instance_statements(state, context, &statements, resolution) }; - let kind = TermItemIr::Instance { - constraints, - resolution: class_resolution, - arguments, - members, - }; + let kind = TermItemIr::Instance { constraints, resolution, arguments, members }; state.info.term_item.insert(item_id, kind); } diff --git a/compiler-core/lowering/src/intermediate.rs b/compiler-core/lowering/src/intermediate.rs index 55a6cc68..21c73438 100644 --- a/compiler-core/lowering/src/intermediate.rs +++ b/compiler-core/lowering/src/intermediate.rs @@ -319,6 +319,7 @@ pub enum TermItemIr { Derive { newtype: bool, constraints: Arc<[TypeId]>, + resolution: Option<(FileId, TypeItemId)>, arguments: Arc<[TypeId]>, }, Foreign { From b6883e4d74b84bf281cb7e4538f113ecd0266a89 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Thu, 8 Jan 2026 03:46:41 +0800 Subject: [PATCH 07/62] Add and use InstanceKind --- compiler-core/checking/src/algorithm/constraint.rs | 11 ++++++++--- compiler-core/checking/src/algorithm/quantify.rs | 11 +++++------ compiler-core/checking/src/algorithm/term_item.rs | 8 +++----- compiler-core/checking/src/core.rs | 9 +++++++-- tests-integration/src/generated/basic.rs | 4 +++- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index 4db7ca33..bc4a0945 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -20,7 +20,7 @@ use rustc_hash::FxHashMap; use crate::algorithm::fold::{FoldAction, TypeFold, fold_type}; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::{transfer, unification}; -use crate::core::{Class, Instance, Variable, debruijn}; +use crate::core::{Class, Instance, InstanceKind, Variable, debruijn}; use crate::{CheckedModule, ExternalQueries, Type, TypeId}; pub fn solve_constraints( @@ -250,12 +250,17 @@ where let mut grouped: FxHashMap<_, Vec<_>> = FxHashMap::default(); for instance in instances { - grouped.entry(instance.chain_id).or_default().push(instance); + if let InstanceKind::Chain { id, .. } = instance.kind { + grouped.entry(id).or_default().push(instance); + } } let mut result: Vec> = grouped.into_values().collect(); for chain in &mut result { - chain.sort_by_key(|instance| instance.chain_position); + chain.sort_by_key(|instance| match instance.kind { + InstanceKind::Chain { position, .. } => position, + InstanceKind::Derive => 0, + }); } Ok(result) diff --git a/compiler-core/checking/src/algorithm/quantify.rs b/compiler-core/checking/src/algorithm/quantify.rs index 50a18324..0279fd9a 100644 --- a/compiler-core/checking/src/algorithm/quantify.rs +++ b/compiler-core/checking/src/algorithm/quantify.rs @@ -253,10 +253,7 @@ pub fn quantify_class(state: &mut CheckState, class: &mut Class) -> Option Option { +pub fn quantify_instance(state: &mut CheckState, instance: &mut Instance) -> Option<()> { let mut graph = UniGraph::default(); for (t, k) in &instance.arguments { @@ -270,7 +267,7 @@ pub fn quantify_instance( } if graph.node_count() == 0 { - return Some(debruijn::Size(0)); + return Some(()); } let unsolved = ordered_toposort(&graph, state)?; @@ -303,7 +300,9 @@ pub fn quantify_instance( instance.constraints = constraints.collect(); - Some(size) + instance.kind_variables = size; + + Some(()) } /// Builds a topological sort of the [`UniGraph`]. diff --git a/compiler-core/checking/src/algorithm/term_item.rs b/compiler-core/checking/src/algorithm/term_item.rs index 6b412d1a..1aff0855 100644 --- a/compiler-core/checking/src/algorithm/term_item.rs +++ b/compiler-core/checking/src/algorithm/term_item.rs @@ -13,7 +13,7 @@ use crate::algorithm::state::{CheckContext, CheckState, InstanceHeadBinding}; use crate::algorithm::{ constraint, inspect, kind, quantify, substitute, term, transfer, unification, }; -use crate::core::{Instance, Type, TypeId, Variable, debruijn, pretty}; +use crate::core::{Instance, InstanceKind, Type, TypeId, Variable, debruijn}; use crate::error::{ErrorKind, ErrorStep}; /// Checks signature declarations for terms. @@ -165,13 +165,11 @@ where arguments: core_arguments, constraints: core_constraints, resolution: (class_file, class_item), - chain_id, - chain_position, + kind: InstanceKind::Chain { id: chain_id, position: chain_position }, kind_variables: debruijn::Size(0), }; - instance.kind_variables = - quantify::quantify_instance(state, &mut instance).unwrap_or(debruijn::Size(0)); + quantify::quantify_instance(state, &mut instance); let arguments = instance.arguments.iter().map(|&(t, k)| { let t = transfer::globalize(state, context, t); diff --git a/compiler-core/checking/src/core.rs b/compiler-core/checking/src/core.rs index 36585197..7ae0ec7a 100644 --- a/compiler-core/checking/src/core.rs +++ b/compiler-core/checking/src/core.rs @@ -113,13 +113,18 @@ impl Synonym { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum InstanceKind { + Chain { id: InstanceChainId, position: u32 }, + Derive, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Instance { pub arguments: Vec<(TypeId, TypeId)>, pub constraints: Vec<(TypeId, TypeId)>, pub resolution: (FileId, TypeItemId), - pub chain_id: InstanceChainId, - pub chain_position: u32, + pub kind: InstanceKind, pub kind_variables: debruijn::Size, } diff --git a/tests-integration/src/generated/basic.rs b/tests-integration/src/generated/basic.rs index 993ed69a..c92b4251 100644 --- a/tests-integration/src/generated/basic.rs +++ b/tests-integration/src/generated/basic.rs @@ -395,7 +395,9 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { } writeln!(snapshot, "instance {head}").unwrap(); - writeln!(snapshot, " chain: {}", instance.chain_position).unwrap(); + if let checking::core::InstanceKind::Chain { position, .. } = instance.kind { + writeln!(snapshot, " chain: {}", position).unwrap(); + } } if !checked.errors.is_empty() { From 3c0f9a5e98486481275540bfa11400e07eb13d2e Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Thu, 8 Jan 2026 03:49:01 +0800 Subject: [PATCH 08/62] Add skeleton for derive checking --- compiler-core/checking/src/algorithm.rs | 28 +++++ .../checking/src/algorithm/term_item.rs | 106 ++++++++++++++++++ compiler-core/checking/src/lib.rs | 3 +- .../127_derive_eq_simple/Data.Eq.purs | 4 + .../checking/127_derive_eq_simple/Main.purs | 7 ++ .../checking/127_derive_eq_simple/Main.snap | 12 ++ tests-integration/src/generated/basic.rs | 51 +++++++++ tests-integration/tests/checking/generated.rs | 2 + 8 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 tests-integration/fixtures/checking/127_derive_eq_simple/Data.Eq.purs create mode 100644 tests-integration/fixtures/checking/127_derive_eq_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap diff --git a/compiler-core/checking/src/algorithm.rs b/compiler-core/checking/src/algorithm.rs index 7a0b807c..5c8bcb3e 100644 --- a/compiler-core/checking/src/algorithm.rs +++ b/compiler-core/checking/src/algorithm.rs @@ -68,6 +68,7 @@ pub fn check_source(queries: &impl ExternalQueries, file_id: FileId) -> QueryRes check_term_signatures(&mut state, &context)?; check_instance_heads(&mut state, &context)?; + check_derive_heads(&mut state, &context)?; check_value_groups(&mut state, &context)?; check_instance_members(&mut state, &context)?; @@ -346,6 +347,33 @@ where Ok(()) } +fn check_derive_heads( + state: &mut state::CheckState, + context: &state::CheckContext, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let items = context.lowered.term_scc.iter().flat_map(|scc| match scc { + Scc::Base(item) | Scc::Recursive(item) => slice::from_ref(item), + Scc::Mutual(items) => items.as_slice(), + }); + + for &item_id in items { + let Some(TermItemIr::Derive { constraints, arguments, resolution, .. }) = + context.lowered.info.get_term_item(item_id) + else { + continue; + }; + + let check_derive = term_item::CheckDerive { item_id, constraints, arguments, resolution }; + + term_item::check_derive(state, context, check_derive)?; + } + + Ok(()) +} + fn check_value_groups( state: &mut state::CheckState, context: &state::CheckContext, diff --git a/compiler-core/checking/src/algorithm/term_item.rs b/compiler-core/checking/src/algorithm/term_item.rs index 1aff0855..9674ad67 100644 --- a/compiler-core/checking/src/algorithm/term_item.rs +++ b/compiler-core/checking/src/algorithm/term_item.rs @@ -197,6 +197,112 @@ where }) } +pub struct CheckDerive<'a> { + pub item_id: TermItemId, + pub constraints: &'a [lowering::TypeId], + pub arguments: &'a [lowering::TypeId], + pub resolution: &'a Option<(FileId, TypeItemId)>, +} + +pub fn check_derive( + state: &mut CheckState, + context: &CheckContext, + input: CheckDerive<'_>, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let CheckDerive { item_id, constraints, arguments, resolution } = input; + state.with_error_step(ErrorStep::TermDeclaration(item_id), |state| { + let Some((class_file, class_item)) = *resolution else { + return Ok(()); + }; + + let TermItemKind::Derive { id: derive_id } = context.indexed.items[item_id].kind else { + return Ok(()); + }; + + // Save the current size of the environment for unbinding. + let size = state.type_scope.size(); + + let class_kind = kind::lookup_file_type(state, context, class_file, class_item)?; + let expected_kinds = instantiate_class_kind(state, context, class_kind)?; + + if expected_kinds.len() != arguments.len() { + state.insert_error(ErrorKind::InstanceHeadMismatch { + class_file, + class_item, + expected: expected_kinds.len(), + actual: arguments.len(), + }); + } + + let mut implicit_kinds = FxHashMap::default(); + + let mut core_arguments = vec![]; + for (argument, expected_kind) in arguments.iter().zip(expected_kinds) { + let (inferred_type, inferred_kind) = + kind::check_surface_kind(state, context, *argument, expected_kind)?; + + // Unify kinds for Implicit and Bound variables, this instance + // declarations like `097_instance_chains` to infer with the + // correct kind annotations for the same variables. + if let Type::Variable(Variable::Implicit(level) | Variable::Bound(level)) = + state.storage[inferred_type] + { + if let Some(bound_kind) = implicit_kinds.get(&level) { + let _ = unification::unify(state, context, inferred_kind, *bound_kind)?; + } else { + implicit_kinds.insert(level, inferred_kind); + } + } + + core_arguments.push((inferred_type, inferred_kind)); + } + + let mut core_constraints = vec![]; + for constraint in constraints.iter() { + let (inferred_type, inferred_kind) = + kind::infer_surface_kind(state, context, *constraint)?; + core_constraints.push((inferred_type, inferred_kind)); + } + + let mut instance = Instance { + arguments: core_arguments, + constraints: core_constraints, + resolution: (class_file, class_item), + kind: InstanceKind::Derive, + kind_variables: debruijn::Size(0), + }; + + quantify::quantify_instance(state, &mut instance); + + let arguments = instance.arguments.iter().map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }); + + instance.arguments = arguments.collect(); + + let constraints = instance.constraints.iter().map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }); + + instance.constraints = constraints.collect(); + + state.checked.derived.insert(derive_id, instance); + + // Capture implicit variables from the instance head before unbinding. + let implicits = state.type_scope.unbind_implicits(debruijn::Level(size.0)); + state.surface_bindings.insert_instance_head(item_id, Arc::from(implicits)); + + Ok(()) + }) +} + /// Instantiates a class kind to extract the expected kinds for instance arguments. /// /// Class kinds have the form `forall k1 k2. T1 -> T2 -> ... -> Constraint` where: diff --git a/compiler-core/checking/src/lib.rs b/compiler-core/checking/src/lib.rs index c20707ee..5e4cb857 100644 --- a/compiler-core/checking/src/lib.rs +++ b/compiler-core/checking/src/lib.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use building_types::{QueryProxy, QueryResult}; use files::FileId; -use indexing::{IndexedModule, InstanceId, TermItemId, TypeItemId}; +use indexing::{DeriveId, IndexedModule, InstanceId, TermItemId, TypeItemId}; use lowering::LoweredModule; use resolving::ResolvedModule; use rustc_hash::FxHashMap; @@ -38,6 +38,7 @@ pub struct CheckedModule { pub operators: FxHashMap, pub synonyms: FxHashMap, pub instances: FxHashMap, + pub derived: FxHashMap, pub classes: FxHashMap, pub errors: Vec, diff --git a/tests-integration/fixtures/checking/127_derive_eq_simple/Data.Eq.purs b/tests-integration/fixtures/checking/127_derive_eq_simple/Data.Eq.purs new file mode 100644 index 00000000..c59d0788 --- /dev/null +++ b/tests-integration/fixtures/checking/127_derive_eq_simple/Data.Eq.purs @@ -0,0 +1,4 @@ +module Data.Eq where + +class Eq a where + eq :: a -> a -> Boolean diff --git a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.purs b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.purs new file mode 100644 index 00000000..f9dfdf82 --- /dev/null +++ b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Eq (class Eq) + +data Proxy a = Proxy + +derive instance Eq (Proxy a) diff --git a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap new file mode 100644 index 00000000..da0cfa0c --- /dev/null +++ b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap @@ -0,0 +1,12 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Proxy :: forall (t1 :: Type) (a :: t1). Proxy @t1 a + +Types +Proxy :: forall (t1 :: Type). t1 -> Type + +Derived +derive Eq (Proxy @&0 &1 :: Type) diff --git a/tests-integration/src/generated/basic.rs b/tests-integration/src/generated/basic.rs index c92b4251..c6ec4ecc 100644 --- a/tests-integration/src/generated/basic.rs +++ b/tests-integration/src/generated/basic.rs @@ -400,6 +400,57 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { } } + if !checked.derived.is_empty() { + writeln!(snapshot, "\nDerived").unwrap(); + } + let mut derived_entries: Vec<_> = checked.derived.iter().collect(); + derived_entries.sort_by_key(|(id, _)| format!("{:?}", id)); + for (_derive_id, instance) in derived_entries { + let (class_file, class_type_id) = instance.resolution; + let class_name: String = if class_file == id { + indexed.items[class_type_id] + .name + .as_ref() + .map(|s| s.to_string()) + .unwrap_or_else(|| "".to_string()) + } else { + engine + .indexed(class_file) + .ok() + .and_then(|idx| idx.items[class_type_id].name.as_ref().map(|s| s.to_string())) + .unwrap_or_else(|| "".to_string()) + }; + + let mut head = String::new(); + + // Print constraints (without kinds) + if !instance.constraints.is_empty() { + let constraints: Vec<_> = instance + .constraints + .iter() + .map(|(t, _)| pretty::print_global(engine, *t)) + .collect(); + if constraints.len() == 1 { + head.push_str(&constraints[0]); + } else { + head.push('('); + head.push_str(&constraints.join(", ")); + head.push(')'); + } + head.push_str(" => "); + } + + // Print class name and arguments with kinds + head.push_str(&class_name); + for (arg_type, arg_kind) in &instance.arguments { + let type_str = pretty::print_global(engine, *arg_type); + let kind_str = pretty::print_global(engine, *arg_kind); + head.push_str(&format!(" ({type_str} :: {kind_str})")); + } + + writeln!(snapshot, "derive {head}").unwrap(); + } + if !checked.errors.is_empty() { writeln!(snapshot, "\nErrors").unwrap(); } diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 771bca05..c59cbbc3 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -263,3 +263,5 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_125_instance_member_overly_general_main() { run_test("125_instance_member_overly_general", "Main"); } #[rustfmt::skip] #[test] fn test_126_instance_phantom_main() { run_test("126_instance_phantom", "Main"); } + +#[rustfmt::skip] #[test] fn test_127_derive_eq_simple_main() { run_test("127_derive_eq_simple", "Main"); } From 5c95de1076332935495948224d942458749e56d6 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Thu, 8 Jan 2026 04:53:09 +0800 Subject: [PATCH 09/62] Fix implicit variable resolution This fixes a bug in implicit variable resolution where variables in instance heads are all lowered into Implicit variables rather than becoming Bound. --- compiler-core/lowering/src/algorithm.rs | 13 +++++++------ .../lowering/007_instance_declaration/Main.snap | 5 ++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler-core/lowering/src/algorithm.rs b/compiler-core/lowering/src/algorithm.rs index 851402c5..b639ce89 100644 --- a/compiler-core/lowering/src/algorithm.rs +++ b/compiler-core/lowering/src/algorithm.rs @@ -270,20 +270,21 @@ impl State { fn resolve_type_variable(&mut self, id: TypeId, name: &str) -> Option { let node = self.graph_scope?; if let GraphNode::Implicit { collecting, bindings, .. } = &mut self.graph.inner[node] { - if *collecting { - let id = bindings.bind(name, id); + if let Some(id) = bindings.get(name) { Some(TypeVariableResolution::Implicit(ImplicitTypeVariable { - binding: true, + binding: false, node, id, })) - } else { - let id = bindings.get(name)?; + } else if *collecting { + let id = bindings.bind(name, id); Some(TypeVariableResolution::Implicit(ImplicitTypeVariable { - binding: false, + binding: true, node, id, })) + } else { + None } } else { self.graph.traverse(node).find_map(|(node, graph)| match graph { diff --git a/tests-integration/fixtures/lowering/007_instance_declaration/Main.snap b/tests-integration/fixtures/lowering/007_instance_declaration/Main.snap index 390b8729..82f9670d 100644 --- a/tests-integration/fixtures/lowering/007_instance_declaration/Main.snap +++ b/tests-integration/fixtures/lowering/007_instance_declaration/Main.snap @@ -19,7 +19,8 @@ a@Some(Position { line: 6, character: 11 }) resolves to a constraint variable "a" Some(Position { line: 6, character: 26 }) b@Some(Position { line: 10, character: 21 }) - introduces a constraint variable "b" + resolves to a constraint variable "b" + Some(Position { line: 10, character: 19 }) a@Some(Position { line: 7, character: 24 }) resolves to a constraint variable "a" Some(Position { line: 6, character: 26 }) @@ -33,11 +34,9 @@ a@Some(Position { line: 7, character: 13 }) b@Some(Position { line: 11, character: 22 }) resolves to a constraint variable "b" Some(Position { line: 10, character: 19 }) - Some(Position { line: 10, character: 21 }) b@Some(Position { line: 11, character: 29 }) resolves to a constraint variable "b" Some(Position { line: 10, character: 19 }) - Some(Position { line: 10, character: 21 }) p@Some(Position { line: 11, character: 20 }) resolves to forall Some(Position { line: 11, character: 17 }) p@Some(Position { line: 11, character: 27 }) From 835d36be462faf9268fb30096b7052772a15ec21 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Thu, 8 Jan 2026 04:59:27 +0800 Subject: [PATCH 10/62] Add unification rule Implicit and Bound Following the changes in the previous commit, this adds subtyping and unification rules for Implicit and Bound variables. Previously, instance heads only contained Implicit variables which can be unified trivially. --- .../checking/src/algorithm/constraint.rs | 2 +- .../checking/src/algorithm/unification.rs | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index bc4a0945..6fcb147a 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -599,7 +599,7 @@ fn match_given_type(state: &mut CheckState, wanted: TypeId, given: TypeId) -> Ma } // Implicit and Bound variables at the same level represent the same - // logical variable. Implicit produces them, Bound consumes them. + // logical variable; Implicit binds the variable, Bound consumes it. (Type::Variable(Variable::Implicit(w_level)), Type::Variable(Variable::Bound(g_level))) | (Type::Variable(Variable::Bound(w_level)), Type::Variable(Variable::Implicit(g_level))) if w_level == g_level => diff --git a/compiler-core/checking/src/algorithm/unification.rs b/compiler-core/checking/src/algorithm/unification.rs index 1e712af3..ec3aa5cc 100644 --- a/compiler-core/checking/src/algorithm/unification.rs +++ b/compiler-core/checking/src/algorithm/unification.rs @@ -83,6 +83,17 @@ where subtype(state, context, inner, t2) } + // Implicit and Bound variables at the same level represent the same + // logical variable; Implicit binds the variable, Bound consumes it. + ( + Type::Variable(Variable::Implicit(t1_level)), + Type::Variable(Variable::Bound(t2_level)), + ) + | ( + Type::Variable(Variable::Bound(t1_level)), + Type::Variable(Variable::Implicit(t2_level)), + ) => Ok(t1_level == t2_level), + (_, _) => unify(state, context, t1, t2), } } @@ -145,6 +156,17 @@ where solve(state, context, unification_id, t1)?.is_some() } + // Implicit and Bound variables at the same level represent the same + // logical variable; Implicit binds the variable, Bound consumes it. + ( + Type::Variable(Variable::Implicit(t1_level)), + Type::Variable(Variable::Bound(t2_level)), + ) + | ( + Type::Variable(Variable::Bound(t1_level)), + Type::Variable(Variable::Implicit(t2_level)), + ) => t1_level == t2_level, + ( Type::Constrained(t1_constraint, t1_inner), Type::Constrained(t2_constraint, t2_inner), From 516eca4b651a3b21f184c204ca97b520704f79ea Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Thu, 8 Jan 2026 05:26:45 +0800 Subject: [PATCH 11/62] Remove erroneous fix for unifying instance head kinds This removes an erroneous fix now that the lowering properly emits Implicit / Bound variables in the instance head, which means that the `a` in `instance TypeEq a a` now refer to the same logical variable instead of binding `a` twice. --- .../checking/src/algorithm/term_item.rs | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/compiler-core/checking/src/algorithm/term_item.rs b/compiler-core/checking/src/algorithm/term_item.rs index 9674ad67..6d5223fd 100644 --- a/compiler-core/checking/src/algorithm/term_item.rs +++ b/compiler-core/checking/src/algorithm/term_item.rs @@ -5,7 +5,6 @@ use files::FileId; use indexing::{TermItemId, TermItemKind, TypeItemId}; use itertools::Itertools; use lowering::TermItemIr; -use rustc_hash::FxHashMap; use crate::ExternalQueries; use crate::algorithm::kind::synonym; @@ -131,26 +130,10 @@ where }); } - let mut implicit_kinds = FxHashMap::default(); - let mut core_arguments = vec![]; for (argument, expected_kind) in arguments.iter().zip(expected_kinds) { let (inferred_type, inferred_kind) = kind::check_surface_kind(state, context, *argument, expected_kind)?; - - // Unify kinds for Implicit and Bound variables, this instance - // declarations like `097_instance_chains` to infer with the - // correct kind annotations for the same variables. - if let Type::Variable(Variable::Implicit(level) | Variable::Bound(level)) = - state.storage[inferred_type] - { - if let Some(bound_kind) = implicit_kinds.get(&level) { - let _ = unification::unify(state, context, inferred_kind, *bound_kind)?; - } else { - implicit_kinds.insert(level, inferred_kind); - } - } - core_arguments.push((inferred_type, inferred_kind)); } @@ -237,26 +220,10 @@ where }); } - let mut implicit_kinds = FxHashMap::default(); - let mut core_arguments = vec![]; for (argument, expected_kind) in arguments.iter().zip(expected_kinds) { let (inferred_type, inferred_kind) = kind::check_surface_kind(state, context, *argument, expected_kind)?; - - // Unify kinds for Implicit and Bound variables, this instance - // declarations like `097_instance_chains` to infer with the - // correct kind annotations for the same variables. - if let Type::Variable(Variable::Implicit(level) | Variable::Bound(level)) = - state.storage[inferred_type] - { - if let Some(bound_kind) = implicit_kinds.get(&level) { - let _ = unification::unify(state, context, inferred_kind, *bound_kind)?; - } else { - implicit_kinds.insert(level, inferred_kind); - } - } - core_arguments.push((inferred_type, inferred_kind)); } From 6f036231a85ef43105a0a7282143726291faa683 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Sun, 11 Jan 2026 00:50:57 +0800 Subject: [PATCH 12/62] Identify compiler-known types like Eq and Eq1 --- compiler-core/checking/src/algorithm/state.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index f5ab4ea4..26477728 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -344,6 +344,7 @@ where pub prim_symbol: PrimSymbolCore, pub prim_row: PrimRowCore, pub prim_row_list: PrimRowListCore, + pub known_types: KnownTypesCore, pub id: FileId, pub indexed: Arc, @@ -373,6 +374,7 @@ where let prim_symbol = PrimSymbolCore::collect(queries, state)?; let prim_row = PrimRowCore::collect(queries, state)?; let prim_row_list = PrimRowListCore::collect(queries, state)?; + let known_types = KnownTypesCore::collect(queries)?; let prim_id = queries.prim_id(); let prim_indexed = queries.indexed(prim_id)?; Ok(CheckContext { @@ -383,6 +385,7 @@ where prim_symbol, prim_row, prim_row_list, + known_types, id, indexed, lowered, @@ -606,6 +609,34 @@ impl PrimRowListCore { } } +fn fetch_known_type( + queries: &impl ExternalQueries, + m: &str, + n: &str, +) -> QueryResult> { + let Some(file_id) = queries.module_file(m) else { + return Ok(None); + }; + let resolved = queries.resolved(file_id)?; + let Some((file_id, type_id)) = resolved.exports.lookup_type(n) else { + return Ok(None); + }; + Ok(Some((file_id, type_id))) +} + +pub struct KnownTypesCore { + pub eq: Option<(FileId, TypeItemId)>, + pub eq1: Option<(FileId, TypeItemId)>, +} + +impl KnownTypesCore { + fn collect(queries: &impl ExternalQueries) -> QueryResult { + let eq = fetch_known_type(queries, "Data.Eq", "Eq")?; + let eq1 = fetch_known_type(queries, "Data.Eq", "Eq1")?; + Ok(KnownTypesCore { eq, eq1 }) + } +} + impl CheckState { pub fn term_binding_group( &mut self, From fa09001c073f1c4e9fe984e94d8de3c2d0693a68 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Sun, 11 Jan 2026 11:52:18 +0800 Subject: [PATCH 13/62] Collect quantified variable count for data-like --- compiler-core/checking/src/algorithm.rs | 2 + compiler-core/checking/src/algorithm/state.rs | 117 ++++++++++++++---- .../checking/src/algorithm/type_item.rs | 4 +- compiler-core/checking/src/core.rs | 6 + compiler-core/checking/src/lib.rs | 5 + .../checking/001_proxy_checking/Main.snap | 5 + .../checking/002_proxy_inference/Main.snap | 5 + .../checking/003_data_recursive/Main.snap | 5 + .../checking/004_data_mutual/Main.snap | 9 ++ .../checking/005_newtype_recursive/Main.snap | 5 + .../Main.snap | 10 ++ .../checking/044_binder_constructor/Main.snap | 13 ++ .../checking/045_do_discard/Main.snap | 5 + .../fixtures/checking/046_do_bind/Main.snap | 5 + .../checking/047_do_non_monadic/Main.snap | 5 + .../checking/049_ado_discard/Main.snap | 5 + .../fixtures/checking/050_ado_bind/Main.snap | 5 + .../051_ado_non_applicative/Main.snap | 5 + .../checking/053_do_polymorphic/Main.snap | 5 + .../checking/054_ado_polymorphic/Main.snap | 5 + .../058_binder_operator_chain/Main.snap | 5 + .../fixtures/checking/062_case_of/Main.snap | 13 ++ .../068_expression_sections/Main.snap | 5 + .../checking/089_no_instance_found/Main.snap | 6 + .../checking/090_instance_improve/Main.snap | 10 ++ .../091_superclass_elaboration/Main.snap | 6 + .../Main.snap | 6 + .../checking/097_instance_chains/Main.snap | 6 + .../checking/098_fundep_propagation/Main.snap | 6 + .../checking/099_builtin_int/Main.snap | 9 ++ .../checking/100_builtin_given/Main.snap | 14 +++ .../checking/101_builtin_symbol/Main.snap | 5 + .../checking/102_builtin_row/Main.snap | 5 + .../105_incomplete_type_signature/Main.snap | 5 + .../Main.snap | 5 + .../Main.snap | 5 + .../Main.snap | 5 + .../109_row_cons_invalid_discharged/Main.snap | 5 + .../Main.snap | 6 + .../111_int_add_invalid_no_instance/Main.snap | 6 + .../112_int_mul_invalid_no_instance/Main.snap | 6 + .../Main.snap | 6 + .../Main.snap | 6 + .../checking/117_do_ado_constrained/Main.snap | 6 + .../Main.snap | 6 + .../Main.snap | 10 ++ .../Main.snap | 6 + .../checking/126_instance_phantom/Main.snap | 6 + .../checking/127_derive_eq_simple/Main.snap | 6 + tests-integration/src/generated/basic.rs | 13 ++ 50 files changed, 401 insertions(+), 29 deletions(-) diff --git a/compiler-core/checking/src/algorithm.rs b/compiler-core/checking/src/algorithm.rs index 5c8bcb3e..4155ddf5 100644 --- a/compiler-core/checking/src/algorithm.rs +++ b/compiler-core/checking/src/algorithm.rs @@ -242,6 +242,8 @@ where if let Some(variable) = kind_variables.first() { state.type_scope.unbind(variable.level); } + + state.binding_group.data.insert(item_id, data); } Ok(()) diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index 26477728..91fbbbe0 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -17,7 +17,7 @@ use rustc_hash::FxHashMap; use sugar::{Bracketed, Sectioned}; use crate::algorithm::{constraint, quantify, transfer}; -use crate::core::{Class, ForallBinder, Synonym, Type, TypeId, TypeInterner, debruijn}; +use crate::core::{Class, DataLike, ForallBinder, Synonym, Type, TypeId, TypeInterner, debruijn}; use crate::error::{CheckError, ErrorKind, ErrorStep}; use crate::{CheckedModule, ExternalQueries}; @@ -290,7 +290,7 @@ pub struct CheckedConstructor { } #[derive(Clone)] -pub struct CheckedDataLike { +pub struct BindingDataLike { pub kind_variables: Vec, pub type_variables: Vec, pub result_kind: TypeId, @@ -304,7 +304,7 @@ pub struct BindingGroupContext { pub synonyms: FxHashMap, pub classes: FxHashMap, pub residual: FxHashMap>, - pub data: FxHashMap, + pub data: FxHashMap, } impl BindingGroupContext { @@ -676,6 +676,10 @@ impl CheckState { where Q: ExternalQueries, { + // Process terms to be committed into the CheckedModule. This loops over + // the BindingGroupContext::terms and generalises unsolved unification + // variables. It also takes into account the residual constraints that + // must be generalised or reported as errors. let mut residuals = mem::take(&mut self.binding_group.residual); for (item_id, type_id) in mem::take(&mut self.binding_group.terms) { let constraints = residuals.remove(&item_id).unwrap_or_default(); @@ -698,42 +702,101 @@ impl CheckState { } } - let mut classes = mem::take(&mut self.binding_group.classes); - for (item_id, type_id) in mem::take(&mut self.binding_group.types) { - if let Some((quantified_type, quantified_count)) = quantify::quantify(self, type_id) { - if let Some(mut class) = classes.remove(&item_id) { - let class_quantified_count = - quantify::quantify_class(self, &mut class).unwrap_or(debruijn::Size(0)); + // Process data/newtype to be committed into the CheckedModule. This loops + // over the BindingGroupContext::data and generalises unsolved unification + // variables in their kinds. DataLike is inserted in CheckedModule::data + // containing the number of quantified and kind variables in the type. + // These are tracked so that the check_derive rule knows exactly how many + // kind variables need to be instantiated into unification variables. + for (item_id, data_like) in mem::take(&mut self.binding_group.data) { + let Some(type_id) = self.binding_group.types.remove(&item_id) else { + continue; + }; + + let Some((quantified_type, quantified_variables)) = quantify::quantify(self, type_id) + else { + continue; + }; + + let kind_count = data_like.kind_variables.len() as u32; + let kind_variables = debruijn::Size(kind_count); + + let data_like = DataLike { quantified_variables, kind_variables }; + self.checked.data.insert(item_id, data_like); + + let type_id = transfer::globalize(self, context, quantified_type); + self.checked.types.insert(item_id, type_id); + } - debug_assert_eq!( - quantified_count, class_quantified_count, - "critical violation: class type signature and declaration should have the same number of variables" - ); + // Process class to be committed into the CheckedModule. This loops over + // the BindingGroupContext::class and generalises unsolved unification + // variables in their kinds. Like DataLike, it stores the number of + // quantified and kind variables in the type. This also generalises + // the class head itself since class head variables may have unsolved + // unification variables on inference mode. + for (item_id, mut class) in mem::take(&mut self.binding_group.classes) { + let Some(type_id) = self.binding_group.types.remove(&item_id) else { + continue; + }; + + let Some((quantified_type, quantified_variables)) = quantify::quantify(self, type_id) + else { + continue; + }; + + let class_quantified_count = + quantify::quantify_class(self, &mut class).unwrap_or(debruijn::Size(0)); + + debug_assert_eq!( + quantified_variables, class_quantified_count, + "critical violation: class type signature and declaration should have the same number of variables" + ); + + class.quantified_variables = quantified_variables; + + let superclasses = class.superclasses.iter().map(|&(t, k)| { + let t = transfer::globalize(self, context, t); + let k = transfer::globalize(self, context, k); + (t, k) + }); - class.quantified_variables = quantified_count; + class.superclasses = superclasses.collect(); - let superclasses = class.superclasses.iter().map(|&(t, k)| { - let t = transfer::globalize(self, context, t); - let k = transfer::globalize(self, context, k); - (t, k) - }); + let type_variable_kinds = class + .type_variable_kinds + .iter() + .map(|&kind| transfer::globalize(self, context, kind)); - class.superclasses = superclasses.collect(); + class.type_variable_kinds = type_variable_kinds.collect(); - let type_variable_kinds = class - .type_variable_kinds - .iter() - .map(|&kind| transfer::globalize(self, context, kind)); + self.checked.classes.insert(item_id, class); - class.type_variable_kinds = type_variable_kinds.collect(); + let type_id = transfer::globalize(self, context, quantified_type); + self.checked.types.insert(item_id, type_id); + } - self.checked.classes.insert(item_id, class); - } + for (item_id, type_id) in mem::take(&mut self.binding_group.types) { + if let Some((quantified_type, _)) = quantify::quantify(self, type_id) { let type_id = transfer::globalize(self, context, quantified_type); self.checked.types.insert(item_id, type_id); } } + // Process synonym to be committed into the CheckedModule. This loops + // over the BindingGroupContext::synonyms and generalises the synonym + // body itself. Kind generalisation is already handled in the previous + // loop. The compiler stores synonym bodies as generalised types to + // take advantage of existing patterns for instantiation, e.g. + // + // ``` + // type Fn a b = a -> b + // ``` + // + // is just stored as the following type: + // + // ``` + // forall a b. a -> b + // ``` for (item_id, mut synonym) in mem::take(&mut self.binding_group.synonyms) { if let Some((synonym_type, quantified_variables)) = quantify::quantify(self, synonym.synonym_type) diff --git a/compiler-core/checking/src/algorithm/type_item.rs b/compiler-core/checking/src/algorithm/type_item.rs index 153f91a9..91ab0489 100644 --- a/compiler-core/checking/src/algorithm/type_item.rs +++ b/compiler-core/checking/src/algorithm/type_item.rs @@ -9,7 +9,7 @@ use lowering::{ use smol_str::SmolStr; use crate::ExternalQueries; -use crate::algorithm::state::{CheckContext, CheckState, CheckedConstructor, CheckedDataLike}; +use crate::algorithm::state::{BindingDataLike, CheckContext, CheckState, CheckedConstructor}; use crate::algorithm::{inspect, kind, unification}; use crate::core::{Class, ForallBinder, Operator, Synonym, Type, TypeId, Variable, debruijn}; use crate::error::{ErrorKind, ErrorStep}; @@ -254,7 +254,7 @@ where let type_unbind_level = type_variables.first().map(|variable| variable.level); let kind_unbind_level = kind_variables.first().map(|variable| variable.level); - let data = CheckedDataLike { kind_variables, type_variables, result_kind, constructors }; + let data = BindingDataLike { kind_variables, type_variables, result_kind, constructors }; state.binding_group.data.insert(item_id, data); if let Some(level) = type_unbind_level { diff --git a/compiler-core/checking/src/core.rs b/compiler-core/checking/src/core.rs index 7ae0ec7a..21cb9708 100644 --- a/compiler-core/checking/src/core.rs +++ b/compiler-core/checking/src/core.rs @@ -135,3 +135,9 @@ pub struct Class { pub quantified_variables: debruijn::Size, pub kind_variables: debruijn::Size, } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DataLike { + pub quantified_variables: debruijn::Size, + pub kind_variables: debruijn::Size, +} diff --git a/compiler-core/checking/src/lib.rs b/compiler-core/checking/src/lib.rs index 5e4cb857..cbdadbf1 100644 --- a/compiler-core/checking/src/lib.rs +++ b/compiler-core/checking/src/lib.rs @@ -40,6 +40,7 @@ pub struct CheckedModule { pub instances: FxHashMap, pub derived: FxHashMap, pub classes: FxHashMap, + pub data: FxHashMap, pub errors: Vec, } @@ -60,6 +61,10 @@ impl CheckedModule { pub fn lookup_class(&self, id: TypeItemId) -> Option { self.classes.get(&id).cloned() } + + pub fn lookup_data(&self, id: TypeItemId) -> Option { + self.data.get(&id).copied() + } } pub fn check_module(queries: &impl ExternalQueries, file_id: FileId) -> QueryResult { diff --git a/tests-integration/fixtures/checking/001_proxy_checking/Main.snap b/tests-integration/fixtures/checking/001_proxy_checking/Main.snap index 5fa3cb79..fd9bdca9 100644 --- a/tests-integration/fixtures/checking/001_proxy_checking/Main.snap +++ b/tests-integration/fixtures/checking/001_proxy_checking/Main.snap @@ -7,3 +7,8 @@ Proxy :: forall (k :: Type) (a :: k). Proxy @k a Types Proxy :: forall (k :: Type). k -> Type + +Data +Proxy + Quantified = :0 + Kind = :1 diff --git a/tests-integration/fixtures/checking/002_proxy_inference/Main.snap b/tests-integration/fixtures/checking/002_proxy_inference/Main.snap index 119d140a..7a4868a7 100644 --- a/tests-integration/fixtures/checking/002_proxy_inference/Main.snap +++ b/tests-integration/fixtures/checking/002_proxy_inference/Main.snap @@ -7,3 +7,8 @@ Proxy :: forall (t1 :: Type) (a :: t1). Proxy @t1 a Types Proxy :: forall (t1 :: Type). t1 -> Type + +Data +Proxy + Quantified = :1 + Kind = :0 diff --git a/tests-integration/fixtures/checking/003_data_recursive/Main.snap b/tests-integration/fixtures/checking/003_data_recursive/Main.snap index f8616299..0ac744a5 100644 --- a/tests-integration/fixtures/checking/003_data_recursive/Main.snap +++ b/tests-integration/fixtures/checking/003_data_recursive/Main.snap @@ -8,3 +8,8 @@ Cons :: forall (a :: Type). a -> List a -> List a Types List :: Type -> Type + +Data +List + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/004_data_mutual/Main.snap b/tests-integration/fixtures/checking/004_data_mutual/Main.snap index 876408b5..7392175b 100644 --- a/tests-integration/fixtures/checking/004_data_mutual/Main.snap +++ b/tests-integration/fixtures/checking/004_data_mutual/Main.snap @@ -10,3 +10,12 @@ Cons :: forall (a :: Type). Tree a -> Forest a -> Forest a Types Tree :: Type -> Type Forest :: Type -> Type + +Data +Tree + Quantified = :0 + Kind = :0 + +Forest + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/005_newtype_recursive/Main.snap b/tests-integration/fixtures/checking/005_newtype_recursive/Main.snap index e0f99dd7..94e57fd6 100644 --- a/tests-integration/fixtures/checking/005_newtype_recursive/Main.snap +++ b/tests-integration/fixtures/checking/005_newtype_recursive/Main.snap @@ -7,3 +7,8 @@ In :: forall (f :: Type -> Type). f (Mu f) -> Mu f Types Mu :: (Type -> Type) -> Type + +Data +Mu + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap b/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap index d3fc39bf..e9507b1c 100644 --- a/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap +++ b/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap @@ -50,3 +50,13 @@ NestedReader = Apply (Apply (ReaderT Int) Identity) Boolean Quantified = :0 Kind = :0 Type = :0 + + +Data +Identity + Quantified = :0 + Kind = :0 + +Tuple + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/044_binder_constructor/Main.snap b/tests-integration/fixtures/checking/044_binder_constructor/Main.snap index 6c9bbbb0..27f4d30e 100644 --- a/tests-integration/fixtures/checking/044_binder_constructor/Main.snap +++ b/tests-integration/fixtures/checking/044_binder_constructor/Main.snap @@ -21,3 +21,16 @@ Types Pair :: Type -> Type -> Type Maybe :: Type -> Type List :: Type -> Type + +Data +Pair + Quantified = :0 + Kind = :0 + +Maybe + Quantified = :0 + Kind = :0 + +List + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/045_do_discard/Main.snap b/tests-integration/fixtures/checking/045_do_discard/Main.snap index 15d6bf18..e7a7de0b 100644 --- a/tests-integration/fixtures/checking/045_do_discard/Main.snap +++ b/tests-integration/fixtures/checking/045_do_discard/Main.snap @@ -14,3 +14,8 @@ test' :: Effect Unit Types Effect :: Type -> Type Unit :: Type + +Data +Unit + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/046_do_bind/Main.snap b/tests-integration/fixtures/checking/046_do_bind/Main.snap index 34547cf9..ad77c33a 100644 --- a/tests-integration/fixtures/checking/046_do_bind/Main.snap +++ b/tests-integration/fixtures/checking/046_do_bind/Main.snap @@ -13,3 +13,8 @@ test' :: Effect (Tuple Int Int) Types Effect :: Type -> Type Tuple :: Type -> Type -> Type + +Data +Tuple + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/047_do_non_monadic/Main.snap b/tests-integration/fixtures/checking/047_do_non_monadic/Main.snap index 384b7954..5c9c9c2e 100644 --- a/tests-integration/fixtures/checking/047_do_non_monadic/Main.snap +++ b/tests-integration/fixtures/checking/047_do_non_monadic/Main.snap @@ -11,3 +11,8 @@ test' :: Tuple Int String Types Tuple :: Type -> Type -> Type + +Data +Tuple + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/049_ado_discard/Main.snap b/tests-integration/fixtures/checking/049_ado_discard/Main.snap index 536e9cb3..e0e045ad 100644 --- a/tests-integration/fixtures/checking/049_ado_discard/Main.snap +++ b/tests-integration/fixtures/checking/049_ado_discard/Main.snap @@ -14,3 +14,8 @@ test' :: Effect Unit Types Effect :: Type -> Type Unit :: Type + +Data +Unit + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/050_ado_bind/Main.snap b/tests-integration/fixtures/checking/050_ado_bind/Main.snap index a2e3e8f5..2d16e859 100644 --- a/tests-integration/fixtures/checking/050_ado_bind/Main.snap +++ b/tests-integration/fixtures/checking/050_ado_bind/Main.snap @@ -13,3 +13,8 @@ test' :: Effect (Tuple Int Int) Types Effect :: Type -> Type Tuple :: Type -> Type -> Type + +Data +Tuple + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/051_ado_non_applicative/Main.snap b/tests-integration/fixtures/checking/051_ado_non_applicative/Main.snap index 370e121a..a7eed7b8 100644 --- a/tests-integration/fixtures/checking/051_ado_non_applicative/Main.snap +++ b/tests-integration/fixtures/checking/051_ado_non_applicative/Main.snap @@ -12,3 +12,8 @@ test' :: Tuple Int String Types Tuple :: Type -> Type -> Type + +Data +Tuple + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap b/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap index 0ba97e7c..b78cd442 100644 --- a/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap +++ b/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap @@ -12,3 +12,8 @@ test' :: forall (t60 :: Type -> Type). t60 (Tuple Int String) Types Tuple :: Type -> Type -> Type + +Data +Tuple + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap b/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap index e90633b2..16be8751 100644 --- a/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap +++ b/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap @@ -12,3 +12,8 @@ test' :: forall (t56 :: Type -> Type). t56 (Tuple Int String) Types Tuple :: Type -> Type -> Type + +Data +Tuple + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/058_binder_operator_chain/Main.snap b/tests-integration/fixtures/checking/058_binder_operator_chain/Main.snap index ab3ae6d3..2580e1b4 100644 --- a/tests-integration/fixtures/checking/058_binder_operator_chain/Main.snap +++ b/tests-integration/fixtures/checking/058_binder_operator_chain/Main.snap @@ -12,3 +12,8 @@ matchCons' :: List Int -> Int Types List :: Type -> Type + +Data +List + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/062_case_of/Main.snap b/tests-integration/fixtures/checking/062_case_of/Main.snap index 6fe8f2a2..bb04cd52 100644 --- a/tests-integration/fixtures/checking/062_case_of/Main.snap +++ b/tests-integration/fixtures/checking/062_case_of/Main.snap @@ -21,3 +21,16 @@ Types Maybe :: Type -> Type Either :: Type -> Type -> Type Tuple :: Type -> Type -> Type + +Data +Maybe + Quantified = :0 + Kind = :0 + +Either + Quantified = :0 + Kind = :0 + +Tuple + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/068_expression_sections/Main.snap b/tests-integration/fixtures/checking/068_expression_sections/Main.snap index df8bb99d..d35c1cf0 100644 --- a/tests-integration/fixtures/checking/068_expression_sections/Main.snap +++ b/tests-integration/fixtures/checking/068_expression_sections/Main.snap @@ -28,3 +28,8 @@ test18 :: Int -> Int Types Tuple :: Type -> Type -> Type + +Data +Tuple + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/089_no_instance_found/Main.snap b/tests-integration/fixtures/checking/089_no_instance_found/Main.snap index f9ed950f..0bcc7879 100644 --- a/tests-integration/fixtures/checking/089_no_instance_found/Main.snap +++ b/tests-integration/fixtures/checking/089_no_instance_found/Main.snap @@ -11,6 +11,12 @@ Types Eq :: Type -> Constraint Foo :: Type +Data +Foo + Quantified = :0 + Kind = :0 + + Classes class Eq (&0 :: Type) diff --git a/tests-integration/fixtures/checking/090_instance_improve/Main.snap b/tests-integration/fixtures/checking/090_instance_improve/Main.snap index 2ae9ab29..db4a3d3a 100644 --- a/tests-integration/fixtures/checking/090_instance_improve/Main.snap +++ b/tests-integration/fixtures/checking/090_instance_improve/Main.snap @@ -11,6 +11,16 @@ True :: Type False :: Type TypeEq :: forall (t7 :: Type). Type -> Type -> t7 -> Constraint +Data +True + Quantified = :0 + Kind = :0 + +False + Quantified = :0 + Kind = :0 + + Classes class TypeEq (&1 :: Type) (&2 :: Type) (&3 :: &0) diff --git a/tests-integration/fixtures/checking/091_superclass_elaboration/Main.snap b/tests-integration/fixtures/checking/091_superclass_elaboration/Main.snap index 72b0f69c..0b476249 100644 --- a/tests-integration/fixtures/checking/091_superclass_elaboration/Main.snap +++ b/tests-integration/fixtures/checking/091_superclass_elaboration/Main.snap @@ -16,6 +16,12 @@ Eq :: Type -> Constraint Ord :: Type -> Constraint Ordering :: Type +Data +Ordering + Quantified = :0 + Kind = :0 + + Classes class Eq (&0 :: Type) class Eq &0 <= Ord (&0 :: Type) diff --git a/tests-integration/fixtures/checking/094_let_binding_constraint_error/Main.snap b/tests-integration/fixtures/checking/094_let_binding_constraint_error/Main.snap index abb15d7a..954a6784 100644 --- a/tests-integration/fixtures/checking/094_let_binding_constraint_error/Main.snap +++ b/tests-integration/fixtures/checking/094_let_binding_constraint_error/Main.snap @@ -11,6 +11,12 @@ Types Foo :: Type Eq :: Type -> Constraint +Data +Foo + Quantified = :0 + Kind = :0 + + Classes class Eq (&0 :: Type) diff --git a/tests-integration/fixtures/checking/097_instance_chains/Main.snap b/tests-integration/fixtures/checking/097_instance_chains/Main.snap index a79609f3..9fb59742 100644 --- a/tests-integration/fixtures/checking/097_instance_chains/Main.snap +++ b/tests-integration/fixtures/checking/097_instance_chains/Main.snap @@ -12,6 +12,12 @@ Types Proxy :: forall (t1 :: Type). t1 -> Type TypeEq :: forall (t3 :: Type) (t6 :: Type) (t7 :: Type). t3 -> t6 -> t7 -> Constraint +Data +Proxy + Quantified = :1 + Kind = :0 + + Classes class TypeEq (&3 :: &0) (&4 :: &1) (&5 :: &2) diff --git a/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap b/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap index a9b3f96a..4fd5094b 100644 --- a/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap +++ b/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap @@ -16,6 +16,12 @@ Z :: Type S :: Type -> Type And :: forall (t7 :: Type) (t10 :: Type) (t11 :: Type). t7 -> t10 -> t11 -> Constraint +Data +Proxy + Quantified = :1 + Kind = :0 + + Classes class IsZero (&2 :: &0) (&3 :: &1) class And (&3 :: &0) (&4 :: &1) (&5 :: &2) diff --git a/tests-integration/fixtures/checking/099_builtin_int/Main.snap b/tests-integration/fixtures/checking/099_builtin_int/Main.snap index 019df2bb..f421134a 100644 --- a/tests-integration/fixtures/checking/099_builtin_int/Main.snap +++ b/tests-integration/fixtures/checking/099_builtin_int/Main.snap @@ -27,3 +27,12 @@ forceSolve :: Types Proxy :: forall (k :: Type). k -> Type Unit :: Type + +Data +Proxy + Quantified = :0 + Kind = :1 + +Unit + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/100_builtin_given/Main.snap b/tests-integration/fixtures/checking/100_builtin_given/Main.snap index ade2c8c5..07dd85d4 100644 --- a/tests-integration/fixtures/checking/100_builtin_given/Main.snap +++ b/tests-integration/fixtures/checking/100_builtin_given/Main.snap @@ -39,3 +39,17 @@ MaxN = 5 Quantified = :0 Kind = :0 Type = :0 + + +Data +Proxy + Quantified = :0 + Kind = :1 + +N + Quantified = :0 + Kind = :0 + +Something + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/101_builtin_symbol/Main.snap b/tests-integration/fixtures/checking/101_builtin_symbol/Main.snap index 4f7e4ba5..bfefe760 100644 --- a/tests-integration/fixtures/checking/101_builtin_symbol/Main.snap +++ b/tests-integration/fixtures/checking/101_builtin_symbol/Main.snap @@ -37,3 +37,8 @@ forceSolve :: Types Proxy :: forall (k :: Type). k -> Type + +Data +Proxy + Quantified = :0 + Kind = :1 diff --git a/tests-integration/fixtures/checking/102_builtin_row/Main.snap b/tests-integration/fixtures/checking/102_builtin_row/Main.snap index 44a65daf..e9ba29b3 100644 --- a/tests-integration/fixtures/checking/102_builtin_row/Main.snap +++ b/tests-integration/fixtures/checking/102_builtin_row/Main.snap @@ -82,3 +82,8 @@ solveRowToList :: Types Proxy :: forall (k :: Type). k -> Type + +Data +Proxy + Quantified = :0 + Kind = :1 diff --git a/tests-integration/fixtures/checking/105_incomplete_type_signature/Main.snap b/tests-integration/fixtures/checking/105_incomplete_type_signature/Main.snap index 90ff5b7f..fb3b6177 100644 --- a/tests-integration/fixtures/checking/105_incomplete_type_signature/Main.snap +++ b/tests-integration/fixtures/checking/105_incomplete_type_signature/Main.snap @@ -7,3 +7,8 @@ Bar :: Foo Types Foo :: Type + +Data +Foo + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/106_row_union_invalid_discharged/Main.snap b/tests-integration/fixtures/checking/106_row_union_invalid_discharged/Main.snap index 4969c0ef..af87489e 100644 --- a/tests-integration/fixtures/checking/106_row_union_invalid_discharged/Main.snap +++ b/tests-integration/fixtures/checking/106_row_union_invalid_discharged/Main.snap @@ -13,3 +13,8 @@ forceSolve :: Types Proxy :: forall (k :: Type). k -> Type + +Data +Proxy + Quantified = :0 + Kind = :1 diff --git a/tests-integration/fixtures/checking/107_symbol_append_invalid_discharged/Main.snap b/tests-integration/fixtures/checking/107_symbol_append_invalid_discharged/Main.snap index c32afe46..3ec57bc9 100644 --- a/tests-integration/fixtures/checking/107_symbol_append_invalid_discharged/Main.snap +++ b/tests-integration/fixtures/checking/107_symbol_append_invalid_discharged/Main.snap @@ -9,3 +9,8 @@ forceSolve :: forall (t7 :: Symbol). Append "hello" t7 "xyz" => { invalid :: Pro Types Proxy :: forall (k :: Type). k -> Type + +Data +Proxy + Quantified = :0 + Kind = :1 diff --git a/tests-integration/fixtures/checking/108_symbol_cons_invalid_discharged/Main.snap b/tests-integration/fixtures/checking/108_symbol_cons_invalid_discharged/Main.snap index 436c3f0e..be875079 100644 --- a/tests-integration/fixtures/checking/108_symbol_cons_invalid_discharged/Main.snap +++ b/tests-integration/fixtures/checking/108_symbol_cons_invalid_discharged/Main.snap @@ -9,3 +9,8 @@ forceSolve :: forall (t7 :: Symbol). Cons "hello" t7 "helloworld" => { invalid : Types Proxy :: forall (k :: Type). k -> Type + +Data +Proxy + Quantified = :0 + Kind = :1 diff --git a/tests-integration/fixtures/checking/109_row_cons_invalid_discharged/Main.snap b/tests-integration/fixtures/checking/109_row_cons_invalid_discharged/Main.snap index b443dffa..496dad83 100644 --- a/tests-integration/fixtures/checking/109_row_cons_invalid_discharged/Main.snap +++ b/tests-integration/fixtures/checking/109_row_cons_invalid_discharged/Main.snap @@ -13,3 +13,8 @@ forceSolve :: Types Proxy :: forall (k :: Type). k -> Type + +Data +Proxy + Quantified = :0 + Kind = :1 diff --git a/tests-integration/fixtures/checking/110_row_lacks_invalid_no_instance/Main.snap b/tests-integration/fixtures/checking/110_row_lacks_invalid_no_instance/Main.snap index e860160b..9910d0a2 100644 --- a/tests-integration/fixtures/checking/110_row_lacks_invalid_no_instance/Main.snap +++ b/tests-integration/fixtures/checking/110_row_lacks_invalid_no_instance/Main.snap @@ -10,5 +10,11 @@ forceSolve :: { invalid :: Proxy @(Row Type) ( a :: Int, b :: String ) } Types Proxy :: forall (k :: Type). k -> Type +Data +Proxy + Quantified = :0 + Kind = :1 + + Errors NoInstanceFound { Lacks @Type "b" ( a :: Int, b :: String ) } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/111_int_add_invalid_no_instance/Main.snap b/tests-integration/fixtures/checking/111_int_add_invalid_no_instance/Main.snap index f2096801..0a4876b9 100644 --- a/tests-integration/fixtures/checking/111_int_add_invalid_no_instance/Main.snap +++ b/tests-integration/fixtures/checking/111_int_add_invalid_no_instance/Main.snap @@ -10,5 +10,11 @@ forceSolve :: { invalid :: Proxy @Int 10 } Types Proxy :: forall (k :: Type). k -> Type +Data +Proxy + Quantified = :0 + Kind = :1 + + Errors NoInstanceFound { Add 2 3 10 } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/112_int_mul_invalid_no_instance/Main.snap b/tests-integration/fixtures/checking/112_int_mul_invalid_no_instance/Main.snap index bd662a1b..d2eab345 100644 --- a/tests-integration/fixtures/checking/112_int_mul_invalid_no_instance/Main.snap +++ b/tests-integration/fixtures/checking/112_int_mul_invalid_no_instance/Main.snap @@ -10,5 +10,11 @@ forceSolve :: { invalid :: Proxy @Int 10 } Types Proxy :: forall (k :: Type). k -> Type +Data +Proxy + Quantified = :0 + Kind = :1 + + Errors NoInstanceFound { Mul 2 3 10 } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/113_int_compare_invalid_no_instance/Main.snap b/tests-integration/fixtures/checking/113_int_compare_invalid_no_instance/Main.snap index 1c6dde5a..020827bc 100644 --- a/tests-integration/fixtures/checking/113_int_compare_invalid_no_instance/Main.snap +++ b/tests-integration/fixtures/checking/113_int_compare_invalid_no_instance/Main.snap @@ -10,5 +10,11 @@ forceSolve :: { invalid :: Proxy @Ordering LT } Types Proxy :: forall (k :: Type). k -> Type +Data +Proxy + Quantified = :0 + Kind = :1 + + Errors NoInstanceFound { Compare 5 1 LT } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/114_int_tostring_invalid_no_instance/Main.snap b/tests-integration/fixtures/checking/114_int_tostring_invalid_no_instance/Main.snap index ba62400e..74ca812d 100644 --- a/tests-integration/fixtures/checking/114_int_tostring_invalid_no_instance/Main.snap +++ b/tests-integration/fixtures/checking/114_int_tostring_invalid_no_instance/Main.snap @@ -10,5 +10,11 @@ forceSolve :: { invalid :: Proxy @Symbol "999" } Types Proxy :: forall (k :: Type). k -> Type +Data +Proxy + Quantified = :0 + Kind = :1 + + Errors NoInstanceFound { ToString 42 "999" } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap b/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap index e0becbc4..e69a5e9f 100644 --- a/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap +++ b/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap @@ -25,6 +25,12 @@ Discard :: (Type -> Type) -> Constraint Bind :: (Type -> Type) -> Constraint Monad :: (Type -> Type) -> Constraint +Data +Tuple + Quantified = :0 + Kind = :0 + + Classes class Functor (&0 :: Type -> Type) class Functor &0 <= Apply (&0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/121_instance_member_inner_forall/Main.snap b/tests-integration/fixtures/checking/121_instance_member_inner_forall/Main.snap index 0f14d53f..7a59baa3 100644 --- a/tests-integration/fixtures/checking/121_instance_member_inner_forall/Main.snap +++ b/tests-integration/fixtures/checking/121_instance_member_inner_forall/Main.snap @@ -10,6 +10,12 @@ Types Functor :: (Type -> Type) -> Constraint Box :: Type -> Type +Data +Box + Quantified = :0 + Kind = :0 + + Classes class Functor (&0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/122_instance_member_inner_forall_constraint/Main.snap b/tests-integration/fixtures/checking/122_instance_member_inner_forall_constraint/Main.snap index 1509291d..bbeb151e 100644 --- a/tests-integration/fixtures/checking/122_instance_member_inner_forall_constraint/Main.snap +++ b/tests-integration/fixtures/checking/122_instance_member_inner_forall_constraint/Main.snap @@ -16,6 +16,16 @@ Functor :: (Type -> Type) -> Constraint Box :: Type -> Type Maybe :: Type -> Type +Data +Box + Quantified = :0 + Kind = :0 + +Maybe + Quantified = :0 + Kind = :0 + + Classes class Show (&0 :: Type) class Functor (&0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/124_instance_member_missing_constraint/Main.snap b/tests-integration/fixtures/checking/124_instance_member_missing_constraint/Main.snap index 7171b982..a4e79833 100644 --- a/tests-integration/fixtures/checking/124_instance_member_missing_constraint/Main.snap +++ b/tests-integration/fixtures/checking/124_instance_member_missing_constraint/Main.snap @@ -12,6 +12,12 @@ Show :: Type -> Constraint Functor :: (Type -> Type) -> Constraint Box :: Type -> Type +Data +Box + Quantified = :0 + Kind = :0 + + Classes class Show (&0 :: Type) class Functor (&0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/126_instance_phantom/Main.snap b/tests-integration/fixtures/checking/126_instance_phantom/Main.snap index 73828956..fdc9aa6f 100644 --- a/tests-integration/fixtures/checking/126_instance_phantom/Main.snap +++ b/tests-integration/fixtures/checking/126_instance_phantom/Main.snap @@ -11,6 +11,12 @@ Types Phantom :: Type -> Constraint Proxy :: forall (t3 :: Type). t3 -> Type +Data +Proxy + Quantified = :1 + Kind = :0 + + Classes class Phantom (&0 :: Type) diff --git a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap index da0cfa0c..74843037 100644 --- a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap +++ b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap @@ -8,5 +8,11 @@ Proxy :: forall (t1 :: Type) (a :: t1). Proxy @t1 a Types Proxy :: forall (t1 :: Type). t1 -> Type +Data +Proxy + Quantified = :1 + Kind = :0 + + Derived derive Eq (Proxy @&0 &1 :: Type) diff --git a/tests-integration/src/generated/basic.rs b/tests-integration/src/generated/basic.rs index c6ec4ecc..6b6d79bd 100644 --- a/tests-integration/src/generated/basic.rs +++ b/tests-integration/src/generated/basic.rs @@ -310,6 +310,19 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { writeln!(snapshot).unwrap(); } + if !checked.data.is_empty() { + writeln!(snapshot, "\nData").unwrap(); + } + for (id, TypeItem { name, kind, .. }) in indexed.items.iter_types() { + let (TypeItemKind::Data { .. } | TypeItemKind::Newtype { .. }) = kind else { continue }; + let Some(name) = name else { continue }; + let Some(data) = checked.lookup_data(id) else { continue }; + writeln!(snapshot, "{name}").unwrap(); + writeln!(snapshot, " Quantified = {}", data.quantified_variables).unwrap(); + writeln!(snapshot, " Kind = {}", data.kind_variables).unwrap(); + writeln!(snapshot).unwrap(); + } + if !checked.classes.is_empty() { writeln!(snapshot, "\nClasses").unwrap(); } From f9bad12766aaa773c4608f8ecfa41168b5d91b42 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 00:08:24 +0800 Subject: [PATCH 14/62] Completely rework checking for type items This completely reworks the checking code for types, reducing the number of implicit invariants that using global mutable state introduced. --- compiler-core/checking/src/algorithm.rs | 212 ++--- .../checking/src/algorithm/constraint.rs | 8 +- compiler-core/checking/src/algorithm/kind.rs | 2 +- .../checking/src/algorithm/kind/synonym.rs | 14 +- compiler-core/checking/src/algorithm/state.rs | 199 ++-- .../checking/src/algorithm/type_item.rs | 893 +++++++++++------- compiler-core/lowering/src/lib.rs | 13 +- .../checking/002_proxy_inference/Main.snap | 4 +- .../checking/006_type_synonym/Main.snap | 4 +- .../checking/007_foreign_poly/Main.snap | 4 +- .../009_expand_identity_synonym/Main.snap | 4 +- .../checking/013_class_phantom/Main.snap | 4 +- .../018_type_operator_valid/Main.snap | 6 +- .../fixtures/checking/026_row_empty/Main.snap | 6 +- .../Main.snap | 9 + .../031_partial_synonym_polykind/Main.snap | 7 + .../checking/044_binder_constructor/Main.snap | 4 +- .../checking/053_do_polymorphic/Main.snap | 2 +- .../checking/054_ado_polymorphic/Main.snap | 2 +- .../fixtures/checking/062_case_of/Main.snap | 2 +- .../068_expression_sections/Main.snap | 20 +- .../Main.snap | 4 +- .../checking/090_instance_improve/Main.snap | 4 +- .../093_constraint_generalization/Main.snap | 4 +- .../checking/097_instance_chains/Main.snap | 10 +- .../checking/098_fundep_propagation/Main.snap | 12 +- .../checking/117_do_ado_constrained/Main.snap | 6 +- .../Main.snap | 2 +- .../Main.snap | 2 +- .../checking/126_instance_phantom/Main.snap | 6 +- .../checking/127_derive_eq_simple/Main.snap | 4 +- .../128_type_operator_mutual/Main.purs | 9 + .../128_type_operator_mutual/Main.snap | 15 + tests-integration/tests/checking/generated.rs | 2 + ...ecking__invalid_type_operator_nullary.snap | 6 +- ...ecking__invalid_type_operator_ternary.snap | 8 +- ...checking__invalid_type_operator_unary.snap | 8 +- .../snapshots/checking__partial_synonym.snap | 11 - 38 files changed, 814 insertions(+), 718 deletions(-) create mode 100644 tests-integration/fixtures/checking/128_type_operator_mutual/Main.purs create mode 100644 tests-integration/fixtures/checking/128_type_operator_mutual/Main.snap diff --git a/compiler-core/checking/src/algorithm.rs b/compiler-core/checking/src/algorithm.rs index 4155ddf5..89e16e66 100644 --- a/compiler-core/checking/src/algorithm.rs +++ b/compiler-core/checking/src/algorithm.rs @@ -54,9 +54,9 @@ use building_types::QueryResult; use files::FileId; use indexing::TermItemKind; use itertools::Itertools; -use lowering::{DataIr, NewtypeIr, Scc, TermItemIr, TypeItemIr}; +use lowering::{Scc, TermItemIr}; -use crate::core::{ForallBinder, Type, TypeId, debruijn}; +use crate::core::{Type, TypeId}; use crate::{CheckedModule, ExternalQueries}; pub fn check_source(queries: &impl ExternalQueries, file_id: FileId) -> QueryResult { @@ -64,7 +64,7 @@ pub fn check_source(queries: &impl ExternalQueries, file_id: FileId) -> QueryRes let context = state::CheckContext::new(queries, &mut state, file_id)?; check_type_signatures(&mut state, &context)?; - check_type_items(&mut state, &context)?; + check_type_definitions(&mut state, &context)?; check_term_signatures(&mut state, &context)?; check_instance_heads(&mut state, &context)?; @@ -75,6 +75,24 @@ pub fn check_source(queries: &impl ExternalQueries, file_id: FileId) -> QueryRes Ok(state.checked) } +/// See [`type_item::check_type_signature`] +/// +/// Kind signatures are acyclic, and can be checked separately from the +/// type definitions. Checking these early adds better information for +/// inference, especially for mutually recursive type declarations. +/// +/// Consider the following example: +/// +/// ```purescript +/// data F a = MkF (G a) +/// +/// data G :: Int -> Type +/// data G a = MkG (F a) +/// ``` +/// +/// By checking the kind signature of `G` first, we can avoid allocating +/// a unification variable for `G` when checking the mutually recursive +/// declarations of `{F, G}` fn check_type_signatures( state: &mut state::CheckState, context: &state::CheckContext, @@ -83,169 +101,61 @@ where Q: ExternalQueries, { for scc in &context.lowered.type_scc { - match scc { - Scc::Base(id) | Scc::Recursive(id) => { - type_item::check_type_signature(state, context, *id)?; - } - Scc::Mutual(mutual) => { - for id in mutual { - type_item::check_type_signature(state, context, *id)?; - } - } + let items = match scc { + Scc::Base(id) | Scc::Recursive(id) => slice::from_ref(id), + Scc::Mutual(items) => items, + }; + for id in items { + type_item::check_type_signature(state, context, *id)?; } } - Ok(()) } -fn check_type_items( +/// See [`type_item::check_type_item`] +/// +/// This function calls [`state::CheckState::with_type_group`] to insert +/// placeholder unification variables for recursive binding groups. After +/// checking a binding group, it calls [`type_item::commit_type_item`] to +/// generalise the types and add them to [`state::CheckState::checked`]. +fn check_type_definitions( state: &mut state::CheckState, context: &state::CheckContext, ) -> QueryResult<()> where Q: ExternalQueries, { - let needs_binding_group = |id: &indexing::TypeItemId| { - // Use the lowering representation to detypeine if we need a binding - // group to match invariant expectations in downstream checking rules. - // Previously, this used the indexing representation which would crash - // on partially-specified kind signatures like `data Foo ::`. - let Some(lowered) = context.lowered.info.get_type_item(*id) else { - return false; - }; - matches!( - lowered, - TypeItemIr::DataGroup { signature: None, .. } - | TypeItemIr::NewtypeGroup { signature: None, .. } - | TypeItemIr::SynonymGroup { signature: None, .. } - | TypeItemIr::ClassGroup { signature: None, .. } - ) - }; - for scc in &context.lowered.type_scc { match scc { - Scc::Base(item) | Scc::Recursive(item) => { - if !state.binding_group.types.contains_key(item) && needs_binding_group(item) { - state.type_binding_group(context, [*item]); + Scc::Base(id) => { + if let Some(item) = type_item::check_type_item(state, context, *id)? { + type_item::commit_type_item(state, context, *id, item)?; } - type_item::check_type_item(state, context, *item)?; - - build_data_constructor_types(state, context, slice::from_ref(item))?; - state.commit_binding_group(context)?; } - Scc::Mutual(items) => { - let with_signature = items - .iter() - .filter(|item| state.binding_group.types.contains_key(item)) - .copied() - .collect_vec(); - - let without_signature = - items.iter().filter(|item| needs_binding_group(item)).copied().collect_vec(); - - let group = without_signature.iter().copied(); - state.type_binding_group(context, group); - - for item in &without_signature { - type_item::check_type_item(state, context, *item)?; - } - - build_data_constructor_types(state, context, &without_signature)?; - state.commit_binding_group(context)?; - - for item in &with_signature { - type_item::check_type_item(state, context, *item)?; - } - - build_data_constructor_types(state, context, &with_signature)?; - state.commit_binding_group(context)?; + Scc::Recursive(id) => { + state.with_type_group(context, slice::from_ref(id), |state| { + if let Some(item) = type_item::check_type_item(state, context, *id)? { + type_item::commit_type_item(state, context, *id, item)?; + } + Ok(()) + })?; } - } - } - - Ok(()) -} - -fn build_data_constructor_types( - state: &mut state::CheckState, - context: &state::CheckContext, - items: &[indexing::TypeItemId], -) -> QueryResult<()> -where - Q: ExternalQueries, -{ - for &item_id in items { - let Some(data) = state.binding_group.data.remove(&item_id) else { - continue; - }; - - let Some(item) = context.lowered.info.get_type_item(item_id) else { - continue; - }; - - let variables = match item { - TypeItemIr::DataGroup { data: Some(DataIr { variables }), .. } => variables, - TypeItemIr::NewtypeGroup { newtype: Some(NewtypeIr { variables }), .. } => variables, - _ => continue, - }; - - let kind_variables = data.kind_variables.iter(); - let surface_bindings = state.surface_bindings.get_type(item_id); - let surface_bindings = surface_bindings.as_deref().unwrap_or_default(); - let mut surface_binding_iter = surface_bindings.iter(); - - let kind_variables = kind_variables.map(|binder| { - let level = if let Some(&binding_id) = surface_binding_iter.next() { - state.type_scope.bound.bind(debruijn::Variable::Forall(binding_id)) - } else { - state.type_scope.bound.bind(debruijn::Variable::Core) - }; - state.type_scope.kinds.insert(level, binder.kind); - ForallBinder { - visible: binder.visible, - name: binder.name.clone(), - level, - kind: binder.kind, - } - }); - - let kind_variables = kind_variables.collect_vec(); - - let type_variables = data.type_variables.iter(); - let surface_bindings = variables.iter(); - - let type_variables = type_variables.zip(surface_bindings).map(|(binder, variable)| { - let level = state.type_scope.bind_forall(variable.id, binder.kind); - ForallBinder { - visible: binder.visible, - name: binder.name.clone(), - level, - kind: binder.kind, + Scc::Mutual(mutual) => { + state.with_type_group(context, mutual, |state| { + let mut items = vec![]; + for &id in mutual { + if let Some(item) = type_item::check_type_item(state, context, id)? { + items.push((id, item)); + } + } + for (id, item) in items { + type_item::commit_type_item(state, context, id, item)?; + } + Ok(()) + })?; } - }); - - let type_variables = type_variables.collect_vec(); - - type_item::build_constructor_types( - state, - context, - item_id, - &kind_variables, - &type_variables, - &data.constructors, - )?; - - if let Some(variable) = type_variables.first() { - state.type_scope.unbind(variable.level); } - - if let Some(variable) = kind_variables.first() { - state.type_scope.unbind(variable.level); - } - - state.binding_group.data.insert(item_id, data); } - Ok(()) } @@ -280,8 +190,8 @@ where Q: ExternalQueries, { let items = context.lowered.term_scc.iter().flat_map(|scc| match scc { - Scc::Base(item) | Scc::Recursive(item) => slice::from_ref(item), - Scc::Mutual(items) => items.as_slice(), + Scc::Base(id) | Scc::Recursive(id) => slice::from_ref(id), + Scc::Mutual(id) => id, }); for &item_id in items { @@ -308,8 +218,8 @@ where Q: ExternalQueries, { let items = context.lowered.term_scc.iter().flat_map(|scc| match scc { - Scc::Base(item) | Scc::Recursive(item) => slice::from_ref(item), - Scc::Mutual(items) => items.as_slice(), + Scc::Base(id) | Scc::Recursive(id) => slice::from_ref(id), + Scc::Mutual(id) => id, }); for &item_id in items { diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index 6fcb147a..21ee12df 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -299,12 +299,8 @@ fn lookup_local_class( where Q: ExternalQueries, { - if let Some(class) = state.binding_group.lookup_class(item_id) { - Some(class) - } else { - let class = state.checked.lookup_class(item_id)?; - Some(localize_class(state, context, &class)) - } + let class = state.checked.lookup_class(item_id)?; + Some(localize_class(state, context, &class)) } fn lookup_global_class( diff --git a/compiler-core/checking/src/algorithm/kind.rs b/compiler-core/checking/src/algorithm/kind.rs index 7a7ee4c7..c96b01ed 100644 --- a/compiler-core/checking/src/algorithm/kind.rs +++ b/compiler-core/checking/src/algorithm/kind.rs @@ -565,7 +565,7 @@ where Q: ExternalQueries, { let type_id = if file_id == context.id { - if let Some(&k) = state.binding_group.types.get(&type_id) { + if let Some(k) = state.binding_group.lookup_type(type_id) { k } else if let Some(&k) = state.checked.types.get(&type_id) { transfer::localize(state, context, k) diff --git a/compiler-core/checking/src/algorithm/kind/synonym.rs b/compiler-core/checking/src/algorithm/kind/synonym.rs index 49092214..0f95d555 100644 --- a/compiler-core/checking/src/algorithm/kind/synonym.rs +++ b/compiler-core/checking/src/algorithm/kind/synonym.rs @@ -70,17 +70,9 @@ fn lookup_local_synonym( where Q: ExternalQueries, { - if let Some(synonym) = state.binding_group.lookup_synonym(type_id) - && let Some(kind) = state.binding_group.lookup_type(type_id) - { - Some((synonym, kind)) - } else if let Some(synonym) = state.checked.lookup_synonym(type_id) - && let Some(kind) = state.checked.lookup_type(type_id) - { - Some(localize_synonym_and_kind(state, context, synonym, kind)) - } else { - None - } + let synonym = state.checked.lookup_synonym(type_id)?; + let kind = state.checked.lookup_type(type_id)?; + Some(localize_synonym_and_kind(state, context, synonym, kind)) } fn lookup_global_synonym( diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index 91fbbbe0..c35a0660 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -1,4 +1,5 @@ pub mod unification; +use itertools::Itertools; pub use unification::*; use std::collections::VecDeque; @@ -10,14 +11,14 @@ use files::FileId; use indexing::{IndexedModule, TermItemId, TypeItemId}; use lowering::{ BinderId, GraphNodeId, ImplicitBindingId, LetBindingNameGroupId, LoweredModule, RecordPunId, - TypeVariableBindingId, + TypeItemIr, TypeVariableBindingId, }; use resolving::ResolvedModule; use rustc_hash::FxHashMap; use sugar::{Bracketed, Sectioned}; use crate::algorithm::{constraint, quantify, transfer}; -use crate::core::{Class, DataLike, ForallBinder, Synonym, Type, TypeId, TypeInterner, debruijn}; +use crate::core::{Type, TypeId, TypeInterner, debruijn}; use crate::error::{CheckError, ErrorKind, ErrorStep}; use crate::{CheckedModule, ExternalQueries}; @@ -289,22 +290,14 @@ pub struct CheckedConstructor { pub arguments: Vec, } -#[derive(Clone)] -pub struct BindingDataLike { - pub kind_variables: Vec, - pub type_variables: Vec, - pub result_kind: TypeId, - pub constructors: Vec, -} +#[derive(Clone, Copy)] +pub struct BindingGroupType(pub TypeId); #[derive(Default)] pub struct BindingGroupContext { pub terms: FxHashMap, - pub types: FxHashMap, - pub synonyms: FxHashMap, - pub classes: FxHashMap, + pub types: FxHashMap, pub residual: FxHashMap>, - pub data: FxHashMap, } impl BindingGroupContext { @@ -313,15 +306,7 @@ impl BindingGroupContext { } pub fn lookup_type(&self, id: TypeItemId) -> Option { - self.types.get(&id).copied() - } - - pub fn lookup_synonym(&self, id: TypeItemId) -> Option { - self.synonyms.get(&id).copied() - } - - pub fn lookup_class(&self, id: TypeItemId) -> Option { - self.classes.get(&id).cloned() + self.types.get(&id).map(|binding| binding.0) } } @@ -659,11 +644,73 @@ impl CheckState { Q: ExternalQueries, { for item in group { - let t = self.fresh_unification_type(context); - self.binding_group.types.insert(item, t); + let kind = self.fresh_unification_type(context); + self.binding_group.types.insert(item, BindingGroupType(kind)); } } + /// Executes a closure with a type binding group in scope. + /// + /// This inserts pending kinds (unification variables) for the given type + /// items, such that recursive or mutually recursive declarations can be + /// checked together. The binding group is cleared after the closure returns. + pub fn with_type_group( + &mut self, + context: &CheckContext, + group: &[TypeItemId], + f: F, + ) -> T + where + Q: ExternalQueries, + F: FnOnce(&mut Self) -> T, + { + // Insert pending kinds for items that need them (non-operators without signatures) + let needs_pending = group.iter().filter(|&&item_id| { + if let Some(TypeItemIr::Operator { .. }) = context.lowered.info.get_type_item(item_id) { + return false; + } + if self.checked.types.contains_key(&item_id) { + return false; + } + true + }); + + for item in needs_pending.copied().collect_vec() { + let kind = self.fresh_unification_type(context); + self.binding_group.types.insert(item, BindingGroupType(kind)); + } + + // Insert pending kinds for operators (share kind with their target) + let operators = group.iter().filter_map(|&item_id| { + let TypeItemIr::Operator { resolution, .. } = + context.lowered.info.get_type_item(item_id)? + else { + return None; + }; + let resolution = resolution.as_ref()?; + Some((item_id, *resolution)) + }); + + for (operator_id, (file_id, item_id)) in operators { + debug_assert!( + file_id == context.id && group.contains(&item_id), + "invariant violated: expected local target for operator" + ); + + let kind = self.binding_group.lookup_type(item_id).or_else(|| { + let kind = self.checked.types.get(&item_id)?; + Some(transfer::localize(self, context, *kind)) + }); + + let kind = kind.expect("invariant violated: expected kind for operator target"); + self.binding_group.types.insert(operator_id, BindingGroupType(kind)); + } + + let result = f(self); + self.binding_group.types.clear(); + result + } + pub fn solve_constraints(&mut self, context: &CheckContext) -> QueryResult> where Q: ExternalQueries, @@ -702,111 +749,15 @@ impl CheckState { } } - // Process data/newtype to be committed into the CheckedModule. This loops - // over the BindingGroupContext::data and generalises unsolved unification - // variables in their kinds. DataLike is inserted in CheckedModule::data - // containing the number of quantified and kind variables in the type. - // These are tracked so that the check_derive rule knows exactly how many - // kind variables need to be instantiated into unification variables. - for (item_id, data_like) in mem::take(&mut self.binding_group.data) { - let Some(type_id) = self.binding_group.types.remove(&item_id) else { - continue; - }; - - let Some((quantified_type, quantified_variables)) = quantify::quantify(self, type_id) - else { - continue; - }; - - let kind_count = data_like.kind_variables.len() as u32; - let kind_variables = debruijn::Size(kind_count); - - let data_like = DataLike { quantified_variables, kind_variables }; - self.checked.data.insert(item_id, data_like); - - let type_id = transfer::globalize(self, context, quantified_type); - self.checked.types.insert(item_id, type_id); - } - - // Process class to be committed into the CheckedModule. This loops over - // the BindingGroupContext::class and generalises unsolved unification - // variables in their kinds. Like DataLike, it stores the number of - // quantified and kind variables in the type. This also generalises - // the class head itself since class head variables may have unsolved - // unification variables on inference mode. - for (item_id, mut class) in mem::take(&mut self.binding_group.classes) { - let Some(type_id) = self.binding_group.types.remove(&item_id) else { - continue; - }; - - let Some((quantified_type, quantified_variables)) = quantify::quantify(self, type_id) - else { - continue; - }; - - let class_quantified_count = - quantify::quantify_class(self, &mut class).unwrap_or(debruijn::Size(0)); - - debug_assert_eq!( - quantified_variables, class_quantified_count, - "critical violation: class type signature and declaration should have the same number of variables" - ); - - class.quantified_variables = quantified_variables; - - let superclasses = class.superclasses.iter().map(|&(t, k)| { - let t = transfer::globalize(self, context, t); - let k = transfer::globalize(self, context, k); - (t, k) - }); - - class.superclasses = superclasses.collect(); - - let type_variable_kinds = class - .type_variable_kinds - .iter() - .map(|&kind| transfer::globalize(self, context, kind)); - - class.type_variable_kinds = type_variable_kinds.collect(); - - self.checked.classes.insert(item_id, class); - - let type_id = transfer::globalize(self, context, quantified_type); - self.checked.types.insert(item_id, type_id); - } - - for (item_id, type_id) in mem::take(&mut self.binding_group.types) { - if let Some((quantified_type, _)) = quantify::quantify(self, type_id) { + // Process types to be committed into the CheckedModule. Generalizes + // pending kinds for recursive type declarations. + for (item_id, BindingGroupType(kind)) in mem::take(&mut self.binding_group.types) { + if let Some((quantified_type, _)) = quantify::quantify(self, kind) { let type_id = transfer::globalize(self, context, quantified_type); self.checked.types.insert(item_id, type_id); } } - // Process synonym to be committed into the CheckedModule. This loops - // over the BindingGroupContext::synonyms and generalises the synonym - // body itself. Kind generalisation is already handled in the previous - // loop. The compiler stores synonym bodies as generalised types to - // take advantage of existing patterns for instantiation, e.g. - // - // ``` - // type Fn a b = a -> b - // ``` - // - // is just stored as the following type: - // - // ``` - // forall a b. a -> b - // ``` - for (item_id, mut synonym) in mem::take(&mut self.binding_group.synonyms) { - if let Some((synonym_type, quantified_variables)) = - quantify::quantify(self, synonym.synonym_type) - { - synonym.quantified_variables = quantified_variables; - synonym.synonym_type = transfer::globalize(self, context, synonym_type); - self.checked.synonyms.insert(item_id, synonym); - } - } - Ok(()) } diff --git a/compiler-core/checking/src/algorithm/type_item.rs b/compiler-core/checking/src/algorithm/type_item.rs index 91ab0489..6593e830 100644 --- a/compiler-core/checking/src/algorithm/type_item.rs +++ b/compiler-core/checking/src/algorithm/type_item.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use building_types::QueryResult; -use indexing::TypeItemId; +use indexing::{TermItemId, TypeItemId}; use itertools::Itertools; use lowering::{ ClassIr, DataIr, NewtypeIr, SynonymIr, TermItemIr, TypeItemIr, TypeVariableBinding, @@ -9,244 +9,146 @@ use lowering::{ use smol_str::SmolStr; use crate::ExternalQueries; -use crate::algorithm::state::{BindingDataLike, CheckContext, CheckState, CheckedConstructor}; -use crate::algorithm::{inspect, kind, unification}; -use crate::core::{Class, ForallBinder, Operator, Synonym, Type, TypeId, Variable, debruijn}; +use crate::algorithm::state::{CheckContext, CheckState, CheckedConstructor}; +use crate::algorithm::{inspect, kind, quantify, transfer, unification}; +use crate::core::{ + Class, DataLike, ForallBinder, Operator, Synonym, Type, TypeId, Variable, debruijn, +}; use crate::error::{ErrorKind, ErrorStep}; const MISSING_NAME: SmolStr = SmolStr::new_static(""); +#[derive(Clone)] +pub struct CheckedSynonym { + pub inferred_kind: Option, + pub kind_variables: Vec, + pub type_variables: Vec, + pub synonym_type: TypeId, +} + +#[derive(Clone)] +pub struct CheckedData { + pub inferred_kind: Option, + pub kind_variables: Vec, + pub type_variables: Vec, + pub constructors: Vec, +} + +#[derive(Clone)] +pub struct CheckedClass { + pub inferred_kind: Option, + pub kind_variables: Vec, + pub type_variables: Vec, + pub superclasses: Arc<[(TypeId, TypeId)]>, + pub members: Vec<(TermItemId, TypeId)>, +} + +#[derive(Clone)] +pub struct CheckedOperator { + pub kind: TypeId, +} + +#[derive(Clone)] +pub enum CheckedTypeItem { + Synonym(CheckedSynonym), + Data(CheckedData), + Class(CheckedClass), + Operator(CheckedOperator), +} + +/// Checks a type item definition, returning [`CheckedTypeItem`]. +/// +/// See the following functions to learn more: +/// - [`check_data_definition`] +/// - [`check_synonym_definition`] +/// - [`check_class_definition`] pub fn check_type_item( state: &mut CheckState, context: &CheckContext, item_id: TypeItemId, -) -> QueryResult<()> +) -> QueryResult> where Q: ExternalQueries, { state.with_error_step(ErrorStep::TypeDeclaration(item_id), |state| { let Some(item) = context.lowered.info.get_type_item(item_id) else { - return Ok(()); + return Ok(None); }; match item { TypeItemIr::DataGroup { signature, data, .. } => { let Some(DataIr { variables }) = data else { - return Ok(()); + return Ok(None); }; - check_data_like(state, context, item_id, *signature, variables) + check_data_definition(state, context, item_id, *signature, variables) } TypeItemIr::NewtypeGroup { signature, newtype, .. } => { let Some(NewtypeIr { variables }) = newtype else { - return Ok(()); + return Ok(None); }; - check_data_like(state, context, item_id, *signature, variables) + check_data_definition(state, context, item_id, *signature, variables) } TypeItemIr::SynonymGroup { signature, synonym } => { let Some(SynonymIr { variables, synonym: Some(synonym) }) = synonym else { - return Ok(()); + return Ok(None); }; - check_synonym(state, context, item_id, *signature, variables, *synonym) + check_synonym_definition(state, context, item_id, *signature, variables, *synonym) } TypeItemIr::ClassGroup { signature, class } => { let Some(class) = class else { - return Ok(()); + return Ok(None); }; - check_class(state, context, item_id, *signature, class) + check_class_definition(state, context, item_id, *signature, class) } - TypeItemIr::Foreign { .. } => Ok(()), + TypeItemIr::Foreign { .. } => Ok(None), TypeItemIr::Operator { associativity, precedence, resolution } => { - let Some(associativity) = *associativity else { return Ok(()) }; - let Some(precedence) = *precedence else { return Ok(()) }; - let Some((file_id, type_id)) = *resolution else { return Ok(()) }; + let Some(associativity) = *associativity else { return Ok(None) }; + let Some(precedence) = *precedence else { return Ok(None) }; + let Some((file_id, type_id)) = *resolution else { return Ok(None) }; let operator = Operator { associativity, precedence, file_id, type_id }; state.checked.operators.insert(item_id, operator); - let id = kind::lookup_file_type(state, context, file_id, type_id)?; - - if !is_binary_operator_type(state, id) { - state.insert_error(ErrorKind::InvalidTypeOperator { id }); - } - - state.binding_group.types.insert(item_id, id); - - Ok(()) - } - } - }) -} - -pub fn check_type_signature( - state: &mut CheckState, - context: &CheckContext, - item_id: TypeItemId, -) -> QueryResult<()> -where - Q: ExternalQueries, -{ - state.with_error_step(ErrorStep::TypeDeclaration(item_id), |state| { - let Some(item) = context.lowered.info.get_type_item(item_id) else { - return Ok(()); - }; - - match item { - TypeItemIr::DataGroup { signature, .. } - | TypeItemIr::NewtypeGroup { signature, .. } - | TypeItemIr::SynonymGroup { signature, .. } - | TypeItemIr::ClassGroup { signature, .. } - | TypeItemIr::Foreign { signature, .. } => { - let Some(signature) = signature else { return Ok(()) }; - - let signature_variables = inspect::collect_signature_variables(context, *signature); - state.surface_bindings.insert_type(item_id, signature_variables); + let kind = kind::lookup_file_type(state, context, file_id, type_id)?; + unify_pending_kind(state, context, item_id, kind)?; - let (inferred_type, _) = - kind::check_surface_kind(state, context, *signature, context.prim.t)?; - state.binding_group.types.insert(item_id, inferred_type); + Ok(Some(CheckedTypeItem::Operator(CheckedOperator { kind }))) } - - TypeItemIr::Operator { .. } => {} } - - Ok(()) }) } -struct SignatureLike { - kind_variables: Vec, - type_variables: Vec, - result_kind: TypeId, -} - -fn check_signature_like( - state: &mut CheckState, - context: &CheckContext, - item_id: TypeItemId, - signature: Option, - variables: &[TypeVariableBinding], - infer_result: impl FnOnce(&mut CheckState) -> TypeId, -) -> QueryResult> -where - Q: ExternalQueries, -{ - let signature = if let Some(signature_id) = signature { - let stored_kind = kind::lookup_file_type(state, context, context.id, item_id)?; - - let surface_bindings = state.surface_bindings.get_type(item_id); - let surface_bindings = surface_bindings.as_deref().unwrap_or_default(); - - let signature = - inspect::inspect_signature_core(state, context, stored_kind, surface_bindings)?; - - if variables.len() != signature.arguments.len() { - state.insert_error(ErrorKind::TypeSignatureVariableMismatch { - id: signature_id, - expected: 0, - actual: 0, - }); - - if let Some(variable) = signature.variables.first() { - state.type_scope.unbind(variable.level); - } - - return Ok(None); - }; - - let variables = variables.iter(); - let arguments = signature.arguments.iter(); - - let kinds = variables - .zip(arguments) - .map(|(variable, &argument)| { - // Use contravariant subtyping for type variables: - // - // data Example :: Argument -> Type - // data Example (a :: Variable) = Example - // - // Signature: Argument -> Type - // Inferred: Variable -> Type - // - // Given - // Variable -> Type <: Argument -> Type - // - // Therefore - // [Argument <: Variable, Type <: Type] - let kind = if let Some(kind_id) = variable.kind { - let (kind, _) = kind::infer_surface_kind(state, context, kind_id)?; - let valid = unification::subtype(state, context, argument, kind)?; - if valid { kind } else { context.prim.unknown } - } else { - argument - }; - - let name = variable.name.clone().unwrap_or(MISSING_NAME); - Ok((variable.id, variable.visible, name, kind)) - }) - .collect::>>()?; - - let kind_variables = signature.variables; - let result_kind = signature.result; - let type_variables = kinds.into_iter().map(|(id, visible, name, kind)| { - let level = state.type_scope.bind_forall(id, kind); - ForallBinder { visible, name, level, kind } - }); - - let type_variables = type_variables.collect_vec(); - - SignatureLike { kind_variables, type_variables, result_kind } - } else { - let kind_variables = vec![]; - let result_kind = infer_result(state); - let type_variables = variables.iter().map(|variable| { - let kind = if let Some(id) = variable.kind { - let (kind, _) = kind::check_surface_kind(state, context, id, context.prim.t)?; - kind - } else { - state.fresh_unification_type(context) - }; - - let visible = variable.visible; - let name = variable.name.clone().unwrap_or(MISSING_NAME); - let level = state.type_scope.bind_forall(variable.id, kind); - Ok(ForallBinder { visible, name, level, kind }) - }); - - let type_variables = type_variables.collect::>>()?; - - SignatureLike { kind_variables, type_variables, result_kind } - }; - - Ok(Some(signature)) -} - -fn check_data_like( +/// Checks a data/newtype definition, returning [`CheckedTypeItem::Data`]. +fn check_data_definition( state: &mut CheckState, context: &CheckContext, item_id: TypeItemId, signature: Option, variables: &[TypeVariableBinding], -) -> QueryResult<()> +) -> QueryResult> where Q: ExternalQueries, { let Some(SignatureLike { kind_variables, type_variables, result_kind }) = check_signature_like(state, context, item_id, signature, variables, |_| context.prim.t)? else { - return Ok(()); + return Ok(None); }; - let type_kind = type_variables.iter().rfold(result_kind, |result, variable| { - state.storage.intern(Type::Function(variable.kind, result)) - }); + let mut inferred_kind = None; if signature.is_none() { - unify_pending_kind(state, context, item_id, type_kind)?; + let data_kind = type_variables.iter().rfold(result_kind, |result, variable| { + state.storage.intern(Type::Function(variable.kind, result)) + }); + + unify_pending_kind(state, context, item_id, data_kind)?; + inferred_kind.replace(data_kind); } let constructors = check_constructor_arguments(state, context, item_id)?; @@ -254,28 +156,30 @@ where let type_unbind_level = type_variables.first().map(|variable| variable.level); let kind_unbind_level = kind_variables.first().map(|variable| variable.level); - let data = BindingDataLike { kind_variables, type_variables, result_kind, constructors }; - state.binding_group.data.insert(item_id, data); - if let Some(level) = type_unbind_level { state.type_scope.unbind(level); } - if let Some(level) = kind_unbind_level { state.type_scope.unbind(level); } - Ok(()) + Ok(Some(CheckedTypeItem::Data(CheckedData { + inferred_kind, + kind_variables, + type_variables, + constructors, + }))) } -fn check_synonym( +/// Checks a synonym body, returning [`CheckedTypeItem::Synonym`]. +fn check_synonym_definition( state: &mut CheckState, context: &CheckContext, item_id: TypeItemId, signature: Option, variables: &[TypeVariableBinding], synonym: lowering::TypeId, -) -> QueryResult<()> +) -> QueryResult> where Q: ExternalQueries, { @@ -284,39 +188,44 @@ where state.fresh_unification_type(context) })? else { - return Ok(()); + return Ok(None); }; let (synonym_type, _) = kind::check_surface_kind(state, context, synonym, result_kind)?; - let type_kind = type_variables.iter().rfold(result_kind, |result, binder| { - state.storage.intern(Type::Function(binder.kind, result)) - }); + let mut inferred_kind = None; if signature.is_none() { - unify_pending_kind(state, context, item_id, type_kind)?; + let synonym_kind = type_variables.iter().rfold(result_kind, |result, binder| { + state.storage.intern(Type::Function(binder.kind, result)) + }); + unify_pending_kind(state, context, item_id, synonym_kind)?; + inferred_kind.replace(synonym_kind); } if let Some(variable) = type_variables.first() { state.type_scope.unbind(variable.level); } - if let Some(variable) = kind_variables.first() { state.type_scope.unbind(variable.level); } - insert_type_synonym(state, item_id, kind_variables, type_variables, synonym_type); - - Ok(()) + Ok(Some(CheckedTypeItem::Synonym(CheckedSynonym { + inferred_kind, + kind_variables, + type_variables, + synonym_type, + }))) } -fn check_class( +/// Checks a class body, returning [`CheckedTypeItem::Class`]. +fn check_class_definition( state: &mut CheckState, context: &CheckContext, item_id: TypeItemId, signature: Option, ClassIr { constraints, variables, .. }: &ClassIr, -) -> QueryResult<()> +) -> QueryResult> where Q: ExternalQueries, { @@ -325,26 +234,18 @@ where context.prim.constraint })? else { - return Ok(()); + return Ok(None); }; + // Check superclasses let superclasses = constraints.iter().map(|&constraint| { let (constraint_type, constraint_kind) = kind::check_surface_kind(state, context, constraint, context.prim.constraint)?; Ok((constraint_type, constraint_kind)) }); + let superclasses: Arc<[(TypeId, TypeId)]> = superclasses.collect::>()?; - let superclasses = superclasses.collect::>>()?; - - let class = { - let quantified_variables = debruijn::Size(0); - let kind_variables = debruijn::Size(kind_variables.len() as u32); - let type_variable_kinds = type_variables.iter().map(|binder| binder.kind).collect(); - Class { superclasses, type_variable_kinds, quantified_variables, kind_variables } - }; - - state.binding_group.classes.insert(item_id, class); - + // Build class reference for member type wrapping let class_reference = { let reference_type = state.storage.intern(Type::Constructor(context.id, item_id)); type_variables.iter().cloned().fold(reference_type, |reference_type, binder| { @@ -354,15 +255,17 @@ where }) }; - let class_kind = type_variables.iter().rfold(result_kind, |result, variable| { - state.storage.intern(Type::Function(variable.kind, result)) - }); + let mut inferred_kind = None; if signature.is_none() { + let class_kind = type_variables.iter().rfold(result_kind, |result, variable| { + state.storage.intern(Type::Function(variable.kind, result)) + }); unify_pending_kind(state, context, item_id, class_kind)?; + inferred_kind.replace(class_kind); } - check_class_members( + let members = check_class_members( state, context, item_id, @@ -374,14 +277,20 @@ where if let Some(variable) = type_variables.first() { state.type_scope.unbind(variable.level); } - if let Some(variable) = kind_variables.first() { state.type_scope.unbind(variable.level); } - Ok(()) + Ok(Some(CheckedTypeItem::Class(CheckedClass { + inferred_kind, + kind_variables, + type_variables, + superclasses, + members, + }))) } +/// Checks class members inline, returning their types. fn check_class_members( state: &mut CheckState, context: &CheckContext, @@ -389,10 +298,12 @@ fn check_class_members( kind_variables: &[ForallBinder], type_variables: &[ForallBinder], class_reference: TypeId, -) -> QueryResult<()> +) -> QueryResult> where Q: ExternalQueries, { + let mut members = vec![]; + for member_id in context.indexed.pairs.class_members(item_id) { let Some(TermItemIr::ClassMember { signature }) = context.lowered.info.get_term_item(member_id) @@ -421,69 +332,49 @@ where state.storage.intern(Type::Forall(variable, inner)) }); - if let Some(pending_type) = state.binding_group.terms.get(&member_id) { - let _ = unification::unify(state, context, *pending_type, member_type)?; - } else { - state.binding_group.terms.insert(member_id, member_type); - } - } - - Ok(()) -} - -fn collect_foralls(state: &CheckState, mut id: TypeId) -> (Vec, TypeId) { - let mut foralls = vec![]; - - while let Type::Forall(ref binder, inner) = state.storage[id] { - foralls.push(binder.clone()); - id = inner; + members.push((member_id, member_type)); } - (foralls, id) + Ok(members) } -fn is_binary_operator_type(state: &CheckState, mut id: TypeId) -> bool { - while let Type::Forall(_, inner_id) = state.storage[id] { - id = inner_id; +/// Generalises the inferred types for a [`CheckedTypeItem`]. +pub fn commit_type_item( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, + item: CheckedTypeItem, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + match item { + CheckedTypeItem::Synonym(synonym) => commit_synonym(state, context, item_id, synonym), + CheckedTypeItem::Data(data) => commit_data(state, context, item_id, data), + CheckedTypeItem::Class(class) => commit_class(state, context, item_id, class), + CheckedTypeItem::Operator(operator) => commit_operator(state, context, item_id, operator), } - - let Type::Function(_, result_id) = state.storage[id] else { - return false; - }; - - let Type::Function(_, result_id) = state.storage[result_id] else { - return false; - }; - - !matches!(state.storage[result_id], Type::Function(_, _)) } -fn unify_pending_kind( +/// Generalises the inferred types for a [`CheckedSynonym`]. +fn commit_synonym( state: &mut CheckState, context: &CheckContext, item_id: TypeItemId, - item_kind: TypeId, + checked: CheckedSynonym, ) -> QueryResult<()> where Q: ExternalQueries, { - let pending_kind = state - .binding_group - .lookup_type(item_id) - .expect("invariant violated: invalid binding_group in kind inference"); - - let _ = unification::subtype(state, context, item_kind, pending_kind)?; + let CheckedSynonym { inferred_kind, kind_variables, type_variables, synonym_type } = checked; - Ok(()) -} + if let Some(inferred_kind) = inferred_kind + && let Some((quantified_type, _)) = quantify::quantify(state, inferred_kind) + { + let type_id = transfer::globalize(state, context, quantified_type); + state.checked.types.insert(item_id, type_id); + } -fn insert_type_synonym( - state: &mut CheckState, - item_id: TypeItemId, - kind_variables: Vec, - type_variables: Vec, - synonym_type: TypeId, -) { let synonym_type = type_variables.iter().rfold(synonym_type, |inner, binder| { let binder = binder.clone(); state.storage.intern(Type::Forall(binder, inner)) @@ -494,118 +385,464 @@ fn insert_type_synonym( state.storage.intern(Type::Forall(binder, inner)) }); - let quantified_variables = debruijn::Size(0); + if let Some((quantified_synonym, quantified_variables)) = + quantify::quantify(state, synonym_type) + { + let kind_var_count = kind_variables.len() as u32; + let kind_variables = debruijn::Size(kind_var_count); - let kind_variables = { - let length = kind_variables.len(); - debruijn::Size(length as u32) - }; + let kind_var_count = type_variables.len() as u32; + let type_variables = debruijn::Size(kind_var_count); - let type_variables = { - let length = type_variables.len(); - debruijn::Size(length as u32) - }; + let synonym_type = transfer::globalize(state, context, quantified_synonym); + + let synonym = + Synonym { quantified_variables, kind_variables, type_variables, synonym_type }; + + state.checked.synonyms.insert(item_id, synonym); + } - let group = Synonym { quantified_variables, kind_variables, type_variables, synonym_type }; - state.binding_group.synonyms.insert(item_id, group); + Ok(()) } -fn check_constructor_arguments( +/// Generalises the inferred types for a [`CheckedData`]. +fn commit_data( state: &mut CheckState, context: &CheckContext, item_id: TypeItemId, -) -> QueryResult> + checked: CheckedData, +) -> QueryResult<()> where Q: ExternalQueries, { - let mut constructors = vec![]; + let CheckedData { inferred_kind, kind_variables, type_variables, constructors } = checked; + let kind_variable_count = kind_variables.len() as u32; - for item_id in context.indexed.pairs.data_constructors(item_id) { - let Some(TermItemIr::Constructor { arguments }) = - context.lowered.info.get_term_item(item_id) + if let Some(inferred_kind) = inferred_kind { + let Some((quantified_type, quantified_variables)) = + quantify::quantify(state, inferred_kind) else { - continue; + return Ok(()); }; - let mut inferred_arguments = vec![]; + let kind_variables = debruijn::Size(kind_variable_count); - for &argument in arguments.iter() { - let inferred_type = - state.with_error_step(ErrorStep::ConstructorArgument(argument), |state| { - let (inferred_type, _) = - kind::check_surface_kind(state, context, argument, context.prim.t)?; - Ok(inferred_type) - })?; - inferred_arguments.push(inferred_type); - } + let data_like = DataLike { quantified_variables, kind_variables }; + state.checked.data.insert(item_id, data_like); - constructors.push(CheckedConstructor { item_id, arguments: inferred_arguments }); + let type_id = transfer::globalize(state, context, quantified_type); + state.checked.types.insert(item_id, type_id); + } else { + let quantified_variables = debruijn::Size(0); + let kind_variables = debruijn::Size(kind_variable_count); + + let data_like = DataLike { quantified_variables, kind_variables }; + state.checked.data.insert(item_id, data_like); + }; + + let data_reference = + build_data_reference(state, context, item_id, &kind_variables, &type_variables); + + for constructor in constructors { + let constructor_type = + constructor.arguments.iter().rfold(data_reference, |result, &argument| { + state.storage.intern(Type::Function(argument, result)) + }); + + let all_variables = { + let from_kind = kind_variables.iter(); + let from_type = type_variables.iter(); + from_kind.chain(from_type).cloned() + }; + + let constructor_type = all_variables.rfold(constructor_type, |inner, variable| { + state.storage.intern(Type::Forall(variable, inner)) + }); + + if let Some((quantified_constructor, _)) = quantify::quantify(state, constructor_type) { + let constructor_type = transfer::globalize(state, context, quantified_constructor); + state.checked.terms.insert(constructor.item_id, constructor_type); + } } - Ok(constructors) + Ok(()) } -pub fn build_constructor_types( +fn build_data_reference( state: &mut CheckState, context: &CheckContext, item_id: TypeItemId, kind_variables: &[ForallBinder], type_variables: &[ForallBinder], - constructors: &[CheckedConstructor], +) -> TypeId +where + Q: ExternalQueries, +{ + let reference_type = state.storage.intern(Type::Constructor(context.id, item_id)); + + let reference_type = kind_variables.iter().fold(reference_type, |reference, binder| { + let variable = Variable::Bound(binder.level); + let variable = state.storage.intern(Type::Variable(variable)); + state.storage.intern(Type::KindApplication(reference, variable)) + }); + + let unsolved_kinds = type_variables.iter().filter_map(|binder| { + let kind = state.normalize_type(binder.kind); + if let Type::Unification(id) = state.storage[kind] { Some((kind, id)) } else { None } + }); + + let mut unsolved_kinds = unsolved_kinds.collect_vec(); + unsolved_kinds.sort_by_key(|&(_, id)| (state.unification.get(id).domain, id)); + + let reference_type = unsolved_kinds.iter().fold(reference_type, |reference, &(kind, _)| { + state.storage.intern(Type::KindApplication(reference, kind)) + }); + + type_variables.iter().fold(reference_type, |reference, binder| { + let variable = Variable::Bound(binder.level); + let variable = state.storage.intern(Type::Variable(variable)); + state.storage.intern(Type::Application(reference, variable)) + }) +} + +/// Generalises the inferred types for a [`CheckedClass`]. +fn commit_class( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, + checked: CheckedClass, ) -> QueryResult<()> where Q: ExternalQueries, { - let data_reference = { - let reference_type = state.storage.intern(Type::Constructor(context.id, item_id)); + let CheckedClass { inferred_kind, kind_variables, type_variables, superclasses, members } = + checked; - let reference_type = kind_variables.iter().fold(reference_type, |reference, binder| { - let variable = Variable::Bound(binder.level); - let variable = state.storage.intern(Type::Variable(variable)); - state.storage.intern(Type::KindApplication(reference, variable)) - }); + let mut quantified_type = None; + let mut quantified_variables = debruijn::Size(0); + + if let Some(inferred_kind) = inferred_kind + && let Some((q_type, q_variables)) = quantify::quantify(state, inferred_kind) + { + quantified_type = Some(q_type); + quantified_variables = q_variables; + }; + + let mut class = { + let kind_var_count = kind_variables.len() as u32; + let kind_variables = debruijn::Size(kind_var_count); + let type_variable_kinds = type_variables.iter().map(|binder| binder.kind).collect(); + Class { superclasses, type_variable_kinds, quantified_variables, kind_variables } + }; + + let class_quantified_count = + quantify::quantify_class(state, &mut class).unwrap_or(debruijn::Size(0)); + + debug_assert_eq!( + quantified_variables, class_quantified_count, + "critical violation: class type signature and declaration should have the same number of variables" + ); + + class.quantified_variables = quantified_variables; + + let superclasses = class.superclasses.iter().map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }); - let unsolved_kinds = type_variables.iter().filter_map(|binder| { - let kind = state.normalize_type(binder.kind); - if let Type::Unification(id) = state.storage[kind] { Some((kind, id)) } else { None } + class.superclasses = superclasses.collect(); + + let type_variable_kinds = + class.type_variable_kinds.iter().map(|&kind| transfer::globalize(state, context, kind)); + + class.type_variable_kinds = type_variable_kinds.collect(); + + state.checked.classes.insert(item_id, class); + + if let Some(quantified_type) = quantified_type { + let type_id = transfer::globalize(state, context, quantified_type); + state.checked.types.insert(item_id, type_id); + } + + for (member_id, member_type) in members { + if let Some((quantified_member, _)) = quantify::quantify(state, member_type) { + let member_type = transfer::globalize(state, context, quantified_member); + state.checked.terms.insert(member_id, member_type); + } + } + + Ok(()) +} + +/// Commits an operator, checking validity and storing the kind. +fn commit_operator( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, + checked: CheckedOperator, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let CheckedOperator { kind } = checked; + + // Now that all items in the SCC are processed, the kind should be fully resolved + if !is_binary_operator_type(state, kind) { + state.insert_error(ErrorKind::InvalidTypeOperator { id: kind }); + } + + // Generalize and store the kind + if let Some((quantified_type, _)) = quantify::quantify(state, kind) { + let type_id = transfer::globalize(state, context, quantified_type); + state.checked.types.insert(item_id, type_id); + } + + Ok(()) +} + +/// Checks the kind signature of a type item. +/// +/// This function also generalises the type and inserts it directly to +/// [`CheckState::checked`], effectively making signatures the ground +/// truth for type definitions to check against. +/// +/// To enable scoped type variables, this function also populates the +/// [`CheckState::surface_bindings`] with the kind variables found in +/// the signature. +pub fn check_type_signature( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + state.with_error_step(ErrorStep::TypeDeclaration(item_id), |state| { + let Some(item) = context.lowered.info.get_type_item(item_id) else { + return Ok(()); + }; + + match item { + TypeItemIr::DataGroup { signature, .. } + | TypeItemIr::NewtypeGroup { signature, .. } + | TypeItemIr::SynonymGroup { signature, .. } + | TypeItemIr::ClassGroup { signature, .. } + | TypeItemIr::Foreign { signature, .. } => { + let Some(signature) = signature else { + return Ok(()); + }; + + let signature_variables = inspect::collect_signature_variables(context, *signature); + state.surface_bindings.insert_type(item_id, signature_variables); + + let (inferred_type, _) = + kind::check_surface_kind(state, context, *signature, context.prim.t)?; + + if let Some((quantified_type, _)) = quantify::quantify(state, inferred_type) { + let type_id = transfer::globalize(state, context, quantified_type); + state.checked.types.insert(item_id, type_id); + } + } + + TypeItemIr::Operator { .. } => {} + } + + Ok(()) + }) +} + +struct SignatureLike { + kind_variables: Vec, + type_variables: Vec, + result_kind: TypeId, +} + +fn check_signature_like( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, + signature: Option, + variables: &[TypeVariableBinding], + infer_result: impl FnOnce(&mut CheckState) -> TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let signature = if let Some(signature_id) = signature { + let stored_kind = kind::lookup_file_type(state, context, context.id, item_id)?; + + let surface_bindings = state.surface_bindings.get_type(item_id); + let surface_bindings = surface_bindings.as_deref().unwrap_or_default(); + + let signature = + inspect::inspect_signature_core(state, context, stored_kind, surface_bindings)?; + + if variables.len() != signature.arguments.len() { + state.insert_error(ErrorKind::TypeSignatureVariableMismatch { + id: signature_id, + expected: 0, + actual: 0, + }); + + if let Some(variable) = signature.variables.first() { + state.type_scope.unbind(variable.level); + } + + return Ok(None); + }; + + let variables = variables.iter(); + let arguments = signature.arguments.iter(); + + let kinds = variables + .zip(arguments) + .map(|(variable, &argument)| { + // Use contravariant subtyping for type variables: + // + // data Example :: Argument -> Type + // data Example (a :: Variable) = Example + // + // Signature: Argument -> Type + // Inferred: Variable -> Type + // + // Given + // Variable -> Type <: Argument -> Type + // + // Therefore + // [Argument <: Variable, Type <: Type] + let kind = if let Some(kind_id) = variable.kind { + let (kind, _) = kind::infer_surface_kind(state, context, kind_id)?; + let valid = unification::subtype(state, context, argument, kind)?; + if valid { kind } else { context.prim.unknown } + } else { + argument + }; + + let name = variable.name.clone().unwrap_or(MISSING_NAME); + Ok((variable.id, variable.visible, name, kind)) + }) + .collect::>>()?; + + let kind_variables = signature.variables; + let result_kind = signature.result; + let type_variables = kinds.into_iter().map(|(id, visible, name, kind)| { + let level = state.type_scope.bind_forall(id, kind); + ForallBinder { visible, name, level, kind } }); - let mut unsolved_kinds = unsolved_kinds.collect_vec(); - unsolved_kinds.sort_by_key(|&(_, id)| (state.unification.get(id).domain, id)); + let type_variables = type_variables.collect_vec(); + + SignatureLike { kind_variables, type_variables, result_kind } + } else { + let kind_variables = vec![]; + let result_kind = infer_result(state); + let type_variables = variables.iter().map(|variable| { + let kind = if let Some(id) = variable.kind { + let (kind, _) = kind::check_surface_kind(state, context, id, context.prim.t)?; + kind + } else { + state.fresh_unification_type(context) + }; - let reference_type = unsolved_kinds.iter().fold(reference_type, |reference, &(kind, _)| { - state.storage.intern(Type::KindApplication(reference, kind)) + let visible = variable.visible; + let name = variable.name.clone().unwrap_or(MISSING_NAME); + let level = state.type_scope.bind_forall(variable.id, kind); + Ok(ForallBinder { visible, name, level, kind }) }); - type_variables.iter().fold(reference_type, |reference, binder| { - let variable = Variable::Bound(binder.level); - let variable = state.storage.intern(Type::Variable(variable)); - state.storage.intern(Type::Application(reference, variable)) - }) + let type_variables = type_variables.collect::>>()?; + + SignatureLike { kind_variables, type_variables, result_kind } }; - for constructor in constructors { - let constructor_type = - constructor.arguments.iter().rfold(data_reference, |result, &argument| { - state.storage.intern(Type::Function(argument, result)) - }); + Ok(Some(signature)) +} - let all_variables = { - let from_kind = kind_variables.iter(); - let from_type = type_variables.iter(); - from_kind.chain(from_type).cloned() +fn collect_foralls(state: &CheckState, mut id: TypeId) -> (Vec, TypeId) { + let mut foralls = vec![]; + + while let Type::Forall(ref binder, inner) = state.storage[id] { + foralls.push(binder.clone()); + id = inner; + } + + (foralls, id) +} + +pub fn is_binary_operator_type(state: &mut CheckState, mut id: TypeId) -> bool { + // Normalize to resolve unification variables (important for Scc::Mutual) + id = state.normalize_type(id); + + while let Type::Forall(_, inner_id) = state.storage[id] { + id = inner_id; + } + + let Type::Function(_, result_id) = state.storage[id] else { + return false; + }; + + let result_id = state.normalize_type(result_id); + let Type::Function(_, result_id) = state.storage[result_id] else { + return false; + }; + + let result_id = state.normalize_type(result_id); + !matches!(state.storage[result_id], Type::Function(_, _)) +} + +/// Unifies a computed kind with the pending kind for recursive/mutual types. +/// +/// Only called when the item has no explicit signature (signature.is_none()). +/// For Scc::Recursive and Scc::Mutual, unifies with the pending kind created +/// by begin_type_group. For Scc::Base, this is a no-op since there's no +/// pending kind to unify with. +fn unify_pending_kind( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, + item_kind: TypeId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + if let Some(pending_kind) = state.binding_group.lookup_type(item_id) { + let _ = unification::subtype(state, context, item_kind, pending_kind)?; + } + Ok(()) +} + +fn check_constructor_arguments( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let mut constructors = vec![]; + + for item_id in context.indexed.pairs.data_constructors(item_id) { + let Some(TermItemIr::Constructor { arguments }) = + context.lowered.info.get_term_item(item_id) + else { + continue; }; - let constructor_type = all_variables.rfold(constructor_type, |inner, variable| { - state.storage.intern(Type::Forall(variable, inner)) - }); + let mut inferred_arguments = vec![]; - if let Some(pending_type) = state.binding_group.terms.get(&constructor.item_id) { - let _ = unification::unify(state, context, *pending_type, constructor_type)?; - } else { - state.binding_group.terms.insert(constructor.item_id, constructor_type); + for &argument in arguments.iter() { + let inferred_type = + state.with_error_step(ErrorStep::ConstructorArgument(argument), |state| { + let (inferred_type, _) = + kind::check_surface_kind(state, context, argument, context.prim.t)?; + Ok(inferred_type) + })?; + inferred_arguments.push(inferred_type); } + + constructors.push(CheckedConstructor { item_id, arguments: inferred_arguments }); } - Ok(()) + Ok(constructors) } diff --git a/compiler-core/lowering/src/lib.rs b/compiler-core/lowering/src/lib.rs index 30d3e0b8..abc50841 100644 --- a/compiler-core/lowering/src/lib.rs +++ b/compiler-core/lowering/src/lib.rs @@ -53,17 +53,8 @@ pub fn lower_module( indexed: &IndexedModule, resolved: &ResolvedModule, ) -> LoweredModule { - let algorithm::State { - info, - graph, - nodes, - term_graph, - type_graph, - kind_graph, - synonym_graph, - mut errors, - .. - } = algorithm::lower_module(file_id, module, prim, stabilized, indexed, resolved); + let algorithm::State { info, graph, nodes, term_graph, type_graph, kind_graph, synonym_graph, mut errors, .. } = + algorithm::lower_module(file_id, module, prim, stabilized, indexed, resolved); let term_scc = tarjan_scc(&term_graph); let term_scc = term_scc.into_iter().map(into_scc(&term_graph)).collect(); diff --git a/tests-integration/fixtures/checking/002_proxy_inference/Main.snap b/tests-integration/fixtures/checking/002_proxy_inference/Main.snap index 7a4868a7..5021cb1b 100644 --- a/tests-integration/fixtures/checking/002_proxy_inference/Main.snap +++ b/tests-integration/fixtures/checking/002_proxy_inference/Main.snap @@ -3,10 +3,10 @@ source: tests-integration/tests/checking/generated.rs expression: report --- Terms -Proxy :: forall (t1 :: Type) (a :: t1). Proxy @t1 a +Proxy :: forall (t0 :: Type) (a :: t0). Proxy @t0 a Types -Proxy :: forall (t1 :: Type). t1 -> Type +Proxy :: forall (t0 :: Type). t0 -> Type Data Proxy diff --git a/tests-integration/fixtures/checking/006_type_synonym/Main.snap b/tests-integration/fixtures/checking/006_type_synonym/Main.snap index 2bdd114a..f649b0f1 100644 --- a/tests-integration/fixtures/checking/006_type_synonym/Main.snap +++ b/tests-integration/fixtures/checking/006_type_synonym/Main.snap @@ -8,7 +8,7 @@ Types Tuple :: Type -> Type -> Type AliasType :: Type AliasTypeType :: Type -> Type -InferApply :: forall (t7 :: Type) (t12 :: Type). (t12 -> t7) -> t12 -> t7 +InferApply :: forall (t4 :: Type) (t9 :: Type). (t9 -> t4) -> t9 -> t4 InferTuple :: Type -> Type -> Type CheckApply :: forall (x :: Type) (y :: Type). (x -> y) -> x -> y CheckApplyElab :: forall (x :: Type) (y :: Type). (x -> y) -> x -> y @@ -24,7 +24,7 @@ AliasTypeType = Array Kind = :0 Type = :0 -InferApply = forall (t7 :: Type) (t12 :: Type) (f :: t12 -> t7) (a :: t12). f a +InferApply = forall (t4 :: Type) (t9 :: Type) (f :: t9 -> t4) (a :: t9). f a Quantified = :2 Kind = :0 Type = :2 diff --git a/tests-integration/fixtures/checking/007_foreign_poly/Main.snap b/tests-integration/fixtures/checking/007_foreign_poly/Main.snap index 474e6fd6..fd5a2567 100644 --- a/tests-integration/fixtures/checking/007_foreign_poly/Main.snap +++ b/tests-integration/fixtures/checking/007_foreign_poly/Main.snap @@ -6,10 +6,10 @@ Terms Types TuplePoly :: forall (a :: Type) (b :: Type). a -> b -> Type -InferTuplePoly :: forall (t8 :: Type) (t10 :: Type). t8 -> t10 -> Type +InferTuplePoly :: forall (t7 :: Type) (t8 :: Type). t7 -> t8 -> Type Synonyms -InferTuplePoly = forall (t8 :: Type) (t10 :: Type) (x :: t8) (y :: t10). TuplePoly @t8 @t10 x y +InferTuplePoly = forall (t7 :: Type) (t8 :: Type) (x :: t7) (y :: t8). TuplePoly @t7 @t8 x y Quantified = :2 Kind = :0 Type = :2 diff --git a/tests-integration/fixtures/checking/009_expand_identity_synonym/Main.snap b/tests-integration/fixtures/checking/009_expand_identity_synonym/Main.snap index 02ebae37..0f911fa4 100644 --- a/tests-integration/fixtures/checking/009_expand_identity_synonym/Main.snap +++ b/tests-integration/fixtures/checking/009_expand_identity_synonym/Main.snap @@ -5,13 +5,13 @@ expression: report Terms Types -Identity :: forall (t1 :: Type). t1 -> t1 +Identity :: forall (t0 :: Type). t0 -> t0 Digit :: Int Test1 :: Type Test2 :: Int Synonyms -Identity = forall (t1 :: Type) (a :: t1). a +Identity = forall (t0 :: Type) (a :: t0). a Quantified = :1 Kind = :0 Type = :1 diff --git a/tests-integration/fixtures/checking/013_class_phantom/Main.snap b/tests-integration/fixtures/checking/013_class_phantom/Main.snap index 6f8fcf7c..1614b466 100644 --- a/tests-integration/fixtures/checking/013_class_phantom/Main.snap +++ b/tests-integration/fixtures/checking/013_class_phantom/Main.snap @@ -3,10 +3,10 @@ source: tests-integration/tests/checking/generated.rs expression: report --- Terms -value :: forall (t1 :: Type) (a :: t1). Phantom a => Int +value :: forall (t0 :: Type) (a :: t0). Phantom a => Int Types -Phantom :: forall (t1 :: Type). t1 -> Constraint +Phantom :: forall (t0 :: Type). t0 -> Constraint Classes class Phantom (&1 :: &0) diff --git a/tests-integration/fixtures/checking/018_type_operator_valid/Main.snap b/tests-integration/fixtures/checking/018_type_operator_valid/Main.snap index 5ca83557..27618457 100644 --- a/tests-integration/fixtures/checking/018_type_operator_valid/Main.snap +++ b/tests-integration/fixtures/checking/018_type_operator_valid/Main.snap @@ -5,11 +5,11 @@ expression: report Terms Types -Add :: forall (t1 :: Type) (t4 :: Type). t1 -> t4 -> t1 -+ :: forall (t1 :: Type) (t4 :: Type). t1 -> t4 -> t1 +Add :: forall (t0 :: Type) (t2 :: Type). t0 -> t2 -> t0 ++ :: forall (t0 :: Type) (t2 :: Type). t0 -> t2 -> t0 Synonyms -Add = forall (t1 :: Type) (t4 :: Type) (a :: t1) (b :: t4). a +Add = forall (t0 :: Type) (t2 :: Type) (a :: t0) (b :: t2). a Quantified = :2 Kind = :0 Type = :2 diff --git a/tests-integration/fixtures/checking/026_row_empty/Main.snap b/tests-integration/fixtures/checking/026_row_empty/Main.snap index c19e311d..86021ade 100644 --- a/tests-integration/fixtures/checking/026_row_empty/Main.snap +++ b/tests-integration/fixtures/checking/026_row_empty/Main.snap @@ -5,9 +5,9 @@ expression: report Terms Types -EmptyRow :: forall (t2 :: Type). Row t2 +EmptyRow :: forall (t1 :: Type). Row t1 EmptyRecord :: Type -TailOnly :: forall (t10 :: Type). Row t10 -> Row t10 +TailOnly :: forall (t7 :: Type). Row t7 -> Row t7 TailOnlyRecord :: Row Type -> Type Synonyms @@ -21,7 +21,7 @@ EmptyRecord = {} Kind = :0 Type = :0 -TailOnly = forall (t10 :: Type) (r :: Row t10). ( | r ) +TailOnly = forall (t7 :: Type) (r :: Row t7). ( | r ) Quantified = :1 Kind = :0 Type = :1 diff --git a/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap b/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap index e9507b1c..d7dd7ff9 100644 --- a/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap +++ b/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap @@ -1,5 +1,6 @@ --- source: tests-integration/tests/checking/generated.rs +assertion_line: 12 expression: report --- Terms @@ -57,6 +58,14 @@ Identity Quantified = :0 Kind = :0 +ReaderT + Quantified = :0 + Kind = :0 + +StateT + Quantified = :0 + Kind = :0 + Tuple Quantified = :0 Kind = :0 diff --git a/tests-integration/fixtures/checking/031_partial_synonym_polykind/Main.snap b/tests-integration/fixtures/checking/031_partial_synonym_polykind/Main.snap index 930caeed..1f6286c9 100644 --- a/tests-integration/fixtures/checking/031_partial_synonym_polykind/Main.snap +++ b/tests-integration/fixtures/checking/031_partial_synonym_polykind/Main.snap @@ -1,5 +1,6 @@ --- source: tests-integration/tests/checking/generated.rs +assertion_line: 12 expression: report --- Terms @@ -73,3 +74,9 @@ EndoFn = forall (a :: Type). Endo Function a Quantified = :0 Kind = :0 Type = :1 + + +Data +Function + Quantified = :0 + Kind = :0 diff --git a/tests-integration/fixtures/checking/044_binder_constructor/Main.snap b/tests-integration/fixtures/checking/044_binder_constructor/Main.snap index 27f4d30e..244cc65b 100644 --- a/tests-integration/fixtures/checking/044_binder_constructor/Main.snap +++ b/tests-integration/fixtures/checking/044_binder_constructor/Main.snap @@ -12,10 +12,10 @@ foo :: Pair Int String -> Int bar :: Maybe Int -> Int baz :: List Int -> Int qux :: Pair String String -> String -foo' :: forall (t18 :: Type) (t19 :: Type). Pair t19 t18 -> t19 +foo' :: forall (t15 :: Type) (t16 :: Type). Pair t16 t15 -> t16 bar' :: Maybe Int -> Int baz' :: List Int -> Int -qux' :: forall (t32 :: Type) (t33 :: Type). Pair t33 t32 -> t33 +qux' :: forall (t29 :: Type) (t30 :: Type). Pair t30 t29 -> t30 Types Pair :: Type -> Type -> Type diff --git a/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap b/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap index b78cd442..c281f778 100644 --- a/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap +++ b/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap @@ -8,7 +8,7 @@ bind :: forall (m :: Type -> Type) (a :: Type) (b :: Type). m a -> (a -> m b) -> discard :: forall (m :: Type -> Type) (a :: Type) (b :: Type). m a -> (a -> m b) -> m b pure :: forall (m :: Type -> Type) (a :: Type). a -> m a test :: forall (m :: Type -> Type). m (Tuple Int String) -test' :: forall (t60 :: Type -> Type). t60 (Tuple Int String) +test' :: forall (t58 :: Type -> Type). t58 (Tuple Int String) Types Tuple :: Type -> Type -> Type diff --git a/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap b/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap index 16be8751..783a485d 100644 --- a/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap +++ b/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap @@ -8,7 +8,7 @@ map :: forall (f :: Type -> Type) (a :: Type) (b :: Type). (a -> b) -> f a -> f apply :: forall (f :: Type -> Type) (a :: Type) (b :: Type). f (a -> b) -> f a -> f b pure :: forall (f :: Type -> Type) (a :: Type). a -> f a test :: forall (f :: Type -> Type). f (Tuple Int String) -test' :: forall (t56 :: Type -> Type). t56 (Tuple Int String) +test' :: forall (t54 :: Type -> Type). t54 (Tuple Int String) Types Tuple :: Type -> Type -> Type diff --git a/tests-integration/fixtures/checking/062_case_of/Main.snap b/tests-integration/fixtures/checking/062_case_of/Main.snap index bb04cd52..ef1c3f52 100644 --- a/tests-integration/fixtures/checking/062_case_of/Main.snap +++ b/tests-integration/fixtures/checking/062_case_of/Main.snap @@ -13,7 +13,7 @@ test1' :: Maybe Int -> Int test2 :: Maybe Int -> Maybe Int -> Int test2' :: Maybe Int -> Maybe Int -> Int test3 :: Either Int String -> Int -test3' :: forall (t51 :: Type). Either Int t51 -> Int +test3' :: forall (t46 :: Type). Either Int t46 -> Int test4 :: Tuple (Maybe Int) (Maybe Int) -> Int test4' :: Tuple (Maybe Int) (Maybe Int) -> Int diff --git a/tests-integration/fixtures/checking/068_expression_sections/Main.snap b/tests-integration/fixtures/checking/068_expression_sections/Main.snap index d35c1cf0..25603427 100644 --- a/tests-integration/fixtures/checking/068_expression_sections/Main.snap +++ b/tests-integration/fixtures/checking/068_expression_sections/Main.snap @@ -10,19 +10,19 @@ negate :: Int -> Int test1 :: Int -> Int test2 :: Int -> Int test3 :: Int -> Int -> Int -test4 :: forall (t17 :: Type) (t18 :: Row Type). { foo :: t17 | t18 } -> t17 -test5 :: forall (t22 :: Type) (t23 :: Row Type). { foo :: t22 | t23 } -> { foo :: Int | t23 } -test6 :: forall (t29 :: Type). Boolean -> t29 -> t29 -> t29 +test4 :: forall (t15 :: Type) (t16 :: Row Type). { foo :: t15 | t16 } -> t15 +test5 :: forall (t20 :: Type) (t21 :: Row Type). { foo :: t20 | t21 } -> { foo :: Int | t21 } +test6 :: forall (t27 :: Type). Boolean -> t27 -> t27 -> t27 test7 :: Int -> Int test8 :: Int -> Int test9 :: Int -> Int -> Int -test10 :: forall (t43 :: Type). ((Int -> Int) -> t43) -> t43 -test11 :: forall (t50 :: Type). t50 -> Array t50 -test12 :: forall (t53 :: Type). t53 -> { foo :: t53 } -test13 :: forall (t56 :: Type). t56 -> t56 -test14 :: forall (t61 :: Type). t61 -> t61 -> Array t61 -test15 :: forall (t64 :: Type) (t65 :: Type). t64 -> t65 -> { a :: t64, b :: t65 } -test16 :: forall (t69 :: Type). t69 -> Tuple t69 Int +test10 :: forall (t41 :: Type). ((Int -> Int) -> t41) -> t41 +test11 :: forall (t48 :: Type). t48 -> Array t48 +test12 :: forall (t51 :: Type). t51 -> { foo :: t51 } +test13 :: forall (t54 :: Type). t54 -> t54 +test14 :: forall (t59 :: Type). t59 -> t59 -> Array t59 +test15 :: forall (t62 :: Type) (t63 :: Type). t62 -> t63 -> { a :: t62, b :: t63 } +test16 :: forall (t67 :: Type). t67 -> Tuple t67 Int test17 :: Tuple String Int test18 :: Int -> Int diff --git a/tests-integration/fixtures/checking/086_instance_functional_dependency_transitive/Main.snap b/tests-integration/fixtures/checking/086_instance_functional_dependency_transitive/Main.snap index e4350fb1..aa5d08c3 100644 --- a/tests-integration/fixtures/checking/086_instance_functional_dependency_transitive/Main.snap +++ b/tests-integration/fixtures/checking/086_instance_functional_dependency_transitive/Main.snap @@ -3,11 +3,11 @@ source: tests-integration/tests/checking/generated.rs expression: report --- Terms -chain :: forall (t4 :: Type) (a :: Type) (b :: t4) (c :: Type). Chain a b c => a -> c +chain :: forall (t1 :: Type) (a :: Type) (b :: t1) (c :: Type). Chain a b c => a -> c test :: Boolean Types -Chain :: forall (t4 :: Type). Type -> t4 -> Type -> Constraint +Chain :: forall (t1 :: Type). Type -> t1 -> Type -> Constraint Classes class Chain (&1 :: Type) (&2 :: &0) (&3 :: Type) diff --git a/tests-integration/fixtures/checking/090_instance_improve/Main.snap b/tests-integration/fixtures/checking/090_instance_improve/Main.snap index db4a3d3a..26f88cdc 100644 --- a/tests-integration/fixtures/checking/090_instance_improve/Main.snap +++ b/tests-integration/fixtures/checking/090_instance_improve/Main.snap @@ -3,13 +3,13 @@ source: tests-integration/tests/checking/generated.rs expression: report --- Terms -identity :: forall (t7 :: Type) (a :: Type) (b :: Type) (r :: t7). TypeEq a b r => a -> b +identity :: forall (t2 :: Type) (a :: Type) (b :: Type) (r :: t2). TypeEq a b r => a -> b test :: Int Types True :: Type False :: Type -TypeEq :: forall (t7 :: Type). Type -> Type -> t7 -> Constraint +TypeEq :: forall (t2 :: Type). Type -> Type -> t2 -> Constraint Data True diff --git a/tests-integration/fixtures/checking/093_constraint_generalization/Main.snap b/tests-integration/fixtures/checking/093_constraint_generalization/Main.snap index 7b3e4421..3657b111 100644 --- a/tests-integration/fixtures/checking/093_constraint_generalization/Main.snap +++ b/tests-integration/fixtures/checking/093_constraint_generalization/Main.snap @@ -4,9 +4,9 @@ expression: report --- Terms eq :: forall (a :: Type). Eq a => a -> a -> Boolean -test :: forall (t8 :: Type). Eq t8 => t8 -> t8 -> Boolean +test :: forall (t6 :: Type). Eq t6 => t6 -> t6 -> Boolean compare :: forall (a :: Type). Ord a => a -> a -> Int -test2 :: forall (t13 :: Type). Ord t13 => t13 -> t13 -> Int +test2 :: forall (t11 :: Type). Ord t11 => t11 -> t11 -> Int Types Eq :: Type -> Constraint diff --git a/tests-integration/fixtures/checking/097_instance_chains/Main.snap b/tests-integration/fixtures/checking/097_instance_chains/Main.snap index 9fb59742..b17561d0 100644 --- a/tests-integration/fixtures/checking/097_instance_chains/Main.snap +++ b/tests-integration/fixtures/checking/097_instance_chains/Main.snap @@ -3,14 +3,14 @@ source: tests-integration/tests/checking/generated.rs expression: report --- Terms -Proxy :: forall (t1 :: Type) (a :: t1). Proxy @t1 a -testSame :: forall (t14 :: Type) (r :: t14). TypeEq @Type @Type @t14 Int Int r => Proxy @t14 r -testDiff :: forall (t21 :: Type) (r :: t21). TypeEq @Type @Type @t21 Int String r => Proxy @t21 r +Proxy :: forall (t0 :: Type) (a :: t0). Proxy @t0 a +testSame :: forall (t10 :: Type) (r :: t10). TypeEq @Type @Type @t10 Int Int r => Proxy @t10 r +testDiff :: forall (t17 :: Type) (r :: t17). TypeEq @Type @Type @t17 Int String r => Proxy @t17 r test :: { testDiff :: Proxy @Boolean False, testSame :: Proxy @Boolean True } Types -Proxy :: forall (t1 :: Type). t1 -> Type -TypeEq :: forall (t3 :: Type) (t6 :: Type) (t7 :: Type). t3 -> t6 -> t7 -> Constraint +Proxy :: forall (t0 :: Type). t0 -> Type +TypeEq :: forall (t1 :: Type) (t2 :: Type) (t3 :: Type). t1 -> t2 -> t3 -> Constraint Data Proxy diff --git a/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap b/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap index 4fd5094b..a9e9a73b 100644 --- a/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap +++ b/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap @@ -3,18 +3,18 @@ source: tests-integration/tests/checking/generated.rs expression: report --- Terms -Proxy :: forall (t1 :: Type) (a :: t1). Proxy @t1 a +Proxy :: forall (t0 :: Type) (a :: t0). Proxy @t0 a test :: - forall (t24 :: Type) (t25 :: Type) (t28 :: Type) (r1 :: t24) (r2 :: t25) (r :: t28). - IsZero @Type @t24 Z r1 => IsZero @Type @t25 Z r2 => And @t24 @t25 @t28 r1 r2 r => Proxy @t28 r + forall (t18 :: Type) (t19 :: Type) (t22 :: Type) (r1 :: t18) (r2 :: t19) (r :: t22). + IsZero @Type @t18 Z r1 => IsZero @Type @t19 Z r2 => And @t18 @t19 @t22 r1 r2 r => Proxy @t22 r forceSolve :: { test :: Proxy @Boolean True } Types -Proxy :: forall (t1 :: Type). t1 -> Type -IsZero :: forall (t3 :: Type) (t5 :: Type). t3 -> t5 -> Constraint +Proxy :: forall (t0 :: Type). t0 -> Type +IsZero :: forall (t1 :: Type) (t2 :: Type). t1 -> t2 -> Constraint Z :: Type S :: Type -> Type -And :: forall (t7 :: Type) (t10 :: Type) (t11 :: Type). t7 -> t10 -> t11 -> Constraint +And :: forall (t3 :: Type) (t4 :: Type) (t5 :: Type). t3 -> t4 -> t5 -> Constraint Data Proxy diff --git a/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap b/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap index e69a5e9f..91c98fc7 100644 --- a/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap +++ b/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap @@ -10,11 +10,11 @@ pure :: forall (f :: Type -> Type) (a :: Type). Applicative f => a -> f a discard :: forall (m :: Type -> Type) (a :: Type) (b :: Type). Discard m => m a -> (a -> m b) -> m b bind :: forall (m :: Type -> Type) (a :: Type) (b :: Type). Bind m => m a -> (a -> m b) -> m b testDo :: forall (m :: Type -> Type). Monad m => m (Tuple Int String) -testDo' :: forall (t63 :: Type -> Type). Bind t63 => t63 (Tuple Int String) +testDo' :: forall (t55 :: Type -> Type). Bind t55 => t55 (Tuple Int String) testAdo :: forall (f :: Type -> Type). Applicative f => f (Tuple Int String) -testAdo' :: forall (t93 :: Type -> Type). Applicative t93 => t93 (Tuple Int String) +testAdo' :: forall (t85 :: Type -> Type). Applicative t85 => t85 (Tuple Int String) testDoDiscard :: forall (m :: Type -> Type). Monad m => m Int -testDoDiscard' :: forall (t109 :: Type -> Type). Discard t109 => t109 Int +testDoDiscard' :: forall (t101 :: Type -> Type). Discard t101 => t101 Int Types Tuple :: Type -> Type -> Type diff --git a/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap b/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap index dcb2c56d..467358bc 100644 --- a/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap +++ b/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap @@ -18,4 +18,4 @@ instance Show (Int :: Type) Errors CannotUnify { Int, String } at [TermDeclaration(Idx::(1))] CannotUnify { Int -> Int, Int -> String } at [TermDeclaration(Idx::(1))] -InstanceMemberTypeMismatch { expected: Id(32), actual: Id(34) } at [TermDeclaration(Idx::(1))] +InstanceMemberTypeMismatch { expected: Id(31), actual: Id(33) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap b/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap index 963d1011..0d845d64 100644 --- a/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap +++ b/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap @@ -17,4 +17,4 @@ instance Show (Boolean :: Type) Errors CannotUnify { forall (a :: Type). a -> String, Boolean -> String } at [TermDeclaration(Idx::(1))] -InstanceMemberTypeMismatch { expected: Id(32), actual: Id(35) } at [TermDeclaration(Idx::(1))] +InstanceMemberTypeMismatch { expected: Id(31), actual: Id(34) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/126_instance_phantom/Main.snap b/tests-integration/fixtures/checking/126_instance_phantom/Main.snap index fdc9aa6f..3d67ef7a 100644 --- a/tests-integration/fixtures/checking/126_instance_phantom/Main.snap +++ b/tests-integration/fixtures/checking/126_instance_phantom/Main.snap @@ -4,12 +4,12 @@ expression: report --- Terms identity :: forall (a :: Type). Phantom a => a -> a -Proxy :: forall (t3 :: Type) (a :: t3). Proxy @t3 a -forceSolve :: forall (t10 :: Type) (t11 :: t10). { solution :: Proxy @t10 t11 } +Proxy :: forall (t1 :: Type) (a :: t1). Proxy @t1 a +forceSolve :: forall (t8 :: Type) (t9 :: t8). { solution :: Proxy @t8 t9 } Types Phantom :: Type -> Constraint -Proxy :: forall (t3 :: Type). t3 -> Type +Proxy :: forall (t1 :: Type). t1 -> Type Data Proxy diff --git a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap index 74843037..60cb5955 100644 --- a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap +++ b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap @@ -3,10 +3,10 @@ source: tests-integration/tests/checking/generated.rs expression: report --- Terms -Proxy :: forall (t1 :: Type) (a :: t1). Proxy @t1 a +Proxy :: forall (t0 :: Type) (a :: t0). Proxy @t0 a Types -Proxy :: forall (t1 :: Type). t1 -> Type +Proxy :: forall (t0 :: Type). t0 -> Type Data Proxy diff --git a/tests-integration/fixtures/checking/128_type_operator_mutual/Main.purs b/tests-integration/fixtures/checking/128_type_operator_mutual/Main.purs new file mode 100644 index 00000000..a4a19a4e --- /dev/null +++ b/tests-integration/fixtures/checking/128_type_operator_mutual/Main.purs @@ -0,0 +1,9 @@ +module Main where + +-- This tests mutual recursion between a data type and its operator alias. +-- The operator `+` resolves to `Add`, and `Add` uses `+` in its body. +-- They form an Scc::Mutual, so they must share the same pending kind. + +data Add a b = MkAdd (a + b) + +infixl 5 type Add as + diff --git a/tests-integration/fixtures/checking/128_type_operator_mutual/Main.snap b/tests-integration/fixtures/checking/128_type_operator_mutual/Main.snap new file mode 100644 index 00000000..9e0fd0c4 --- /dev/null +++ b/tests-integration/fixtures/checking/128_type_operator_mutual/Main.snap @@ -0,0 +1,15 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +MkAdd :: forall (t1 :: Type) (t3 :: Type) (a :: t1) (b :: t3). a + b -> Add @t1 @t3 a b + +Types +Add :: forall (t1 :: Type) (t3 :: Type). t1 -> t3 -> Type ++ :: forall (t1 :: Type) (t3 :: Type). t1 -> t3 -> Type + +Data +Add + Quantified = :2 + Kind = :0 diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index c59cbbc3..abff5212 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -265,3 +265,5 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_126_instance_phantom_main() { run_test("126_instance_phantom", "Main"); } #[rustfmt::skip] #[test] fn test_127_derive_eq_simple_main() { run_test("127_derive_eq_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_128_type_operator_mutual_main() { run_test("128_type_operator_mutual", "Main"); } diff --git a/tests-integration/tests/snapshots/checking__invalid_type_operator_nullary.snap b/tests-integration/tests/snapshots/checking__invalid_type_operator_nullary.snap index 3b3bb996..b0baa088 100644 --- a/tests-integration/tests/snapshots/checking__invalid_type_operator_nullary.snap +++ b/tests-integration/tests/snapshots/checking__invalid_type_operator_nullary.snap @@ -7,10 +7,6 @@ expression: checked.errors kind: InvalidTypeOperator { id: Id(1), }, - step: [ - TypeDeclaration( - Idx::(1), - ), - ], + step: [], }, ] diff --git a/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap b/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap index f14492e7..da18a1ef 100644 --- a/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap +++ b/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap @@ -5,12 +5,8 @@ expression: checked.errors [ CheckError { kind: InvalidTypeOperator { - id: Id(47), + id: Id(39), }, - step: [ - TypeDeclaration( - Idx::(1), - ), - ], + step: [], }, ] diff --git a/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap b/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap index 72a72b69..049aa4ac 100644 --- a/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap +++ b/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap @@ -5,12 +5,8 @@ expression: checked.errors [ CheckError { kind: InvalidTypeOperator { - id: Id(29), + id: Id(27), }, - step: [ - TypeDeclaration( - Idx::(1), - ), - ], + step: [], }, ] diff --git a/tests-integration/tests/snapshots/checking__partial_synonym.snap b/tests-integration/tests/snapshots/checking__partial_synonym.snap index 01a8e0f5..f7c489b4 100644 --- a/tests-integration/tests/snapshots/checking__partial_synonym.snap +++ b/tests-integration/tests/snapshots/checking__partial_synonym.snap @@ -30,15 +30,4 @@ expression: checked.errors ), ], }, - CheckError { - kind: CannotUnify { - t1: Id(1), - t2: Id(14), - }, - step: [ - TypeDeclaration( - Idx::(1), - ), - ], - }, ] From 32a0e0329838ac003995ce4a179b0c07d838dbfe Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 01:00:55 +0800 Subject: [PATCH 15/62] Rework value group checking to use commit --- compiler-core/checking/src/algorithm.rs | 48 ++++++---- compiler-core/checking/src/algorithm/state.rs | 94 +++++-------------- compiler-core/checking/src/algorithm/term.rs | 20 ++-- .../checking/src/algorithm/term_item.rs | 54 ++++++++++- 4 files changed, 109 insertions(+), 107 deletions(-) diff --git a/compiler-core/checking/src/algorithm.rs b/compiler-core/checking/src/algorithm.rs index 89e16e66..dd91a6ae 100644 --- a/compiler-core/checking/src/algorithm.rs +++ b/compiler-core/checking/src/algorithm.rs @@ -133,7 +133,7 @@ where } } Scc::Recursive(id) => { - state.with_type_group(context, slice::from_ref(id), |state| { + state.with_type_group(context, [*id], |state| { if let Some(item) = type_item::check_type_item(state, context, *id)? { type_item::commit_type_item(state, context, *id, item)?; } @@ -304,42 +304,52 @@ where for scc in &context.lowered.term_scc { match scc { - Scc::Base(item) | Scc::Recursive(item) => { - let Some(value_group) = extract_value_group(*item) else { + Scc::Base(id) | Scc::Recursive(id) => { + let Some(value_group) = extract_value_group(*id) else { continue; }; - if !state.checked.terms.contains_key(item) && value_group.signature.is_none() { - state.term_binding_group(context, [*item]); - } - term_item::check_value_group(state, context, value_group)?; - state.commit_binding_group(context)?; + state.with_term_group(context, [*id], |state| { + if let Some(item) = term_item::check_value_group(state, context, value_group)? { + term_item::commit_value_group(state, context, *id, item)?; + } + Ok(()) + })?; } - Scc::Mutual(items) => { + Scc::Mutual(mutual) => { let value_groups = - items.iter().filter_map(|&id| extract_value_group(id)).collect_vec(); + mutual.iter().filter_map(|&id| extract_value_group(id)).collect_vec(); let with_signature = value_groups .iter() - .filter(|value_group| state.checked.terms.contains_key(&value_group.item_id)) + .filter(|value_group| value_group.signature.is_some()) + .copied() .collect_vec(); let without_signature = value_groups .iter() .filter(|value_group| value_group.signature.is_none()) + .copied() .collect_vec(); let group = without_signature.iter().map(|value_group| value_group.item_id); - state.term_binding_group(context, group); - - for value_group in without_signature { - term_item::check_value_group(state, context, *value_group)?; - } - state.commit_binding_group(context)?; + state.with_term_group(context, group, |state| { + let mut groups = vec![]; + for value_group in &without_signature { + if let Some(group) = + term_item::check_value_group(state, context, *value_group)? + { + groups.push((value_group.item_id, group)); + } + } + for (item_id, group) in groups { + term_item::commit_value_group(state, context, item_id, group)?; + } + Ok(()) + })?; for value_group in with_signature { - term_item::check_value_group(state, context, *value_group)?; + term_item::check_value_group(state, context, value_group)?; } - state.commit_binding_group(context)?; } } } diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index c35a0660..5ca3ff96 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -17,7 +17,7 @@ use resolving::ResolvedModule; use rustc_hash::FxHashMap; use sugar::{Bracketed, Sectioned}; -use crate::algorithm::{constraint, quantify, transfer}; +use crate::algorithm::{constraint, transfer}; use crate::core::{Type, TypeId, TypeInterner, debruijn}; use crate::error::{CheckError, ErrorKind, ErrorStep}; use crate::{CheckedModule, ExternalQueries}; @@ -297,7 +297,6 @@ pub struct BindingGroupType(pub TypeId); pub struct BindingGroupContext { pub terms: FxHashMap, pub types: FxHashMap, - pub residual: FxHashMap>, } impl BindingGroupContext { @@ -623,49 +622,47 @@ impl KnownTypesCore { } impl CheckState { - pub fn term_binding_group( + /// Executes the given closure with a term binding group in scope. + /// + /// This inserts unification variables for the given term items such that + /// recursive or mutually recursive declarations can be checked together. + pub fn with_term_group( &mut self, context: &CheckContext, group: impl IntoIterator, - ) where + f: F, + ) -> T + where Q: ExternalQueries, + F: FnOnce(&mut Self) -> T, { for item in group { - let t = self.fresh_unification_type(context); - self.binding_group.terms.insert(item, t); + if !self.checked.terms.contains_key(&item) { + let t = self.fresh_unification_type(context); + self.binding_group.terms.insert(item, t); + } } - } - pub fn type_binding_group( - &mut self, - context: &CheckContext, - group: impl IntoIterator, - ) where - Q: ExternalQueries, - { - for item in group { - let kind = self.fresh_unification_type(context); - self.binding_group.types.insert(item, BindingGroupType(kind)); - } + let result = f(self); + self.binding_group.terms.clear(); + result } - /// Executes a closure with a type binding group in scope. + /// Executes the given closure with a type binding group in scope. /// - /// This inserts pending kinds (unification variables) for the given type - /// items, such that recursive or mutually recursive declarations can be - /// checked together. The binding group is cleared after the closure returns. + /// This inserts unification variables for the given type items such that + /// recursive or mutually recursive declarations can be checked together. pub fn with_type_group( &mut self, context: &CheckContext, - group: &[TypeItemId], + group: impl AsRef<[TypeItemId]>, f: F, ) -> T where Q: ExternalQueries, F: FnOnce(&mut Self) -> T, { - // Insert pending kinds for items that need them (non-operators without signatures) - let needs_pending = group.iter().filter(|&&item_id| { + let needs_pending = group.as_ref().iter().filter(|&&item_id| { if let Some(TypeItemIr::Operator { .. }) = context.lowered.info.get_type_item(item_id) { return false; } @@ -680,8 +677,7 @@ impl CheckState { self.binding_group.types.insert(item, BindingGroupType(kind)); } - // Insert pending kinds for operators (share kind with their target) - let operators = group.iter().filter_map(|&item_id| { + let operators = group.as_ref().iter().filter_map(|&item_id| { let TypeItemIr::Operator { resolution, .. } = context.lowered.info.get_type_item(item_id)? else { @@ -693,7 +689,7 @@ impl CheckState { for (operator_id, (file_id, item_id)) in operators { debug_assert!( - file_id == context.id && group.contains(&item_id), + file_id == context.id && group.as_ref().contains(&item_id), "invariant violated: expected local target for operator" ); @@ -719,48 +715,6 @@ impl CheckState { constraint::solve_constraints(self, context, wanted, given) } - pub fn commit_binding_group(&mut self, context: &CheckContext) -> QueryResult<()> - where - Q: ExternalQueries, - { - // Process terms to be committed into the CheckedModule. This loops over - // the BindingGroupContext::terms and generalises unsolved unification - // variables. It also takes into account the residual constraints that - // must be generalised or reported as errors. - let mut residuals = mem::take(&mut self.binding_group.residual); - for (item_id, type_id) in mem::take(&mut self.binding_group.terms) { - let constraints = residuals.remove(&item_id).unwrap_or_default(); - if let Some(result) = - quantify::quantify_with_constraints(self, context, type_id, constraints)? - { - self.with_error_step(ErrorStep::TermDeclaration(item_id), |this| { - for constraint in result.ambiguous { - let constraint = transfer::globalize(this, context, constraint); - this.insert_error(ErrorKind::AmbiguousConstraint { constraint }); - } - for constraint in result.unsatisfied { - let constraint = transfer::globalize(this, context, constraint); - this.insert_error(ErrorKind::NoInstanceFound { constraint }); - } - }); - - let type_id = transfer::globalize(self, context, result.quantified); - self.checked.terms.insert(item_id, type_id); - } - } - - // Process types to be committed into the CheckedModule. Generalizes - // pending kinds for recursive type declarations. - for (item_id, BindingGroupType(kind)) in mem::take(&mut self.binding_group.types) { - if let Some((quantified_type, _)) = quantify::quantify(self, kind) { - let type_id = transfer::globalize(self, context, quantified_type); - self.checked.types.insert(item_id, type_id); - } - } - - Ok(()) - } - /// Executes an action with an [`ErrorStep`] in scope. pub fn with_error_step(&mut self, step: ErrorStep, f: F) -> T where diff --git a/compiler-core/checking/src/algorithm/term.rs b/compiler-core/checking/src/algorithm/term.rs index 97a7db19..d082656c 100644 --- a/compiler-core/checking/src/algorithm/term.rs +++ b/compiler-core/checking/src/algorithm/term.rs @@ -12,21 +12,17 @@ use crate::error::{ErrorKind, ErrorStep}; /// Infers the type of top-level value group equations. /// /// This function depends on the unification variable created for the current -/// binding group by the [`CheckState::term_binding_group`] function. It uses +/// binding group by [`CheckState::with_term_group`]. This function returns +/// the inferred type and residual constraints for later generalisation via +/// [`term_item::commit_value_group`]. /// -/// group type that [`infer_equations_core`] will check against. -/// -/// This function solves all constraints generated during inference using the -/// [`CheckState::solve_constraints`] function, and pushes residual constraints -/// onto the [`CheckState::binding_group`] for quantification. -/// -/// See [`CheckState::commit_binding_group`] to see how types are generalised. +/// [`term_item::commit_value_group`]: crate::algorithm::term_item::commit_value_group pub fn infer_equations( state: &mut CheckState, context: &CheckContext, item_id: TermItemId, equations: &[lowering::Equation], -) -> QueryResult<()> +) -> QueryResult<(TypeId, Vec)> where Q: ExternalQueries, { @@ -37,10 +33,8 @@ where infer_equations_core(state, context, group_type, equations)?; - let residual = state.solve_constraints(context)?; - state.binding_group.residual.insert(item_id, residual); - - Ok(()) + let residual_constraints = state.solve_constraints(context)?; + Ok((group_type, residual_constraints)) } /// Infers the type of value group equations. diff --git a/compiler-core/checking/src/algorithm/term_item.rs b/compiler-core/checking/src/algorithm/term_item.rs index 6d5223fd..e6509e64 100644 --- a/compiler-core/checking/src/algorithm/term_item.rs +++ b/compiler-core/checking/src/algorithm/term_item.rs @@ -15,6 +15,12 @@ use crate::algorithm::{ use crate::core::{Instance, InstanceKind, Type, TypeId, Variable, debruijn}; use crate::error::{ErrorKind, ErrorStep}; +#[derive(Clone)] +pub struct InferredValueGroup { + pub inferred_type: TypeId, + pub residual_constraints: Vec, +} + /// Checks signature declarations for terms. /// /// This function checks the term signatures for [`TermItemIr::Foreign`], @@ -324,13 +330,13 @@ pub struct CheckValueGroup<'a> { /// Checks a value declaration group. /// -/// This rule is implemented through the [`inspect::inspect_signature_core`], -/// [`term::check_equations`], and [`term::infer_equations`] functions. +/// This function optionally returns [`InferredValueGroup`] +/// for value declarations that do not have a signature. pub fn check_value_group( state: &mut CheckState, context: &CheckContext, input: CheckValueGroup<'_>, -) -> QueryResult<()> +) -> QueryResult> where Q: ExternalQueries, { @@ -345,13 +351,51 @@ where let signature = inspect::inspect_signature_core(state, context, group_type, surface_bindings)?; - term::check_equations(state, context, *signature_id, signature, equations) + term::check_equations(state, context, *signature_id, signature, equations)?; + Ok(None) } else { - term::infer_equations(state, context, item_id, equations) + let (inferred_type, residual_constraints) = + term::infer_equations(state, context, item_id, equations)?; + Ok(Some(InferredValueGroup { inferred_type, residual_constraints })) } }) } +/// Generalises an [`InferredValueGroup`]. +pub fn commit_value_group( + state: &mut CheckState, + context: &CheckContext, + item_id: TermItemId, + inferred: InferredValueGroup, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let InferredValueGroup { inferred_type, residual_constraints } = inferred; + + let Some(result) = + quantify::quantify_with_constraints(state, context, inferred_type, residual_constraints)? + else { + return Ok(()); + }; + + state.with_error_step(ErrorStep::TermDeclaration(item_id), |state| { + for constraint in result.ambiguous { + let constraint = transfer::globalize(state, context, constraint); + state.insert_error(ErrorKind::AmbiguousConstraint { constraint }); + } + for constraint in result.unsatisfied { + let constraint = transfer::globalize(state, context, constraint); + state.insert_error(ErrorKind::NoInstanceFound { constraint }); + } + }); + + let type_id = transfer::globalize(state, context, result.quantified); + state.checked.terms.insert(item_id, type_id); + + Ok(()) +} + /// Input fields for [`check_instance_members`]. pub struct CheckInstanceMembers<'a> { pub instance_id: TermItemId, From d8137617dbf1155ce942fb291723e6c613e27f67 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 06:31:03 +0800 Subject: [PATCH 16/62] Implement derive for Eq and Ord --- compiler-core/checking/src/algorithm.rs | 28 +- .../checking/src/algorithm/constraint.rs | 33 +- .../checking/src/algorithm/derive.rs | 346 ++++++++++++++++++ .../src/algorithm/derive/higher_kinded.rs | 105 ++++++ .../checking/src/algorithm/safety.rs | 37 ++ compiler-core/checking/src/algorithm/state.rs | 13 +- .../checking/src/algorithm/term_item.rs | 90 ----- compiler-core/checking/src/error.rs | 13 + .../Main.snap | 2 +- .../Main.snap | 2 +- .../checking/127_derive_eq_simple/Main.purs | 6 + .../checking/127_derive_eq_simple/Main.snap | 16 + .../129_derive_eq_with_fields/Data.Eq.purs | 10 + .../129_derive_eq_with_fields/Main.purs | 11 + .../129_derive_eq_with_fields/Main.snap | 25 ++ .../130_derive_eq_parameterized/Data.Eq.purs | 7 + .../130_derive_eq_parameterized/Main.purs | 7 + .../130_derive_eq_parameterized/Main.snap | 19 + .../Data.Eq.purs | 4 + .../131_derive_eq_missing_instance/Main.purs | 7 + .../131_derive_eq_missing_instance/Main.snap | 21 ++ .../Data.Eq.purs | 10 + .../132_derive_eq_1_higher_kinded/Main.purs | 12 + .../132_derive_eq_1_higher_kinded/Main.snap | 28 ++ .../133_derive_eq_partial/Data.Eq.purs | 7 + .../checking/133_derive_eq_partial/Main.purs | 9 + .../checking/133_derive_eq_partial/Main.snap | 23 ++ .../134_derive_ord_simple/Data.Eq.purs | 10 + .../134_derive_ord_simple/Data.Ord.purs | 12 + .../checking/134_derive_ord_simple/Main.purs | 23 ++ .../checking/134_derive_ord_simple/Main.snap | 46 +++ .../Data.Eq.purs | 10 + .../Data.Ord.purs | 12 + .../135_derive_ord_1_higher_kinded/Main.purs | 14 + .../135_derive_ord_1_higher_kinded/Main.snap | 30 ++ .../Data.Eq.purs | 10 + .../Data.Ord.purs | 12 + .../136_derive_nested_higher_kinded/Main.purs | 24 ++ .../136_derive_nested_higher_kinded/Main.snap | 52 +++ tests-integration/tests/checking/generated.rs | 16 + ...ecking__invalid_type_operator_ternary.snap | 2 +- ...checking__invalid_type_operator_unary.snap | 2 +- 42 files changed, 1060 insertions(+), 106 deletions(-) create mode 100644 compiler-core/checking/src/algorithm/derive.rs create mode 100644 compiler-core/checking/src/algorithm/derive/higher_kinded.rs create mode 100644 compiler-core/checking/src/algorithm/safety.rs create mode 100644 tests-integration/fixtures/checking/129_derive_eq_with_fields/Data.Eq.purs create mode 100644 tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.purs create mode 100644 tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.snap create mode 100644 tests-integration/fixtures/checking/130_derive_eq_parameterized/Data.Eq.purs create mode 100644 tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.purs create mode 100644 tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.snap create mode 100644 tests-integration/fixtures/checking/131_derive_eq_missing_instance/Data.Eq.purs create mode 100644 tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.purs create mode 100644 tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap create mode 100644 tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Data.Eq.purs create mode 100644 tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.purs create mode 100644 tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.snap create mode 100644 tests-integration/fixtures/checking/133_derive_eq_partial/Data.Eq.purs create mode 100644 tests-integration/fixtures/checking/133_derive_eq_partial/Main.purs create mode 100644 tests-integration/fixtures/checking/133_derive_eq_partial/Main.snap create mode 100644 tests-integration/fixtures/checking/134_derive_ord_simple/Data.Eq.purs create mode 100644 tests-integration/fixtures/checking/134_derive_ord_simple/Data.Ord.purs create mode 100644 tests-integration/fixtures/checking/134_derive_ord_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap create mode 100644 tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Eq.purs create mode 100644 tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Ord.purs create mode 100644 tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.purs create mode 100644 tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap create mode 100644 tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Eq.purs create mode 100644 tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Ord.purs create mode 100644 tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.purs create mode 100644 tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap diff --git a/compiler-core/checking/src/algorithm.rs b/compiler-core/checking/src/algorithm.rs index dd91a6ae..f800d0b8 100644 --- a/compiler-core/checking/src/algorithm.rs +++ b/compiler-core/checking/src/algorithm.rs @@ -12,6 +12,9 @@ pub mod binder; /// Implements the type class constraint solver. pub mod constraint; +/// Implements type class deriving. +pub mod derive; + /// Implements type folding for traversals that modify. pub mod fold; @@ -27,6 +30,9 @@ pub mod operator; /// Implements generalisation for inferred types. pub mod quantify; +/// Safety mechanisms for the type checker. +pub mod safety; + /// Implements the algorithm's core state structures. pub mod state; @@ -272,15 +278,31 @@ where }); for &item_id in items { - let Some(TermItemIr::Derive { constraints, arguments, resolution, .. }) = + let Some(TermItemIr::Derive { newtype, constraints, arguments, resolution }) = context.lowered.info.get_term_item(item_id) else { continue; }; - let check_derive = term_item::CheckDerive { item_id, constraints, arguments, resolution }; + let Some((class_file, class_id)) = *resolution else { + continue; + }; + + let TermItemKind::Derive { id: derive_id } = context.indexed.items[item_id].kind else { + continue; + }; + + let check_derive = derive::CheckDerive { + item_id, + derive_id, + constraints, + arguments, + class_file, + class_id, + is_newtype: *newtype, + }; - term_item::check_derive(state, context, check_derive)?; + derive::check_derive(state, context, check_derive)?; } Ok(()) diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index 21ee12df..0627b141 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -230,22 +230,37 @@ fn collect_instance_chains( where Q: ExternalQueries, { - let instances = if file_id == context.id { - state + let (instances, derived) = if file_id == context.id { + let instances = state .checked .instances .values() - .filter(|instance| instance.resolution.1 == item_id) + .filter(|instance| instance.resolution == (file_id, item_id)) .cloned() - .collect_vec() + .collect_vec(); + let derived = state + .checked + .derived + .values() + .filter(|instance| instance.resolution == (file_id, item_id)) + .cloned() + .collect_vec(); + (instances, derived) } else { let checked = context.queries.checked(file_id)?; - checked + let instances = checked .instances .values() - .filter(|instance| instance.resolution.1 == item_id) + .filter(|instance| instance.resolution == (file_id, item_id)) .cloned() - .collect_vec() + .collect_vec(); + let derived = checked + .derived + .values() + .filter(|instance| instance.resolution == (file_id, item_id)) + .cloned() + .collect_vec(); + (instances, derived) }; let mut grouped: FxHashMap<_, Vec<_>> = FxHashMap::default(); @@ -263,6 +278,10 @@ where }); } + for instance in derived { + result.push(vec![instance]); + } + Ok(result) } diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs new file mode 100644 index 00000000..c8bf64d2 --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -0,0 +1,346 @@ +//! Implements type class deriving for PureScript. + +mod higher_kinded; + +use building_types::QueryResult; +use files::FileId; +use indexing::{DeriveId, TermItemId, TypeItemId}; +use itertools::Itertools; + +use crate::ExternalQueries; +use crate::algorithm::safety::safe_loop; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::{kind, quantify, substitute, transfer}; +use crate::core::{Instance, InstanceKind, Type, TypeId, Variable, debruijn}; +use crate::error::{ErrorKind, ErrorStep}; + +/// Input fields for [`check_derive`]. +pub struct CheckDerive<'a> { + pub item_id: TermItemId, + pub derive_id: DeriveId, + pub constraints: &'a [lowering::TypeId], + pub arguments: &'a [lowering::TypeId], + pub class_file: FileId, + pub class_id: TypeItemId, + pub is_newtype: bool, +} + +struct ElaboratedDerive { + derive_id: DeriveId, + constraints: Vec<(TypeId, TypeId)>, + arguments: Vec<(TypeId, TypeId)>, + class_file: FileId, + class_id: TypeItemId, +} + +/// Checks a derived instance. +pub fn check_derive( + state: &mut CheckState, + context: &CheckContext, + input: CheckDerive<'_>, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let CheckDerive { + item_id, + derive_id, + constraints, + arguments, + class_file, + class_id, + is_newtype, + } = input; + + if is_newtype { + return Ok(()); + } + + state.with_error_step(ErrorStep::TermDeclaration(item_id), |state| { + // Save the current size of the environment for unbinding. + let size = state.type_scope.size(); + + let mut core_arguments = vec![]; + for argument in arguments.iter() { + let (inferred_type, inferred_kind) = + kind::infer_surface_kind(state, context, *argument)?; + core_arguments.push((inferred_type, inferred_kind)); + } + + let mut core_constraints = vec![]; + for constraint in constraints.iter() { + let (inferred_type, inferred_kind) = + kind::infer_surface_kind(state, context, *constraint)?; + core_constraints.push((inferred_type, inferred_kind)); + } + + let elaborated = ElaboratedDerive { + derive_id, + constraints: core_constraints, + arguments: core_arguments, + class_file, + class_id, + }; + + let class = (class_file, class_id); + + let is_eq = context.known_types.eq == Some(class); + let is_ord = context.known_types.ord == Some(class); + + if is_eq || is_ord { + check_derive_class(state, context, &elaborated)?; + } else { + state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); + }; + + // Unbind type variables bound during elaboration. + state.type_scope.unbind(debruijn::Level(size.0)); + + Ok(()) + }) +} + +fn check_derive_class( + state: &mut CheckState, + context: &CheckContext, + input: &ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 1, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((data_file, data_id)) = extract_type_constructor(state, derived_type) else { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + let mut instance = Instance { + arguments: input.arguments.clone(), + constraints: input.constraints.clone(), + resolution: (input.class_file, input.class_id), + kind: InstanceKind::Derive, + kind_variables: debruijn::Size(0), + }; + + quantify::quantify_instance(state, &mut instance); + + let global_arguments = instance.arguments.iter().map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }); + + let global_arguments = global_arguments.collect_vec(); + + let global_constraints = instance.constraints.iter().map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }); + + let global_constraints = global_constraints.collect_vec(); + + // Register derived instances to allow recursive references. + state.checked.derived.insert( + input.derive_id, + Instance { + arguments: global_arguments, + constraints: global_constraints, + resolution: (input.class_file, input.class_id), + kind: InstanceKind::Derive, + kind_variables: instance.kind_variables, + }, + ); + + for (constraint_type, _) in &input.constraints { + state.constraints.push_given(*constraint_type); + } + + let class = (input.class_file, input.class_id); + generate_field_constraints(state, context, data_file, data_id, derived_type, class)?; + + let residual = state.solve_constraints(context)?; + for constraint in residual { + let constraint = transfer::globalize(state, context, constraint); + state.insert_error(ErrorKind::NoInstanceFound { constraint }); + } + + Ok(()) +} + +fn extract_type_constructor( + state: &mut CheckState, + mut type_id: TypeId, +) -> Option<(FileId, TypeItemId)> { + safe_loop! { + type_id = state.normalize_type(type_id); + match state.storage[type_id] { + Type::Constructor(file, id) => return Some((file, id)), + Type::Application(function, _) => type_id = function, + Type::KindApplication(function, _) => type_id = function, + _ => return None, + } + } +} + +/// Extracts type and kind arguments from an application. +/// +/// This function returns both type and kind arguments as constructors +/// have both. These are used to instantiate the constructor type with +/// arguments from the instance head. +fn extract_type_arguments(state: &mut CheckState, applied_type: TypeId) -> Vec { + let mut arguments = vec![]; + let mut current_id = applied_type; + + safe_loop! { + current_id = state.normalize_type(current_id); + match state.storage[current_id] { + Type::Application(function, argument) => { + arguments.push(argument); + current_id = function; + } + Type::KindApplication(function, argument) => { + arguments.push(argument); + current_id = function; + } + _ => break, + } + } + + arguments.reverse(); + arguments +} + +fn lookup_local_term_type( + state: &mut CheckState, + context: &CheckContext, + file_id: FileId, + term_id: TermItemId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let global_type = if file_id == context.id { + state.checked.terms.get(&term_id).copied() + } else { + let checked = context.queries.checked(file_id)?; + checked.terms.get(&term_id).copied() + }; + Ok(global_type.map(|global_type| transfer::localize(state, context, global_type))) +} + +/// Generates constraints for all fields of across all constructors. +/// +/// For Eq/Ord, this function uses special handling to emit `Eq1` and `Ord1` for +/// higher-kinded type variables of kind `Type -> Type` that appear applied in +/// the constructor fields. +fn generate_field_constraints( + state: &mut CheckState, + context: &CheckContext, + data_file: FileId, + data_id: TypeItemId, + derived_type: TypeId, + class: (FileId, TypeItemId), +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let constructors = if data_file == context.id { + context.indexed.pairs.data_constructors(data_id).collect_vec() + } else { + let indexed = context.queries.indexed(data_file)?; + indexed.pairs.data_constructors(data_id).collect_vec() + }; + + let class1 = if context.known_types.eq == Some(class) { + context.known_types.eq1 + } else if context.known_types.ord == Some(class) { + context.known_types.ord1 + } else { + None + }; + + for constructor_id in constructors { + let constructor_type = lookup_local_term_type(state, context, data_file, constructor_id)?; + let Some(constructor_type) = constructor_type else { continue }; + + let field_types = extract_constructor_fields(state, constructor_type, derived_type); + for field_type in field_types { + higher_kinded::generate_constraint(state, context, field_type, class, class1); + } + } + + Ok(()) +} + +/// Extracts constructor fields from a constructor. +/// +/// This function uses [`extract_type_arguments`] to deconstruct the instance +/// head, then uses [`substitute::SubstituteBound`] to effectively specialise +/// the constructor type for the instance head in particular. Consider the ff: +/// +/// ```purescript +/// data Either a b = Left a | Right b +/// +/// derive instance Eq (Either Int b) +/// -- Left :: Int -> Either Int b +/// -- Right :: b -> Either Int b +/// +/// data Proxy a = Proxy +/// +/// derive instance Eq (Proxy @Type Int) +/// -- Proxy :: Proxy @Type Int +/// ``` +fn extract_constructor_fields( + state: &mut CheckState, + constructor_type: TypeId, + derived_type: TypeId, +) -> Vec { + let type_arguments = extract_type_arguments(state, derived_type); + let mut arguments_iter = type_arguments.into_iter(); + let mut current_id = constructor_type; + + safe_loop! { + current_id = state.normalize_type(current_id); + match &state.storage[current_id] { + Type::Forall(binder, inner) => { + let binder_level = binder.level; + let binder_kind = binder.kind; + let inner = *inner; + + let argument_type = arguments_iter.next().unwrap_or_else(|| { + let skolem = Variable::Skolem(binder_level, binder_kind); + state.storage.intern(Type::Variable(skolem)) + }); + + current_id = substitute::SubstituteBound::on(state, binder_level, argument_type, inner); + } + _ => break, + } + } + + let mut fields = vec![]; + + safe_loop! { + current_id = state.normalize_type(current_id); + match state.storage[current_id] { + Type::Function(argument, result) => { + fields.push(argument); + current_id = result; + } + _ => break, + } + } + + fields +} diff --git a/compiler-core/checking/src/algorithm/derive/higher_kinded.rs b/compiler-core/checking/src/algorithm/derive/higher_kinded.rs new file mode 100644 index 00000000..1595480e --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/higher_kinded.rs @@ -0,0 +1,105 @@ +//! Generic constraint generation for derive instance with higher-kinded support. +//! +//! When deriving classes like `Eq` for types with higher-kinded type +//! parameters, we need to emit the corresponding `*1` constraints like +//! `Eq1` for type variables of kind `Type -> Type`. + +use files::FileId; +use indexing::TypeItemId; + +use crate::ExternalQueries; +use crate::algorithm::fold::Zonk; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::core::{RowType, Type, TypeId, Variable}; + +/// Generates constraints for a field type, handling higher-kinded type variables. +/// +/// Given `f :: Type -> Type` and deriving `Eq` +/// +/// For a field type like `f Int` +/// - Emits `Eq1 f` instead of `Eq (f Int)` +/// +/// For a field type like `f (g Int)` +/// - Emits `Eq1 f` instead of `Eq (f (g Int))` +/// - Emits `Eq1 g` instead of `Eq (g Int)` +/// +/// For nominal types like `Array (f Int)` +/// - Emits `Class (Array (f Int))` +/// +/// For records like `{ a :: f Int }` +/// - Each field is traversed, emits `Eq1 f` +pub fn generate_constraint( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, + class: (FileId, TypeItemId), + class1: Option<(FileId, TypeItemId)>, +) where + Q: ExternalQueries, +{ + let type_id = state.normalize_type(type_id); + match state.storage[type_id].clone() { + Type::Application(function, argument) => { + let function = state.normalize_type(function); + if function == context.prim.record { + generate_constraint(state, context, argument, class, class1); + } else if is_variable_type_type(state, context, function) { + if let Some(class1) = class1 { + emit_constraint(state, class1, function); + } + generate_constraint(state, context, argument, class, class1); + } else { + emit_constraint(state, class, type_id); + } + } + Type::Row(RowType { ref fields, .. }) => { + for field in fields.iter() { + generate_constraint(state, context, field.id, class, class1); + } + } + _ => { + emit_constraint(state, class, type_id); + } + } +} + +fn is_variable_type_type( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, +) -> bool +where + Q: ExternalQueries, +{ + let type_id = state.normalize_type(type_id); + + let Type::Variable(ref variable) = state.storage[type_id] else { + return false; + }; + + let Some(kind) = lookup_variable_kind(state, variable) else { + return false; + }; + + Zonk::on(state, kind) == context.prim.type_to_type +} + +fn lookup_variable_kind(state: &CheckState, variable: &Variable) -> Option { + match variable { + Variable::Skolem(_, kind) => Some(*kind), + Variable::Bound(level) | Variable::Implicit(level) => { + state.type_scope.kinds.get(*level).copied() + } + Variable::Free(_) => None, + } +} + +fn emit_constraint( + state: &mut CheckState, + (class_file, class_id): (FileId, TypeItemId), + type_id: TypeId, +) { + let class_type = state.storage.intern(Type::Constructor(class_file, class_id)); + let constraint = state.storage.intern(Type::Application(class_type, type_id)); + state.constraints.push_wanted(constraint); +} diff --git a/compiler-core/checking/src/algorithm/safety.rs b/compiler-core/checking/src/algorithm/safety.rs new file mode 100644 index 00000000..69447b82 --- /dev/null +++ b/compiler-core/checking/src/algorithm/safety.rs @@ -0,0 +1,37 @@ +//! Safety mechanisms for the type checker. + +/// Fuel constant for bounded loops to prevent infinite looping. +pub const FUEL: u32 = 1_000_000; + +/// Executes a loop body with fuel, breaking when fuel runs out. +/// +/// Use this for loops that traverse type structures which could +/// theoretically be infinite due to bugs or malformed input. +/// +/// # Example +/// +/// ```ignore +/// let mut current_id = type_id; +/// safe_loop! { +/// current_id = state.normalize_type(current_id); +/// match state.storage[current_id] { +/// Type::Application(function, _) => current_id = function, +/// _ => break, +/// } +/// } +/// ``` +#[macro_export] +macro_rules! safe_loop { + ($($body:tt)*) => {{ + let mut fuel = 0u32; + loop { + if fuel >= $crate::algorithm::safety::FUEL { + unreachable!("invariant violated: fuel exhausted"); + } + fuel += 1; + $($body)* + } + }}; +} + +pub use safe_loop; diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index 5ca3ff96..b8221d11 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -418,6 +418,7 @@ impl<'r, 's> PrimLookup<'r, 's> { pub struct PrimCore { pub t: TypeId, + pub type_to_type: TypeId, pub function: TypeId, pub array: TypeId, pub record: TypeId, @@ -438,8 +439,12 @@ impl PrimCore { let resolved = queries.resolved(queries.prim_id())?; let mut lookup = PrimLookup::new(&resolved, &mut state.storage, "Prim"); + let t = lookup.type_constructor("Type"); + let type_to_type = lookup.intern(Type::Function(t, t)); + Ok(PrimCore { - t: lookup.type_constructor("Type"), + t, + type_to_type, function: lookup.type_constructor("Function"), array: lookup.type_constructor("Array"), record: lookup.type_constructor("Record"), @@ -611,13 +616,17 @@ fn fetch_known_type( pub struct KnownTypesCore { pub eq: Option<(FileId, TypeItemId)>, pub eq1: Option<(FileId, TypeItemId)>, + pub ord: Option<(FileId, TypeItemId)>, + pub ord1: Option<(FileId, TypeItemId)>, } impl KnownTypesCore { fn collect(queries: &impl ExternalQueries) -> QueryResult { let eq = fetch_known_type(queries, "Data.Eq", "Eq")?; let eq1 = fetch_known_type(queries, "Data.Eq", "Eq1")?; - Ok(KnownTypesCore { eq, eq1 }) + let ord = fetch_known_type(queries, "Data.Ord", "Ord")?; + let ord1 = fetch_known_type(queries, "Data.Ord", "Ord1")?; + Ok(KnownTypesCore { eq, eq1, ord, ord1 }) } } diff --git a/compiler-core/checking/src/algorithm/term_item.rs b/compiler-core/checking/src/algorithm/term_item.rs index e6509e64..3b74be92 100644 --- a/compiler-core/checking/src/algorithm/term_item.rs +++ b/compiler-core/checking/src/algorithm/term_item.rs @@ -186,96 +186,6 @@ where }) } -pub struct CheckDerive<'a> { - pub item_id: TermItemId, - pub constraints: &'a [lowering::TypeId], - pub arguments: &'a [lowering::TypeId], - pub resolution: &'a Option<(FileId, TypeItemId)>, -} - -pub fn check_derive( - state: &mut CheckState, - context: &CheckContext, - input: CheckDerive<'_>, -) -> QueryResult<()> -where - Q: ExternalQueries, -{ - let CheckDerive { item_id, constraints, arguments, resolution } = input; - state.with_error_step(ErrorStep::TermDeclaration(item_id), |state| { - let Some((class_file, class_item)) = *resolution else { - return Ok(()); - }; - - let TermItemKind::Derive { id: derive_id } = context.indexed.items[item_id].kind else { - return Ok(()); - }; - - // Save the current size of the environment for unbinding. - let size = state.type_scope.size(); - - let class_kind = kind::lookup_file_type(state, context, class_file, class_item)?; - let expected_kinds = instantiate_class_kind(state, context, class_kind)?; - - if expected_kinds.len() != arguments.len() { - state.insert_error(ErrorKind::InstanceHeadMismatch { - class_file, - class_item, - expected: expected_kinds.len(), - actual: arguments.len(), - }); - } - - let mut core_arguments = vec![]; - for (argument, expected_kind) in arguments.iter().zip(expected_kinds) { - let (inferred_type, inferred_kind) = - kind::check_surface_kind(state, context, *argument, expected_kind)?; - core_arguments.push((inferred_type, inferred_kind)); - } - - let mut core_constraints = vec![]; - for constraint in constraints.iter() { - let (inferred_type, inferred_kind) = - kind::infer_surface_kind(state, context, *constraint)?; - core_constraints.push((inferred_type, inferred_kind)); - } - - let mut instance = Instance { - arguments: core_arguments, - constraints: core_constraints, - resolution: (class_file, class_item), - kind: InstanceKind::Derive, - kind_variables: debruijn::Size(0), - }; - - quantify::quantify_instance(state, &mut instance); - - let arguments = instance.arguments.iter().map(|&(t, k)| { - let t = transfer::globalize(state, context, t); - let k = transfer::globalize(state, context, k); - (t, k) - }); - - instance.arguments = arguments.collect(); - - let constraints = instance.constraints.iter().map(|&(t, k)| { - let t = transfer::globalize(state, context, t); - let k = transfer::globalize(state, context, k); - (t, k) - }); - - instance.constraints = constraints.collect(); - - state.checked.derived.insert(derive_id, instance); - - // Capture implicit variables from the instance head before unbinding. - let implicits = state.type_scope.unbind_implicits(debruijn::Level(size.0)); - state.surface_bindings.insert_instance_head(item_id, Arc::from(implicits)); - - Ok(()) - }) -} - /// Instantiates a class kind to extract the expected kinds for instance arguments. /// /// Class kinds have the form `forall k1 k2. T1 -> T2 -> ... -> Constraint` where: diff --git a/compiler-core/checking/src/error.rs b/compiler-core/checking/src/error.rs index a00ff197..2bb54ae7 100644 --- a/compiler-core/checking/src/error.rs +++ b/compiler-core/checking/src/error.rs @@ -25,10 +25,23 @@ pub enum ErrorKind { AmbiguousConstraint { constraint: TypeId, }, + CannotDeriveClass { + class_file: files::FileId, + class_id: indexing::TypeItemId, + }, + CannotDeriveForType { + type_id: TypeId, + }, CannotUnify { t1: TypeId, t2: TypeId, }, + DeriveInvalidArity { + class_file: files::FileId, + class_id: indexing::TypeItemId, + expected: usize, + actual: usize, + }, EmptyAdoBlock, EmptyDoBlock, InstanceHeadMismatch { diff --git a/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap b/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap index 467358bc..dcb2c56d 100644 --- a/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap +++ b/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap @@ -18,4 +18,4 @@ instance Show (Int :: Type) Errors CannotUnify { Int, String } at [TermDeclaration(Idx::(1))] CannotUnify { Int -> Int, Int -> String } at [TermDeclaration(Idx::(1))] -InstanceMemberTypeMismatch { expected: Id(31), actual: Id(33) } at [TermDeclaration(Idx::(1))] +InstanceMemberTypeMismatch { expected: Id(32), actual: Id(34) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap b/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap index 0d845d64..963d1011 100644 --- a/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap +++ b/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap @@ -17,4 +17,4 @@ instance Show (Boolean :: Type) Errors CannotUnify { forall (a :: Type). a -> String, Boolean -> String } at [TermDeclaration(Idx::(1))] -InstanceMemberTypeMismatch { expected: Id(31), actual: Id(34) } at [TermDeclaration(Idx::(1))] +InstanceMemberTypeMismatch { expected: Id(32), actual: Id(35) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.purs b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.purs index f9dfdf82..a2d03a12 100644 --- a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.purs +++ b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.purs @@ -5,3 +5,9 @@ import Data.Eq (class Eq) data Proxy a = Proxy derive instance Eq (Proxy a) + +data NoEq = MkNoEq + +data ContainsNoEq = MkContainsNoEq NoEq + +derive instance Eq ContainsNoEq diff --git a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap index 60cb5955..94954e3c 100644 --- a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap +++ b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap @@ -4,15 +4,31 @@ expression: report --- Terms Proxy :: forall (t0 :: Type) (a :: t0). Proxy @t0 a +MkNoEq :: NoEq +MkContainsNoEq :: NoEq -> ContainsNoEq Types Proxy :: forall (t0 :: Type). t0 -> Type +NoEq :: Type +ContainsNoEq :: Type Data Proxy Quantified = :1 Kind = :0 +NoEq + Quantified = :0 + Kind = :0 + +ContainsNoEq + Quantified = :0 + Kind = :0 + Derived derive Eq (Proxy @&0 &1 :: Type) +derive Eq (ContainsNoEq :: Type) + +Errors +NoInstanceFound { Eq NoEq } at [TermDeclaration(Idx::(4))] diff --git a/tests-integration/fixtures/checking/129_derive_eq_with_fields/Data.Eq.purs b/tests-integration/fixtures/checking/129_derive_eq_with_fields/Data.Eq.purs new file mode 100644 index 00000000..eca9493e --- /dev/null +++ b/tests-integration/fixtures/checking/129_derive_eq_with_fields/Data.Eq.purs @@ -0,0 +1,10 @@ +module Data.Eq where + +class Eq a where + eq :: a -> a -> Boolean + +instance Eq Int where + eq _ _ = true + +instance Eq Boolean where + eq _ _ = true diff --git a/tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.purs b/tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.purs new file mode 100644 index 00000000..50f7caaf --- /dev/null +++ b/tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.purs @@ -0,0 +1,11 @@ +module Main where + +import Data.Eq (class Eq) + +data Box = MkBox Int + +derive instance Eq Box + +data Pair = MkPair Int Boolean + +derive instance Eq Pair diff --git a/tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.snap b/tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.snap new file mode 100644 index 00000000..b1f41303 --- /dev/null +++ b/tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.snap @@ -0,0 +1,25 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +MkBox :: Int -> Box +MkPair :: Int -> Boolean -> Pair + +Types +Box :: Type +Pair :: Type + +Data +Box + Quantified = :0 + Kind = :0 + +Pair + Quantified = :0 + Kind = :0 + + +Derived +derive Eq (Box :: Type) +derive Eq (Pair :: Type) diff --git a/tests-integration/fixtures/checking/130_derive_eq_parameterized/Data.Eq.purs b/tests-integration/fixtures/checking/130_derive_eq_parameterized/Data.Eq.purs new file mode 100644 index 00000000..b30c2e37 --- /dev/null +++ b/tests-integration/fixtures/checking/130_derive_eq_parameterized/Data.Eq.purs @@ -0,0 +1,7 @@ +module Data.Eq where + +class Eq a where + eq :: a -> a -> Boolean + +instance Eq Int where + eq _ _ = true diff --git a/tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.purs b/tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.purs new file mode 100644 index 00000000..02d3d137 --- /dev/null +++ b/tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Eq (class Eq) + +data Maybe a = Nothing | Just a + +derive instance Eq a => Eq (Maybe a) diff --git a/tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.snap b/tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.snap new file mode 100644 index 00000000..0c0d4ef1 --- /dev/null +++ b/tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.snap @@ -0,0 +1,19 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe a +Just :: forall (a :: Type). a -> Maybe a + +Types +Maybe :: Type -> Type + +Data +Maybe + Quantified = :0 + Kind = :0 + + +Derived +derive Eq &0 => Eq (Maybe &0 :: Type) diff --git a/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Data.Eq.purs b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Data.Eq.purs new file mode 100644 index 00000000..c59d0788 --- /dev/null +++ b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Data.Eq.purs @@ -0,0 +1,4 @@ +module Data.Eq where + +class Eq a where + eq :: a -> a -> Boolean diff --git a/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.purs b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.purs new file mode 100644 index 00000000..4af36bd3 --- /dev/null +++ b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Eq (class Eq) + +data Box = MkBox Int + +derive instance Eq Box diff --git a/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap new file mode 100644 index 00000000..42f0ab59 --- /dev/null +++ b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +MkBox :: Int -> Box + +Types +Box :: Type + +Data +Box + Quantified = :0 + Kind = :0 + + +Derived +derive Eq (Box :: Type) + +Errors +NoInstanceFound { Eq Int } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Data.Eq.purs b/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Data.Eq.purs new file mode 100644 index 00000000..601c73ef --- /dev/null +++ b/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Data.Eq.purs @@ -0,0 +1,10 @@ +module Data.Eq where + +class Eq a where + eq :: a -> a -> Boolean + +class Eq1 f where + eq1 :: forall a. Eq a => f a -> f a -> Boolean + +instance Eq Int where + eq _ _ = true diff --git a/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.purs b/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.purs new file mode 100644 index 00000000..04e710c8 --- /dev/null +++ b/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.purs @@ -0,0 +1,12 @@ +module Main where + +import Data.Eq (class Eq, class Eq1) + +data Wrap f a = MkWrap (f a) + +derive instance (Eq1 f, Eq a) => Eq (Wrap f a) + +-- Should fail: missing Eq1 f constraint +data WrapNoEq1 f a = MkWrapNoEq1 (f a) + +derive instance Eq a => Eq (WrapNoEq1 f a) diff --git a/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.snap b/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.snap new file mode 100644 index 00000000..9a45920c --- /dev/null +++ b/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.snap @@ -0,0 +1,28 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +MkWrap :: forall (t4 :: Type) (f :: t4 -> Type) (a :: t4). f a -> Wrap @t4 f a +MkWrapNoEq1 :: forall (t10 :: Type) (f :: t10 -> Type) (a :: t10). f a -> WrapNoEq1 @t10 f a + +Types +Wrap :: forall (t4 :: Type). (t4 -> Type) -> t4 -> Type +WrapNoEq1 :: forall (t10 :: Type). (t10 -> Type) -> t10 -> Type + +Data +Wrap + Quantified = :1 + Kind = :0 + +WrapNoEq1 + Quantified = :1 + Kind = :0 + + +Derived +derive (Eq1 &0, Eq &1) => Eq (Wrap @Type &0 &1 :: Type) +derive Eq &1 => Eq (WrapNoEq1 @Type &0 &1 :: Type) + +Errors +NoInstanceFound { Eq1 &0 } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/133_derive_eq_partial/Data.Eq.purs b/tests-integration/fixtures/checking/133_derive_eq_partial/Data.Eq.purs new file mode 100644 index 00000000..b30c2e37 --- /dev/null +++ b/tests-integration/fixtures/checking/133_derive_eq_partial/Data.Eq.purs @@ -0,0 +1,7 @@ +module Data.Eq where + +class Eq a where + eq :: a -> a -> Boolean + +instance Eq Int where + eq _ _ = true diff --git a/tests-integration/fixtures/checking/133_derive_eq_partial/Main.purs b/tests-integration/fixtures/checking/133_derive_eq_partial/Main.purs new file mode 100644 index 00000000..e08f6f51 --- /dev/null +++ b/tests-integration/fixtures/checking/133_derive_eq_partial/Main.purs @@ -0,0 +1,9 @@ +module Main where + +import Data.Eq (class Eq) + +data Either a b = Left a | Right b + +derive instance Eq b => Eq (Either Int b) + +derive instance Eq (Either Int b) diff --git a/tests-integration/fixtures/checking/133_derive_eq_partial/Main.snap b/tests-integration/fixtures/checking/133_derive_eq_partial/Main.snap new file mode 100644 index 00000000..aba4244e --- /dev/null +++ b/tests-integration/fixtures/checking/133_derive_eq_partial/Main.snap @@ -0,0 +1,23 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Left :: forall (a :: Type) (b :: Type). a -> Either a b +Right :: forall (a :: Type) (b :: Type). b -> Either a b + +Types +Either :: Type -> Type -> Type + +Data +Either + Quantified = :0 + Kind = :0 + + +Derived +derive Eq &0 => Eq (Either Int &0 :: Type) +derive Eq (Either Int &0 :: Type) + +Errors +NoInstanceFound { Eq &0 } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/134_derive_ord_simple/Data.Eq.purs b/tests-integration/fixtures/checking/134_derive_ord_simple/Data.Eq.purs new file mode 100644 index 00000000..eca9493e --- /dev/null +++ b/tests-integration/fixtures/checking/134_derive_ord_simple/Data.Eq.purs @@ -0,0 +1,10 @@ +module Data.Eq where + +class Eq a where + eq :: a -> a -> Boolean + +instance Eq Int where + eq _ _ = true + +instance Eq Boolean where + eq _ _ = true diff --git a/tests-integration/fixtures/checking/134_derive_ord_simple/Data.Ord.purs b/tests-integration/fixtures/checking/134_derive_ord_simple/Data.Ord.purs new file mode 100644 index 00000000..7566f8e4 --- /dev/null +++ b/tests-integration/fixtures/checking/134_derive_ord_simple/Data.Ord.purs @@ -0,0 +1,12 @@ +module Data.Ord where + +import Data.Eq (class Eq) + +class Eq a <= Ord a where + compare :: a -> a -> Int + +instance Ord Int where + compare _ _ = 0 + +instance Ord Boolean where + compare _ _ = 0 diff --git a/tests-integration/fixtures/checking/134_derive_ord_simple/Main.purs b/tests-integration/fixtures/checking/134_derive_ord_simple/Main.purs new file mode 100644 index 00000000..210b310f --- /dev/null +++ b/tests-integration/fixtures/checking/134_derive_ord_simple/Main.purs @@ -0,0 +1,23 @@ +module Main where + +import Data.Eq (class Eq) +import Data.Ord (class Ord) + +data Box = MkBox Int + +derive instance Eq Box +derive instance Ord Box + +data Pair = MkPair Int Boolean + +derive instance Eq Pair +derive instance Ord Pair + +data NoOrd = MkNoOrd + +derive instance Eq NoOrd + +data ContainsNoOrd = MkContainsNoOrd NoOrd + +derive instance Eq ContainsNoOrd +derive instance Ord ContainsNoOrd diff --git a/tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap b/tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap new file mode 100644 index 00000000..439d94fd --- /dev/null +++ b/tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap @@ -0,0 +1,46 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +MkBox :: Int -> Box +MkPair :: Int -> Boolean -> Pair +MkNoOrd :: NoOrd +MkContainsNoOrd :: NoOrd -> ContainsNoOrd + +Types +Box :: Type +Pair :: Type +NoOrd :: Type +ContainsNoOrd :: Type + +Data +Box + Quantified = :0 + Kind = :0 + +Pair + Quantified = :0 + Kind = :0 + +NoOrd + Quantified = :0 + Kind = :0 + +ContainsNoOrd + Quantified = :0 + Kind = :0 + + +Derived +derive Eq (Box :: Type) +derive Ord (Box :: Type) +derive Eq (Pair :: Type) +derive Ord (Pair :: Type) +derive Eq (NoOrd :: Type) +derive Eq (ContainsNoOrd :: Type) +derive Ord (ContainsNoOrd :: Type) + +Errors +NoInstanceFound { Eq NoOrd } at [TermDeclaration(Idx::(9))] +NoInstanceFound { Ord NoOrd } at [TermDeclaration(Idx::(10))] diff --git a/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Eq.purs b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Eq.purs new file mode 100644 index 00000000..601c73ef --- /dev/null +++ b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Eq.purs @@ -0,0 +1,10 @@ +module Data.Eq where + +class Eq a where + eq :: a -> a -> Boolean + +class Eq1 f where + eq1 :: forall a. Eq a => f a -> f a -> Boolean + +instance Eq Int where + eq _ _ = true diff --git a/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Ord.purs b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Ord.purs new file mode 100644 index 00000000..5d17e073 --- /dev/null +++ b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Ord.purs @@ -0,0 +1,12 @@ +module Data.Ord where + +import Data.Eq (class Eq, class Eq1) + +class Eq a <= Ord a where + compare :: a -> a -> Int + +class Eq1 f <= Ord1 f where + compare1 :: forall a. Ord a => f a -> f a -> Int + +instance Ord Int where + compare _ _ = 0 diff --git a/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.purs b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.purs new file mode 100644 index 00000000..85ac1a72 --- /dev/null +++ b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.purs @@ -0,0 +1,14 @@ +module Main where + +import Data.Eq (class Eq, class Eq1) +import Data.Ord (class Ord, class Ord1) + +data Wrap f a = MkWrap (f a) + +derive instance (Eq1 f, Eq a) => Eq (Wrap f a) +derive instance (Ord1 f, Ord a) => Ord (Wrap f a) + +data WrapNoOrd1 f a = MkWrapNoOrd1 (f a) + +derive instance (Eq1 f, Eq a) => Eq (WrapNoOrd1 f a) +derive instance Ord a => Ord (WrapNoOrd1 f a) diff --git a/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap new file mode 100644 index 00000000..fe4107be --- /dev/null +++ b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap @@ -0,0 +1,30 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +MkWrap :: forall (t4 :: Type) (f :: t4 -> Type) (a :: t4). f a -> Wrap @t4 f a +MkWrapNoOrd1 :: forall (t10 :: Type) (f :: t10 -> Type) (a :: t10). f a -> WrapNoOrd1 @t10 f a + +Types +Wrap :: forall (t4 :: Type). (t4 -> Type) -> t4 -> Type +WrapNoOrd1 :: forall (t10 :: Type). (t10 -> Type) -> t10 -> Type + +Data +Wrap + Quantified = :1 + Kind = :0 + +WrapNoOrd1 + Quantified = :1 + Kind = :0 + + +Derived +derive (Eq1 &0, Eq &1) => Eq (Wrap @Type &0 &1 :: Type) +derive (Ord1 &0, Ord &1) => Ord (Wrap @Type &0 &1 :: Type) +derive (Eq1 &0, Eq &1) => Eq (WrapNoOrd1 @Type &0 &1 :: Type) +derive Ord &1 => Ord (WrapNoOrd1 @Type &0 &1 :: Type) + +Errors +NoInstanceFound { Ord1 &0 } at [TermDeclaration(Idx::(5))] diff --git a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Eq.purs b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Eq.purs new file mode 100644 index 00000000..601c73ef --- /dev/null +++ b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Eq.purs @@ -0,0 +1,10 @@ +module Data.Eq where + +class Eq a where + eq :: a -> a -> Boolean + +class Eq1 f where + eq1 :: forall a. Eq a => f a -> f a -> Boolean + +instance Eq Int where + eq _ _ = true diff --git a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Ord.purs b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Ord.purs new file mode 100644 index 00000000..5d17e073 --- /dev/null +++ b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Ord.purs @@ -0,0 +1,12 @@ +module Data.Ord where + +import Data.Eq (class Eq, class Eq1) + +class Eq a <= Ord a where + compare :: a -> a -> Int + +class Eq1 f <= Ord1 f where + compare1 :: forall a. Ord a => f a -> f a -> Int + +instance Ord Int where + compare _ _ = 0 diff --git a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.purs b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.purs new file mode 100644 index 00000000..db480759 --- /dev/null +++ b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.purs @@ -0,0 +1,24 @@ +module Main where + +import Data.Eq (class Eq, class Eq1) +import Data.Ord (class Ord, class Ord1) + +data T f g = T (f (g Int)) + +derive instance (Eq1 f) => Eq (T f g) +derive instance (Ord1 f) => Ord (T f g) + +data Maybe a = Nothing | Just a + +derive instance Eq a => Eq (Maybe a) +derive instance Ord a => Ord (Maybe a) + +data U f = U (f (Maybe Int)) + +derive instance (Eq1 f) => Eq (U f) +derive instance (Ord1 f) => Ord (U f) + +data V f g = V (f (g 42)) + +derive instance (Eq1 f) => Eq (V f g) +derive instance (Ord1 f) => Ord (V f g) diff --git a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap new file mode 100644 index 00000000..9ac9f6d1 --- /dev/null +++ b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap @@ -0,0 +1,52 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +T :: forall (t4 :: Type) (f :: t4 -> Type) (g :: Type -> t4). f (g Int) -> T f g +Nothing :: forall (a :: Type). Maybe a +Just :: forall (a :: Type). a -> Maybe a +U :: forall (f :: Type -> Type). f (Maybe Int) -> U f +V :: forall (t20 :: Type) (f :: t20 -> Type) (g :: Int -> t20). f (g 42) -> V f g + +Types +T :: forall (t4 :: Type). (t4 -> Type) -> (Type -> t4) -> Type +Maybe :: Type -> Type +U :: (Type -> Type) -> Type +V :: forall (t20 :: Type). (t20 -> Type) -> (Int -> t20) -> Type + +Data +T + Quantified = :1 + Kind = :0 + +Maybe + Quantified = :0 + Kind = :0 + +U + Quantified = :0 + Kind = :0 + +V + Quantified = :1 + Kind = :0 + + +Derived +derive Eq1 &0 => Eq (V @Type &0 &1 :: Type) +derive Ord1 &0 => Ord (V @Type &0 &1 :: Type) +derive Eq1 &0 => Eq (T @Type &0 &1 :: Type) +derive Ord1 &0 => Ord (T @Type &0 &1 :: Type) +derive Eq &0 => Eq (Maybe &0 :: Type) +derive Ord &0 => Ord (Maybe &0 :: Type) +derive Eq1 &0 => Eq (U &0 :: Type) +derive Ord1 &0 => Ord (U &0 :: Type) + +Errors +NoInstanceFound { Eq1 &1 } at [TermDeclaration(Idx::(1))] +NoInstanceFound { Ord1 &1 } at [TermDeclaration(Idx::(2))] +NoInstanceFound { Eq (Maybe Int) } at [TermDeclaration(Idx::(8))] +NoInstanceFound { Ord (Maybe Int) } at [TermDeclaration(Idx::(9))] +NoInstanceFound { Eq (&1 42) } at [TermDeclaration(Idx::(11))] +NoInstanceFound { Ord (&1 42) } at [TermDeclaration(Idx::(12))] diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index abff5212..030d445f 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -267,3 +267,19 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_127_derive_eq_simple_main() { run_test("127_derive_eq_simple", "Main"); } #[rustfmt::skip] #[test] fn test_128_type_operator_mutual_main() { run_test("128_type_operator_mutual", "Main"); } + +#[rustfmt::skip] #[test] fn test_129_derive_eq_with_fields_main() { run_test("129_derive_eq_with_fields", "Main"); } + +#[rustfmt::skip] #[test] fn test_130_derive_eq_parameterized_main() { run_test("130_derive_eq_parameterized", "Main"); } + +#[rustfmt::skip] #[test] fn test_131_derive_eq_missing_instance_main() { run_test("131_derive_eq_missing_instance", "Main"); } + +#[rustfmt::skip] #[test] fn test_132_derive_eq_1_higher_kinded_main() { run_test("132_derive_eq_1_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_133_derive_eq_partial_main() { run_test("133_derive_eq_partial", "Main"); } + +#[rustfmt::skip] #[test] fn test_134_derive_ord_simple_main() { run_test("134_derive_ord_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_135_derive_ord_1_higher_kinded_main() { run_test("135_derive_ord_1_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_136_derive_nested_higher_kinded_main() { run_test("136_derive_nested_higher_kinded", "Main"); } diff --git a/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap b/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap index da18a1ef..4541c5fa 100644 --- a/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap +++ b/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap @@ -5,7 +5,7 @@ expression: checked.errors [ CheckError { kind: InvalidTypeOperator { - id: Id(39), + id: Id(40), }, step: [], }, diff --git a/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap b/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap index 049aa4ac..174777a7 100644 --- a/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap +++ b/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap @@ -5,7 +5,7 @@ expression: checked.errors [ CheckError { kind: InvalidTypeOperator { - id: Id(27), + id: Id(28), }, step: [], }, From 3643aac2d07cc33f79ee040a6a936a876daaa601 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 16:32:18 +0800 Subject: [PATCH 17/62] Initial implementation for 'derive newtype' --- .../checking/src/algorithm/derive.rs | 175 ++++++++++++++++-- compiler-core/checking/src/error.rs | 3 + .../137_derive_newtype_simple/Data.Show.purs | 7 + .../137_derive_newtype_simple/Main.purs | 7 + .../137_derive_newtype_simple/Main.snap | 18 ++ .../Data.Show.purs | 7 + .../Main.purs | 7 + .../Main.snap | 18 ++ .../Data.Show.purs | 7 + .../139_derive_newtype_with_given/Main.purs | 7 + .../139_derive_newtype_with_given/Main.snap | 18 ++ .../Data.Show.purs | 7 + .../140_derive_newtype_recursive/Main.purs | 7 + .../140_derive_newtype_recursive/Main.snap | 18 ++ .../141_derive_newtype_phantom/Data.Show.purs | 10 + .../141_derive_newtype_phantom/Main.purs | 7 + .../141_derive_newtype_phantom/Main.snap | 18 ++ .../Data.Show.purs | 7 + .../142_derive_newtype_not_newtype/Main.purs | 7 + .../142_derive_newtype_not_newtype/Main.snap | 18 ++ .../Data.Show.purs | 7 + .../Main.purs | 7 + .../Main.snap | 21 +++ .../Data.Show.purs | 7 + .../Main.purs | 7 + .../Main.snap | 21 +++ .../Data.Show.purs | 7 + .../145_derive_newtype_multi_param/Main.purs | 7 + .../145_derive_newtype_multi_param/Main.snap | 18 ++ tests-integration/tests/checking/generated.rs | 18 ++ 30 files changed, 481 insertions(+), 12 deletions(-) create mode 100644 tests-integration/fixtures/checking/137_derive_newtype_simple/Data.Show.purs create mode 100644 tests-integration/fixtures/checking/137_derive_newtype_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/137_derive_newtype_simple/Main.snap create mode 100644 tests-integration/fixtures/checking/138_derive_newtype_parameterized/Data.Show.purs create mode 100644 tests-integration/fixtures/checking/138_derive_newtype_parameterized/Main.purs create mode 100644 tests-integration/fixtures/checking/138_derive_newtype_parameterized/Main.snap create mode 100644 tests-integration/fixtures/checking/139_derive_newtype_with_given/Data.Show.purs create mode 100644 tests-integration/fixtures/checking/139_derive_newtype_with_given/Main.purs create mode 100644 tests-integration/fixtures/checking/139_derive_newtype_with_given/Main.snap create mode 100644 tests-integration/fixtures/checking/140_derive_newtype_recursive/Data.Show.purs create mode 100644 tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.purs create mode 100644 tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.snap create mode 100644 tests-integration/fixtures/checking/141_derive_newtype_phantom/Data.Show.purs create mode 100644 tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.purs create mode 100644 tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.snap create mode 100644 tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Data.Show.purs create mode 100644 tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.purs create mode 100644 tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.snap create mode 100644 tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Data.Show.purs create mode 100644 tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.purs create mode 100644 tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.snap create mode 100644 tests-integration/fixtures/checking/144_derive_newtype_missing_given/Data.Show.purs create mode 100644 tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.purs create mode 100644 tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.snap create mode 100644 tests-integration/fixtures/checking/145_derive_newtype_multi_param/Data.Show.purs create mode 100644 tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.purs create mode 100644 tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.snap diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index c8bf64d2..42498df8 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -52,10 +52,6 @@ where is_newtype, } = input; - if is_newtype { - return Ok(()); - } - state.with_error_step(ErrorStep::TermDeclaration(item_id), |state| { // Save the current size of the environment for unbinding. let size = state.type_scope.size(); @@ -82,16 +78,20 @@ where class_id, }; - let class = (class_file, class_id); + if is_newtype { + check_newtype_derive(state, context, &elaborated)?; + } else { + let class = (class_file, class_id); - let is_eq = context.known_types.eq == Some(class); - let is_ord = context.known_types.ord == Some(class); + let is_eq = context.known_types.eq == Some(class); + let is_ord = context.known_types.ord == Some(class); - if is_eq || is_ord { - check_derive_class(state, context, &elaborated)?; - } else { - state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); - }; + if is_eq || is_ord { + check_derive_class(state, context, &elaborated)?; + } else { + state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); + }; + } // Unbind type variables bound during elaboration. state.type_scope.unbind(debruijn::Level(size.0)); @@ -178,6 +178,105 @@ where Ok(()) } +fn check_newtype_derive( + state: &mut CheckState, + context: &CheckContext, + input: &ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [ref preceding_arguments @ .., (newtype_type, _)] = input.arguments[..] else { + return Ok(()); + }; + + let insert_error = + |state: &mut CheckState, context: &CheckContext, kind: fn(TypeId) -> ErrorKind| { + let global = transfer::globalize(state, context, newtype_type); + state.insert_error(kind(global)); + }; + + let Some((newtype_file, newtype_id)) = extract_type_constructor(state, newtype_type) else { + insert_error(state, context, |type_id| ErrorKind::CannotDeriveForType { type_id }); + return Ok(()); + }; + + if newtype_file != context.id { + insert_error(state, context, |type_id| ErrorKind::CannotDeriveForType { type_id }); + return Ok(()); + } + + if !is_newtype(context, newtype_file, newtype_id)? { + insert_error(state, context, |type_id| ErrorKind::ExpectedNewtype { type_id }); + return Ok(()); + } + + let inner_type = get_newtype_inner(state, context, newtype_file, newtype_id, newtype_type)?; + + let mut instance = Instance { + arguments: input.arguments.clone(), + constraints: input.constraints.clone(), + resolution: (input.class_file, input.class_id), + kind: InstanceKind::Derive, + kind_variables: debruijn::Size(0), + }; + + quantify::quantify_instance(state, &mut instance); + + let global_arguments = instance.arguments.iter().map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }); + + let global_arguments = global_arguments.collect_vec(); + + let global_constraints = instance.constraints.iter().map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }); + + let global_constraints = global_constraints.collect_vec(); + + state.checked.derived.insert( + input.derive_id, + Instance { + arguments: global_arguments, + constraints: global_constraints, + resolution: (input.class_file, input.class_id), + kind: InstanceKind::Derive, + kind_variables: instance.kind_variables, + }, + ); + + for (constraint_type, _) in &input.constraints { + state.constraints.push_given(*constraint_type); + } + + // Build `Class t1 t2 Inner` given the constraint `Class t1 t2 Newtype` + let delegate_constraint = { + let class_type = state.storage.intern(Type::Constructor(input.class_file, input.class_id)); + + let preceding_arguments = + preceding_arguments.iter().fold(class_type, |function, (argument, _)| { + state.storage.intern(Type::Application(function, *argument)) + }); + + state.storage.intern(Type::Application(preceding_arguments, inner_type)) + }; + + state.constraints.push_wanted(delegate_constraint); + + let residual = state.solve_constraints(context)?; + for constraint in residual { + let constraint = transfer::globalize(state, context, constraint); + state.insert_error(ErrorKind::NoInstanceFound { constraint }); + } + + Ok(()) +} + fn extract_type_constructor( state: &mut CheckState, mut type_id: TypeId, @@ -193,6 +292,24 @@ fn extract_type_constructor( } } +/// Checks if a type item is a newtype by examining its indexed kind. +fn is_newtype( + context: &CheckContext, + file_id: FileId, + type_id: TypeItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let is_newtype = if file_id == context.id { + matches!(context.indexed.items[type_id].kind, indexing::TypeItemKind::Newtype { .. }) + } else { + let indexed = context.queries.indexed(file_id)?; + matches!(indexed.items[type_id].kind, indexing::TypeItemKind::Newtype { .. }) + }; + Ok(is_newtype) +} + /// Extracts type and kind arguments from an application. /// /// This function returns both type and kind arguments as constructors @@ -239,6 +356,40 @@ where Ok(global_type.map(|global_type| transfer::localize(state, context, global_type))) } +/// Gets the inner type for a newtype, specialized with type arguments. +/// +/// Newtypes have exactly one constructor with exactly one field. +/// This function extracts that field type, substituting any type parameters. +fn get_newtype_inner( + state: &mut CheckState, + context: &CheckContext, + newtype_file: FileId, + newtype_id: TypeItemId, + newtype_type: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let constructors = if newtype_file == context.id { + context.indexed.pairs.data_constructors(newtype_id).collect_vec() + } else { + let indexed = context.queries.indexed(newtype_file)?; + indexed.pairs.data_constructors(newtype_id).collect_vec() + }; + + let [constructor_id] = constructors[..] else { + return Ok(context.prim.unknown); + }; + + let constructor_type = lookup_local_term_type(state, context, newtype_file, constructor_id)?; + let Some(constructor_type) = constructor_type else { + return Ok(context.prim.unknown); + }; + + let fields = extract_constructor_fields(state, constructor_type, newtype_type); + Ok(fields.into_iter().next().unwrap_or(context.prim.unknown)) +} + /// Generates constraints for all fields of across all constructors. /// /// For Eq/Ord, this function uses special handling to emit `Eq1` and `Ord1` for diff --git a/compiler-core/checking/src/error.rs b/compiler-core/checking/src/error.rs index 2bb54ae7..ba06845b 100644 --- a/compiler-core/checking/src/error.rs +++ b/compiler-core/checking/src/error.rs @@ -57,6 +57,9 @@ pub enum ErrorKind { InvalidTypeOperator { id: TypeId, }, + ExpectedNewtype { + type_id: TypeId, + }, NoInstanceFound { constraint: TypeId, }, diff --git a/tests-integration/fixtures/checking/137_derive_newtype_simple/Data.Show.purs b/tests-integration/fixtures/checking/137_derive_newtype_simple/Data.Show.purs new file mode 100644 index 00000000..ace9fabe --- /dev/null +++ b/tests-integration/fixtures/checking/137_derive_newtype_simple/Data.Show.purs @@ -0,0 +1,7 @@ +module Data.Show where + +class Show a where + show :: a -> String + +instance Show Int where + show _ = "" diff --git a/tests-integration/fixtures/checking/137_derive_newtype_simple/Main.purs b/tests-integration/fixtures/checking/137_derive_newtype_simple/Main.purs new file mode 100644 index 00000000..87027037 --- /dev/null +++ b/tests-integration/fixtures/checking/137_derive_newtype_simple/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Show (class Show) + +newtype Wrapper = Wrapper Int + +derive newtype instance Show Wrapper diff --git a/tests-integration/fixtures/checking/137_derive_newtype_simple/Main.snap b/tests-integration/fixtures/checking/137_derive_newtype_simple/Main.snap new file mode 100644 index 00000000..e7946aa6 --- /dev/null +++ b/tests-integration/fixtures/checking/137_derive_newtype_simple/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Wrapper :: Int -> Wrapper + +Types +Wrapper :: Type + +Data +Wrapper + Quantified = :0 + Kind = :0 + + +Derived +derive Show (Wrapper :: Type) diff --git a/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Data.Show.purs b/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Data.Show.purs new file mode 100644 index 00000000..ace9fabe --- /dev/null +++ b/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Data.Show.purs @@ -0,0 +1,7 @@ +module Data.Show where + +class Show a where + show :: a -> String + +instance Show Int where + show _ = "" diff --git a/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Main.purs b/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Main.purs new file mode 100644 index 00000000..439202ce --- /dev/null +++ b/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Show (class Show) + +newtype Identity a = Identity a + +derive newtype instance Show (Identity Int) diff --git a/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Main.snap b/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Main.snap new file mode 100644 index 00000000..51cf2d11 --- /dev/null +++ b/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Identity :: forall (a :: Type). a -> Identity a + +Types +Identity :: Type -> Type + +Data +Identity + Quantified = :0 + Kind = :0 + + +Derived +derive Show (Identity Int :: Type) diff --git a/tests-integration/fixtures/checking/139_derive_newtype_with_given/Data.Show.purs b/tests-integration/fixtures/checking/139_derive_newtype_with_given/Data.Show.purs new file mode 100644 index 00000000..ace9fabe --- /dev/null +++ b/tests-integration/fixtures/checking/139_derive_newtype_with_given/Data.Show.purs @@ -0,0 +1,7 @@ +module Data.Show where + +class Show a where + show :: a -> String + +instance Show Int where + show _ = "" diff --git a/tests-integration/fixtures/checking/139_derive_newtype_with_given/Main.purs b/tests-integration/fixtures/checking/139_derive_newtype_with_given/Main.purs new file mode 100644 index 00000000..45a2f67b --- /dev/null +++ b/tests-integration/fixtures/checking/139_derive_newtype_with_given/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Show (class Show) + +newtype Identity a = Identity a + +derive newtype instance Show a => Show (Identity a) diff --git a/tests-integration/fixtures/checking/139_derive_newtype_with_given/Main.snap b/tests-integration/fixtures/checking/139_derive_newtype_with_given/Main.snap new file mode 100644 index 00000000..9d9f2328 --- /dev/null +++ b/tests-integration/fixtures/checking/139_derive_newtype_with_given/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Identity :: forall (a :: Type). a -> Identity a + +Types +Identity :: Type -> Type + +Data +Identity + Quantified = :0 + Kind = :0 + + +Derived +derive Show &0 => Show (Identity &0 :: Type) diff --git a/tests-integration/fixtures/checking/140_derive_newtype_recursive/Data.Show.purs b/tests-integration/fixtures/checking/140_derive_newtype_recursive/Data.Show.purs new file mode 100644 index 00000000..ace9fabe --- /dev/null +++ b/tests-integration/fixtures/checking/140_derive_newtype_recursive/Data.Show.purs @@ -0,0 +1,7 @@ +module Data.Show where + +class Show a where + show :: a -> String + +instance Show Int where + show _ = "" diff --git a/tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.purs b/tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.purs new file mode 100644 index 00000000..8a4abc99 --- /dev/null +++ b/tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Show (class Show) + +newtype Mu f = Mu (f (Mu f)) + +derive newtype instance Show (f (Mu f)) => Show (Mu f) diff --git a/tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.snap b/tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.snap new file mode 100644 index 00000000..6db61fac --- /dev/null +++ b/tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Mu :: forall (f :: Type -> Type). f (Mu f) -> Mu f + +Types +Mu :: (Type -> Type) -> Type + +Data +Mu + Quantified = :0 + Kind = :0 + + +Derived +derive Show (&0 (Mu &0)) => Show (Mu &0 :: Type) diff --git a/tests-integration/fixtures/checking/141_derive_newtype_phantom/Data.Show.purs b/tests-integration/fixtures/checking/141_derive_newtype_phantom/Data.Show.purs new file mode 100644 index 00000000..5c5866e8 --- /dev/null +++ b/tests-integration/fixtures/checking/141_derive_newtype_phantom/Data.Show.purs @@ -0,0 +1,10 @@ +module Data.Show where + +class Show a where + show :: a -> String + +instance Show Int where + show _ = "" + +instance Show a => Show (Array a) where + show _ = "" diff --git a/tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.purs b/tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.purs new file mode 100644 index 00000000..95cfe594 --- /dev/null +++ b/tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Show (class Show) + +newtype Vector (n :: Int) a = Vector (Array a) + +derive newtype instance Show a => Show (Vector n a) diff --git a/tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.snap b/tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.snap new file mode 100644 index 00000000..5cd575ae --- /dev/null +++ b/tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Vector :: forall (n :: Int) (a :: Type). Array a -> Vector n a + +Types +Vector :: Int -> Type -> Type + +Data +Vector + Quantified = :0 + Kind = :0 + + +Derived +derive Show &1 => Show (Vector &0 &1 :: Type) diff --git a/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Data.Show.purs b/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Data.Show.purs new file mode 100644 index 00000000..ace9fabe --- /dev/null +++ b/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Data.Show.purs @@ -0,0 +1,7 @@ +module Data.Show where + +class Show a where + show :: a -> String + +instance Show Int where + show _ = "" diff --git a/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.purs b/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.purs new file mode 100644 index 00000000..8092e314 --- /dev/null +++ b/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Show (class Show) + +data Foo = Foo Int + +derive newtype instance Show Foo diff --git a/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.snap b/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.snap new file mode 100644 index 00000000..5df74bcd --- /dev/null +++ b/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Foo :: Int -> Foo + +Types +Foo :: Type + +Data +Foo + Quantified = :0 + Kind = :0 + + +Errors +ExpectedNewtype { type_id: Id(9) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Data.Show.purs b/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Data.Show.purs new file mode 100644 index 00000000..ace9fabe --- /dev/null +++ b/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Data.Show.purs @@ -0,0 +1,7 @@ +module Data.Show where + +class Show a where + show :: a -> String + +instance Show Int where + show _ = "" diff --git a/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.purs b/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.purs new file mode 100644 index 00000000..a28cb1bd --- /dev/null +++ b/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Show (class Show) + +newtype Identity a = Identity a + +derive newtype instance Show (Identity String) diff --git a/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.snap b/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.snap new file mode 100644 index 00000000..7ebfb058 --- /dev/null +++ b/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Identity :: forall (a :: Type). a -> Identity a + +Types +Identity :: Type -> Type + +Data +Identity + Quantified = :0 + Kind = :0 + + +Derived +derive Show (Identity String :: Type) + +Errors +NoInstanceFound { Show String } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Data.Show.purs b/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Data.Show.purs new file mode 100644 index 00000000..ace9fabe --- /dev/null +++ b/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Data.Show.purs @@ -0,0 +1,7 @@ +module Data.Show where + +class Show a where + show :: a -> String + +instance Show Int where + show _ = "" diff --git a/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.purs b/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.purs new file mode 100644 index 00000000..f99f1ba3 --- /dev/null +++ b/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Show (class Show) + +newtype Identity a = Identity a + +derive newtype instance Show (Identity a) diff --git a/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.snap b/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.snap new file mode 100644 index 00000000..f39d47f2 --- /dev/null +++ b/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Identity :: forall (a :: Type). a -> Identity a + +Types +Identity :: Type -> Type + +Data +Identity + Quantified = :0 + Kind = :0 + + +Derived +derive Show (Identity &0 :: Type) + +Errors +NoInstanceFound { Show &0 } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Data.Show.purs b/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Data.Show.purs new file mode 100644 index 00000000..ace9fabe --- /dev/null +++ b/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Data.Show.purs @@ -0,0 +1,7 @@ +module Data.Show where + +class Show a where + show :: a -> String + +instance Show Int where + show _ = "" diff --git a/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.purs b/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.purs new file mode 100644 index 00000000..14b8f605 --- /dev/null +++ b/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Show (class Show) + +newtype Pair a b = Pair a + +derive newtype instance Show (Pair Int String) diff --git a/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.snap b/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.snap new file mode 100644 index 00000000..fddd6c2f --- /dev/null +++ b/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Pair :: forall (t1 :: Type) (a :: Type) (b :: t1). a -> Pair @t1 a b + +Types +Pair :: forall (t1 :: Type). Type -> t1 -> Type + +Data +Pair + Quantified = :1 + Kind = :0 + + +Derived +derive Show (Pair @Type Int String :: Type) diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 030d445f..dba5e7ef 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -283,3 +283,21 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_135_derive_ord_1_higher_kinded_main() { run_test("135_derive_ord_1_higher_kinded", "Main"); } #[rustfmt::skip] #[test] fn test_136_derive_nested_higher_kinded_main() { run_test("136_derive_nested_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_137_derive_newtype_simple_main() { run_test("137_derive_newtype_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_138_derive_newtype_parameterized_main() { run_test("138_derive_newtype_parameterized", "Main"); } + +#[rustfmt::skip] #[test] fn test_139_derive_newtype_with_given_main() { run_test("139_derive_newtype_with_given", "Main"); } + +#[rustfmt::skip] #[test] fn test_140_derive_newtype_recursive_main() { run_test("140_derive_newtype_recursive", "Main"); } + +#[rustfmt::skip] #[test] fn test_141_derive_newtype_phantom_main() { run_test("141_derive_newtype_phantom", "Main"); } + +#[rustfmt::skip] #[test] fn test_142_derive_newtype_not_newtype_main() { run_test("142_derive_newtype_not_newtype", "Main"); } + +#[rustfmt::skip] #[test] fn test_143_derive_newtype_missing_instance_main() { run_test("143_derive_newtype_missing_instance", "Main"); } + +#[rustfmt::skip] #[test] fn test_144_derive_newtype_missing_given_main() { run_test("144_derive_newtype_missing_given", "Main"); } + +#[rustfmt::skip] #[test] fn test_145_derive_newtype_multi_param_main() { run_test("145_derive_newtype_multi_param", "Main"); } From 37fe6d880bf501993add80b4a095f55a429a192f Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 17:19:12 +0800 Subject: [PATCH 18/62] Initial derive for Functor and Bifunctor --- .../checking/src/algorithm/derive.rs | 13 +- .../checking/src/algorithm/derive/functor.rs | 408 ++++++++++++++++++ compiler-core/checking/src/algorithm/state.rs | 6 +- compiler-core/checking/src/error.rs | 4 + .../Data.Functor.purs | 4 + .../146_derive_functor_simple/Main.purs | 12 + .../146_derive_functor_simple/Main.snap | 33 ++ .../Data.Functor.purs | 4 + .../Main.purs | 9 + .../Main.snap | 28 ++ .../Data.Functor.purs | 4 + .../Main.purs | 13 + .../Main.snap | 35 ++ .../Data.Bifunctor.purs | 4 + .../149_derive_bifunctor_simple/Main.purs | 12 + .../149_derive_bifunctor_simple/Main.snap | 34 ++ .../Data.Bifunctor.purs | 4 + .../Data.Functor.purs | 4 + .../Main.purs | 10 + .../Main.snap | 34 ++ .../Data.Bifunctor.purs | 4 + .../Main.purs | 6 + .../Main.snap | 24 ++ tests-integration/tests/checking/generated.rs | 12 + 24 files changed, 717 insertions(+), 4 deletions(-) create mode 100644 compiler-core/checking/src/algorithm/derive/functor.rs create mode 100644 tests-integration/fixtures/checking/146_derive_functor_simple/Data.Functor.purs create mode 100644 tests-integration/fixtures/checking/146_derive_functor_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap create mode 100644 tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Data.Functor.purs create mode 100644 tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.purs create mode 100644 tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap create mode 100644 tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Data.Functor.purs create mode 100644 tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.purs create mode 100644 tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap create mode 100644 tests-integration/fixtures/checking/149_derive_bifunctor_simple/Data.Bifunctor.purs create mode 100644 tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap create mode 100644 tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Bifunctor.purs create mode 100644 tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Functor.purs create mode 100644 tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.purs create mode 100644 tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap create mode 100644 tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Data.Bifunctor.purs create mode 100644 tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.purs create mode 100644 tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index 42498df8..26fcb266 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -1,5 +1,6 @@ //! Implements type class deriving for PureScript. +mod functor; mod higher_kinded; use building_types::QueryResult; @@ -85,9 +86,15 @@ where let is_eq = context.known_types.eq == Some(class); let is_ord = context.known_types.ord == Some(class); + let is_functor = context.known_types.functor == Some(class); + let is_bifunctor = context.known_types.bifunctor == Some(class); if is_eq || is_ord { check_derive_class(state, context, &elaborated)?; + } else if is_functor { + functor::check_derive_functor(state, context, &elaborated)?; + } else if is_bifunctor { + functor::check_derive_bifunctor(state, context, &elaborated)?; } else { state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); }; @@ -277,7 +284,7 @@ where Ok(()) } -fn extract_type_constructor( +pub(crate) fn extract_type_constructor( state: &mut CheckState, mut type_id: TypeId, ) -> Option<(FileId, TypeItemId)> { @@ -315,7 +322,7 @@ where /// This function returns both type and kind arguments as constructors /// have both. These are used to instantiate the constructor type with /// arguments from the instance head. -fn extract_type_arguments(state: &mut CheckState, applied_type: TypeId) -> Vec { +pub(crate) fn extract_type_arguments(state: &mut CheckState, applied_type: TypeId) -> Vec { let mut arguments = vec![]; let mut current_id = applied_type; @@ -338,7 +345,7 @@ fn extract_type_arguments(state: &mut CheckState, applied_type: TypeId) -> Vec( +pub(crate) fn lookup_local_term_type( state: &mut CheckState, context: &CheckContext, file_id: FileId, diff --git a/compiler-core/checking/src/algorithm/derive/functor.rs b/compiler-core/checking/src/algorithm/derive/functor.rs new file mode 100644 index 00000000..9d6eef6f --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/functor.rs @@ -0,0 +1,408 @@ +//! Implements derive for Functor and Bifunctor type classes. +//! +//! Unlike Eq/Ord which require constraints on all fields, Functor/Bifunctor +//! derivation is variance-aware: the derived parameter must only appear in +//! covariant positions (right of function arrows, type application arguments). + +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; +use itertools::Itertools; + +use crate::ExternalQueries; +use crate::algorithm::derive::{ElaboratedDerive, extract_type_arguments}; +use crate::algorithm::safety::safe_loop; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::{quantify, substitute, transfer}; +use crate::core::{Instance, InstanceKind, RowType, Type, TypeId, Variable, debruijn}; +use crate::error::ErrorKind; + +/// Variance of a type position. +#[derive(Clone, Copy, PartialEq, Eq)] +enum Variance { + Covariant, + Contravariant, +} + +impl Variance { + fn flip(self) -> Variance { + match self { + Variance::Covariant => Variance::Contravariant, + Variance::Contravariant => Variance::Covariant, + } + } +} + +/// Tracks the Skolem variables representing derived type parameters. +struct DerivedSkolems { + /// Skolem levels for the parameters being mapped over. + /// - `Functor`: one level + /// - `Bifunctor`: two levels + levels: Vec, +} + +impl DerivedSkolems { + fn contains(&self, level: debruijn::Level) -> bool { + self.levels.contains(&level) + } +} + +/// Checks a derive instance for Functor. +pub fn check_derive_functor( + state: &mut CheckState, + context: &CheckContext, + input: &ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 1, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + let mut instance = Instance { + arguments: input.arguments.clone(), + constraints: input.constraints.clone(), + resolution: (input.class_file, input.class_id), + kind: InstanceKind::Derive, + kind_variables: debruijn::Size(0), + }; + + quantify::quantify_instance(state, &mut instance); + + let global_arguments = instance + .arguments + .iter() + .map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }) + .collect_vec(); + + let global_constraints = instance + .constraints + .iter() + .map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }) + .collect_vec(); + + state.checked.derived.insert( + input.derive_id, + Instance { + arguments: global_arguments, + constraints: global_constraints, + resolution: (input.class_file, input.class_id), + kind: InstanceKind::Derive, + kind_variables: instance.kind_variables, + }, + ); + + for (constraint_type, _) in &input.constraints { + state.constraints.push_given(*constraint_type); + } + + let functor = Some((input.class_file, input.class_id)); + generate_functor_constraints(state, context, data_file, data_id, derived_type, functor, 1)?; + + let residual = state.solve_constraints(context)?; + for constraint in residual { + let constraint = transfer::globalize(state, context, constraint); + state.insert_error(ErrorKind::NoInstanceFound { constraint }); + } + + Ok(()) +} + +/// Checks a derive instance for Bifunctor. +pub fn check_derive_bifunctor( + state: &mut CheckState, + context: &CheckContext, + input: &ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 1, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + let mut instance = Instance { + arguments: input.arguments.clone(), + constraints: input.constraints.clone(), + resolution: (input.class_file, input.class_id), + kind: InstanceKind::Derive, + kind_variables: debruijn::Size(0), + }; + + quantify::quantify_instance(state, &mut instance); + + let global_arguments = instance + .arguments + .iter() + .map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }) + .collect_vec(); + + let global_constraints = instance + .constraints + .iter() + .map(|&(t, k)| { + let t = transfer::globalize(state, context, t); + let k = transfer::globalize(state, context, k); + (t, k) + }) + .collect_vec(); + + state.checked.derived.insert( + input.derive_id, + Instance { + arguments: global_arguments, + constraints: global_constraints, + resolution: (input.class_file, input.class_id), + kind: InstanceKind::Derive, + kind_variables: instance.kind_variables, + }, + ); + + for (constraint_type, _) in &input.constraints { + state.constraints.push_given(*constraint_type); + } + + // Bifunctor derivation emits Functor constraints for wrapped parameters. + let functor = context.known_types.functor; + generate_functor_constraints(state, context, data_file, data_id, derived_type, functor, 2)?; + + let residual = state.solve_constraints(context)?; + for constraint in residual { + let constraint = transfer::globalize(state, context, constraint); + state.insert_error(ErrorKind::NoInstanceFound { constraint }); + } + + Ok(()) +} + +/// Generates variance-aware constraints for Functor/Bifunctor derivation. +fn generate_functor_constraints( + state: &mut CheckState, + context: &CheckContext, + data_file: FileId, + data_id: TypeItemId, + derived_type: TypeId, + functor: Option<(FileId, TypeItemId)>, + parameter_count: usize, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let constructors = if data_file == context.id { + context.indexed.pairs.data_constructors(data_id).collect_vec() + } else { + let indexed = context.queries.indexed(data_file)?; + indexed.pairs.data_constructors(data_id).collect_vec() + }; + + for constructor_id in constructors { + let constructor_type = + super::lookup_local_term_type(state, context, data_file, constructor_id)?; + let Some(constructor_type) = constructor_type else { continue }; + + let (fields, skolems) = + extract_fields_with_skolems(state, constructor_type, derived_type, parameter_count); + + for field_type in fields { + check_functor_field(state, context, field_type, Variance::Covariant, &skolems, functor); + } + } + + Ok(()) +} + +/// Extracts constructor fields and tracks Skolem variables for unmapped parameters. +/// +/// Similar to `extract_constructor_fields` but also returns the Skolem variables +/// created for type parameters that weren't provided in the instance head. +fn extract_fields_with_skolems( + state: &mut CheckState, + constructor_type: TypeId, + derived_type: TypeId, + parameter_count: usize, +) -> (Vec, DerivedSkolems) { + let type_arguments = extract_type_arguments(state, derived_type); + let mut arguments_iter = type_arguments.into_iter(); + let mut current_id = constructor_type; + let mut levels = vec![]; + + safe_loop! { + current_id = state.normalize_type(current_id); + match &state.storage[current_id] { + Type::Forall(binder, inner) => { + let binder_level = binder.level; + let binder_kind = binder.kind; + let inner = *inner; + + let argument_type = arguments_iter.next().unwrap_or_else(|| { + levels.push(binder_level); + let skolem = Variable::Skolem(binder_level, binder_kind); + state.storage.intern(Type::Variable(skolem)) + }); + + current_id = substitute::SubstituteBound::on(state, binder_level, argument_type, inner); + } + _ => break, + } + } + + // The last parameter_count variables are used for Functor/Bifunctor. + // When arguments_iter runs out, it creates skolem variables instead. + let levels = levels.into_iter().rev().take(parameter_count).collect_vec(); + + let mut fields = vec![]; + safe_loop! { + current_id = state.normalize_type(current_id); + match state.storage[current_id] { + Type::Function(argument, result) => { + fields.push(argument); + current_id = result; + } + _ => break, + } + } + + (fields, DerivedSkolems { levels }) +} + +/// Checks a field type for variance violations and emits Functor constraints. +fn check_functor_field( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, + variance: Variance, + skolems: &DerivedSkolems, + functor: Option<(FileId, TypeItemId)>, +) where + Q: ExternalQueries, +{ + let type_id = state.normalize_type(type_id); + + match state.storage[type_id].clone() { + Type::Variable(Variable::Skolem(level, _)) if skolems.contains(level) => { + if variance == Variance::Contravariant { + let global = transfer::globalize(state, context, type_id); + state.insert_error(ErrorKind::ContravariantOccurrence { type_id: global }); + } + } + + Type::Function(argument, result) => { + check_functor_field(state, context, argument, variance.flip(), skolems, functor); + check_functor_field(state, context, result, variance, skolems, functor); + } + + Type::Application(function, argument) => { + let function = state.normalize_type(function); + + if function == context.prim.record { + check_functor_field(state, context, argument, variance, skolems, functor); + } else if contains_derived_skolem(state, argument, skolems) { + if variance == Variance::Contravariant { + let global = transfer::globalize(state, context, type_id); + state.insert_error(ErrorKind::ContravariantOccurrence { type_id: global }); + } else if let Some(functor) = functor { + emit_constraint(state, functor, function); + } else { + state.insert_error(ErrorKind::DeriveMissingFunctor); + } + check_functor_field(state, context, argument, variance, skolems, functor); + } else { + check_functor_field(state, context, argument, variance, skolems, functor); + } + } + + Type::Row(RowType { ref fields, .. }) => { + for field in fields.iter() { + check_functor_field(state, context, field.id, variance, skolems, functor); + } + } + + Type::KindApplication(_, argument) => { + check_functor_field(state, context, argument, variance, skolems, functor); + } + + _ => (), + } +} + +/// Checks if a type contains any of the derived parameter Skolem variables. +fn contains_derived_skolem( + state: &mut CheckState, + type_id: TypeId, + skolems: &DerivedSkolems, +) -> bool { + let type_id = state.normalize_type(type_id); + + match state.storage[type_id].clone() { + Type::Variable(Variable::Skolem(level, _)) => skolems.contains(level), + + Type::Application(function, argument) | Type::KindApplication(function, argument) => { + contains_derived_skolem(state, function, skolems) + || contains_derived_skolem(state, argument, skolems) + } + + Type::Function(argument, result) => { + contains_derived_skolem(state, argument, skolems) + || contains_derived_skolem(state, result, skolems) + } + + Type::Row(RowType { ref fields, tail }) => { + fields.iter().any(|f| contains_derived_skolem(state, f.id, skolems)) + || tail.map_or(false, |t| contains_derived_skolem(state, t, skolems)) + } + + Type::Forall(_, inner) | Type::Constrained(_, inner) | Type::Kinded(inner, _) => { + contains_derived_skolem(state, inner, skolems) + } + + _ => false, + } +} + +/// Emits a constraint `Class type_id`. +fn emit_constraint( + state: &mut CheckState, + (class_file, class_id): (FileId, TypeItemId), + type_id: TypeId, +) { + let class_type = state.storage.intern(Type::Constructor(class_file, class_id)); + let constraint = state.storage.intern(Type::Application(class_type, type_id)); + state.constraints.push_wanted(constraint); +} diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index b8221d11..c3074f97 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -618,6 +618,8 @@ pub struct KnownTypesCore { pub eq1: Option<(FileId, TypeItemId)>, pub ord: Option<(FileId, TypeItemId)>, pub ord1: Option<(FileId, TypeItemId)>, + pub functor: Option<(FileId, TypeItemId)>, + pub bifunctor: Option<(FileId, TypeItemId)>, } impl KnownTypesCore { @@ -626,7 +628,9 @@ impl KnownTypesCore { let eq1 = fetch_known_type(queries, "Data.Eq", "Eq1")?; let ord = fetch_known_type(queries, "Data.Ord", "Ord")?; let ord1 = fetch_known_type(queries, "Data.Ord", "Ord1")?; - Ok(KnownTypesCore { eq, eq1, ord, ord1 }) + let functor = fetch_known_type(queries, "Data.Functor", "Functor")?; + let bifunctor = fetch_known_type(queries, "Data.Bifunctor", "Bifunctor")?; + Ok(KnownTypesCore { eq, eq1, ord, ord1, functor, bifunctor }) } } diff --git a/compiler-core/checking/src/error.rs b/compiler-core/checking/src/error.rs index ba06845b..42f3fe7f 100644 --- a/compiler-core/checking/src/error.rs +++ b/compiler-core/checking/src/error.rs @@ -32,6 +32,9 @@ pub enum ErrorKind { CannotDeriveForType { type_id: TypeId, }, + ContravariantOccurrence { + type_id: TypeId, + }, CannotUnify { t1: TypeId, t2: TypeId, @@ -42,6 +45,7 @@ pub enum ErrorKind { expected: usize, actual: usize, }, + DeriveMissingFunctor, EmptyAdoBlock, EmptyDoBlock, InstanceHeadMismatch { diff --git a/tests-integration/fixtures/checking/146_derive_functor_simple/Data.Functor.purs b/tests-integration/fixtures/checking/146_derive_functor_simple/Data.Functor.purs new file mode 100644 index 00000000..354b844e --- /dev/null +++ b/tests-integration/fixtures/checking/146_derive_functor_simple/Data.Functor.purs @@ -0,0 +1,4 @@ +module Data.Functor where + +class Functor f where + map :: forall a b. (a -> b) -> f a -> f b diff --git a/tests-integration/fixtures/checking/146_derive_functor_simple/Main.purs b/tests-integration/fixtures/checking/146_derive_functor_simple/Main.purs new file mode 100644 index 00000000..391e32cb --- /dev/null +++ b/tests-integration/fixtures/checking/146_derive_functor_simple/Main.purs @@ -0,0 +1,12 @@ +module Main where + +import Data.Functor (class Functor) + +data Identity a = Identity a +derive instance Functor Identity + +data Const e a = Const e +derive instance Functor (Const e) + +data Maybe a = Nothing | Just a +derive instance Functor Maybe diff --git a/tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap b/tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap new file mode 100644 index 00000000..6abf739c --- /dev/null +++ b/tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap @@ -0,0 +1,33 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Identity :: forall (a :: Type). a -> Identity a +Const :: forall (t2 :: Type) (e :: Type) (a :: t2). e -> Const @t2 e a +Nothing :: forall (a :: Type). Maybe a +Just :: forall (a :: Type). a -> Maybe a + +Types +Identity :: Type -> Type +Const :: forall (t2 :: Type). Type -> t2 -> Type +Maybe :: Type -> Type + +Data +Identity + Quantified = :0 + Kind = :0 + +Const + Quantified = :1 + Kind = :0 + +Maybe + Quantified = :0 + Kind = :0 + + +Derived +derive Functor (Identity :: Type -> Type) +derive Functor (Const @&0 &1 :: &0 -> Type) +derive Functor (Maybe :: Type -> Type) diff --git a/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Data.Functor.purs b/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Data.Functor.purs new file mode 100644 index 00000000..354b844e --- /dev/null +++ b/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Data.Functor.purs @@ -0,0 +1,4 @@ +module Data.Functor where + +class Functor f where + map :: forall a b. (a -> b) -> f a -> f b diff --git a/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.purs b/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.purs new file mode 100644 index 00000000..af0de0ee --- /dev/null +++ b/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.purs @@ -0,0 +1,9 @@ +module Main where + +import Data.Functor (class Functor) + +data Wrap f a = Wrap (f a) +derive instance Functor f => Functor (Wrap f) + +data WrapNoFunctor f a = WrapNoFunctor (f a) +derive instance Functor (WrapNoFunctor f) diff --git a/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap b/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap new file mode 100644 index 00000000..a85da0e3 --- /dev/null +++ b/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap @@ -0,0 +1,28 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Wrap :: forall (t4 :: Type) (f :: t4 -> Type) (a :: t4). f a -> Wrap @t4 f a +WrapNoFunctor :: forall (t10 :: Type) (f :: t10 -> Type) (a :: t10). f a -> WrapNoFunctor @t10 f a + +Types +Wrap :: forall (t4 :: Type). (t4 -> Type) -> t4 -> Type +WrapNoFunctor :: forall (t10 :: Type). (t10 -> Type) -> t10 -> Type + +Data +Wrap + Quantified = :1 + Kind = :0 + +WrapNoFunctor + Quantified = :1 + Kind = :0 + + +Derived +derive Functor &0 => Functor (Wrap @Type &0 :: Type -> Type) +derive Functor (WrapNoFunctor @&0 &1 :: &0 -> Type) + +Errors +NoInstanceFound { Functor &0 } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Data.Functor.purs b/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Data.Functor.purs new file mode 100644 index 00000000..354b844e --- /dev/null +++ b/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Data.Functor.purs @@ -0,0 +1,4 @@ +module Data.Functor where + +class Functor f where + map :: forall a b. (a -> b) -> f a -> f b diff --git a/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.purs b/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.purs new file mode 100644 index 00000000..08f7b484 --- /dev/null +++ b/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.purs @@ -0,0 +1,13 @@ +module Main where + +import Data.Functor (class Functor) + +data Predicate a = Predicate (a -> Boolean) +derive instance Functor Predicate + +data Reader r a = Reader (r -> a) +derive instance Functor (Reader r) + +-- Pass: variance flips twice +data Cont r a = Cont ((a -> r) -> r) +derive instance Functor (Cont r) diff --git a/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap b/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap new file mode 100644 index 00000000..fa6173a0 --- /dev/null +++ b/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap @@ -0,0 +1,35 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Predicate :: forall (a :: Type). (a -> Boolean) -> Predicate a +Reader :: forall (r :: Type) (a :: Type). (r -> a) -> Reader r a +Cont :: forall (r :: Type) (a :: Type). ((a -> r) -> r) -> Cont r a + +Types +Predicate :: Type -> Type +Reader :: Type -> Type -> Type +Cont :: Type -> Type -> Type + +Data +Predicate + Quantified = :0 + Kind = :0 + +Reader + Quantified = :0 + Kind = :0 + +Cont + Quantified = :0 + Kind = :0 + + +Derived +derive Functor (Predicate :: Type -> Type) +derive Functor (Reader &0 :: Type -> Type) +derive Functor (Cont &0 :: Type -> Type) + +Errors +ContravariantOccurrence { type_id: Id(31) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Data.Bifunctor.purs b/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Data.Bifunctor.purs new file mode 100644 index 00000000..54b5e06b --- /dev/null +++ b/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Data.Bifunctor.purs @@ -0,0 +1,4 @@ +module Data.Bifunctor where + +class Bifunctor f where + bimap :: forall a b c d. (a -> b) -> (c -> d) -> f a c -> f b d diff --git a/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.purs b/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.purs new file mode 100644 index 00000000..aa956e4b --- /dev/null +++ b/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.purs @@ -0,0 +1,12 @@ +module Main where + +import Data.Bifunctor (class Bifunctor) + +data Either a b = Left a | Right b +derive instance Bifunctor Either + +data Pair a b = Pair a b +derive instance Bifunctor Pair + +data Const2 e a b = Const2 e +derive instance Bifunctor (Const2 e) diff --git a/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap b/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap new file mode 100644 index 00000000..b2576844 --- /dev/null +++ b/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap @@ -0,0 +1,34 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Left :: forall (a :: Type) (b :: Type). a -> Either a b +Right :: forall (a :: Type) (b :: Type). b -> Either a b +Pair :: forall (a :: Type) (b :: Type). a -> b -> Pair a b +Const2 :: + forall (t5 :: Type) (t6 :: Type) (e :: Type) (a :: t5) (b :: t6). e -> Const2 @t5 @t6 e a b + +Types +Either :: Type -> Type -> Type +Pair :: Type -> Type -> Type +Const2 :: forall (t5 :: Type) (t6 :: Type). Type -> t5 -> t6 -> Type + +Data +Either + Quantified = :0 + Kind = :0 + +Pair + Quantified = :0 + Kind = :0 + +Const2 + Quantified = :2 + Kind = :0 + + +Derived +derive Bifunctor (Either :: Type -> Type -> Type) +derive Bifunctor (Pair :: Type -> Type -> Type) +derive Bifunctor (Const2 @&0 @&1 &2 :: &0 -> &1 -> Type) diff --git a/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Bifunctor.purs b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Bifunctor.purs new file mode 100644 index 00000000..54b5e06b --- /dev/null +++ b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Bifunctor.purs @@ -0,0 +1,4 @@ +module Data.Bifunctor where + +class Bifunctor f where + bimap :: forall a b c d. (a -> b) -> (c -> d) -> f a c -> f b d diff --git a/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Functor.purs b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Functor.purs new file mode 100644 index 00000000..354b844e --- /dev/null +++ b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Functor.purs @@ -0,0 +1,4 @@ +module Data.Functor where + +class Functor f where + map :: forall a b. (a -> b) -> f a -> f b diff --git a/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.purs b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.purs new file mode 100644 index 00000000..d6d7c0da --- /dev/null +++ b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.purs @@ -0,0 +1,10 @@ +module Main where + +import Data.Functor (class Functor) +import Data.Bifunctor (class Bifunctor) + +data WrapBoth f g a b = WrapBoth (f a) (g b) +derive instance (Functor f, Functor g) => Bifunctor (WrapBoth f g) + +data WrapBothNoConstraint f g a b = WrapBothNoConstraint (f a) (g b) +derive instance Bifunctor (WrapBothNoConstraint f g) diff --git a/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap new file mode 100644 index 00000000..bac235fc --- /dev/null +++ b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap @@ -0,0 +1,34 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +WrapBoth :: + forall (t6 :: Type) (t10 :: Type) (f :: t6 -> Type) (g :: t10 -> Type) (a :: t6) (b :: t10). + f a -> g b -> WrapBoth @t6 @t10 f g a b +WrapBothNoConstraint :: + forall (t18 :: Type) (t22 :: Type) (f :: t18 -> Type) (g :: t22 -> Type) (a :: t18) (b :: t22). + f a -> g b -> WrapBothNoConstraint @t18 @t22 f g a b + +Types +WrapBoth :: forall (t6 :: Type) (t10 :: Type). (t6 -> Type) -> (t10 -> Type) -> t6 -> t10 -> Type +WrapBothNoConstraint :: + forall (t18 :: Type) (t22 :: Type). (t18 -> Type) -> (t22 -> Type) -> t18 -> t22 -> Type + +Data +WrapBoth + Quantified = :2 + Kind = :0 + +WrapBothNoConstraint + Quantified = :2 + Kind = :0 + + +Derived +derive (Functor &0, Functor &1) => Bifunctor (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) +derive Bifunctor (WrapBothNoConstraint @&0 @&1 &2 &3 :: &0 -> &1 -> Type) + +Errors +NoInstanceFound { Functor &0 } at [TermDeclaration(Idx::(3))] +NoInstanceFound { Functor &1 } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Data.Bifunctor.purs b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Data.Bifunctor.purs new file mode 100644 index 00000000..54b5e06b --- /dev/null +++ b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Data.Bifunctor.purs @@ -0,0 +1,4 @@ +module Data.Bifunctor where + +class Bifunctor f where + bimap :: forall a b c d. (a -> b) -> (c -> d) -> f a c -> f b d diff --git a/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.purs b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.purs new file mode 100644 index 00000000..2a0fb76e --- /dev/null +++ b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.purs @@ -0,0 +1,6 @@ +module Main where + +import Data.Bifunctor (class Bifunctor) + +data WrapBoth f g a b = WrapBoth (f a) (g b) +derive instance Bifunctor (WrapBoth f g) diff --git a/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap new file mode 100644 index 00000000..3526e92e --- /dev/null +++ b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap @@ -0,0 +1,24 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +WrapBoth :: + forall (t6 :: Type) (t10 :: Type) (f :: t6 -> Type) (g :: t10 -> Type) (a :: t6) (b :: t10). + f a -> g b -> WrapBoth @t6 @t10 f g a b + +Types +WrapBoth :: forall (t6 :: Type) (t10 :: Type). (t6 -> Type) -> (t10 -> Type) -> t6 -> t10 -> Type + +Data +WrapBoth + Quantified = :2 + Kind = :0 + + +Derived +derive Bifunctor (WrapBoth @&0 @&1 &2 &3 :: &0 -> &1 -> Type) + +Errors +DeriveMissingFunctor at [TermDeclaration(Idx::(1))] +DeriveMissingFunctor at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index dba5e7ef..23d4f5f1 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -301,3 +301,15 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_144_derive_newtype_missing_given_main() { run_test("144_derive_newtype_missing_given", "Main"); } #[rustfmt::skip] #[test] fn test_145_derive_newtype_multi_param_main() { run_test("145_derive_newtype_multi_param", "Main"); } + +#[rustfmt::skip] #[test] fn test_146_derive_functor_simple_main() { run_test("146_derive_functor_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_147_derive_functor_higher_kinded_main() { run_test("147_derive_functor_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_148_derive_functor_contravariant_error_main() { run_test("148_derive_functor_contravariant_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_149_derive_bifunctor_simple_main() { run_test("149_derive_bifunctor_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_150_derive_bifunctor_higher_kinded_main() { run_test("150_derive_bifunctor_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_151_derive_bifunctor_missing_functor_main() { run_test("151_derive_bifunctor_missing_functor", "Main"); } From f2e68b7d62a346176db4979d64f2daea9bd57df9 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 18:11:03 +0800 Subject: [PATCH 19/62] Extract shared utils to derive::tools --- .../checking/src/algorithm/derive.rs | 146 +++--------------- .../checking/src/algorithm/derive/functor.rs | 142 ++--------------- .../src/algorithm/derive/higher_kinded.rs | 16 +- .../checking/src/algorithm/derive/tools.rs | 109 +++++++++++++ 4 files changed, 149 insertions(+), 264 deletions(-) create mode 100644 compiler-core/checking/src/algorithm/derive/tools.rs diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index 26fcb266..6fc91a51 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -2,17 +2,17 @@ mod functor; mod higher_kinded; +mod tools; use building_types::QueryResult; use files::FileId; use indexing::{DeriveId, TermItemId, TypeItemId}; -use itertools::Itertools; use crate::ExternalQueries; use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::{kind, quantify, substitute, transfer}; -use crate::core::{Instance, InstanceKind, Type, TypeId, Variable, debruijn}; +use crate::algorithm::{kind, substitute, transfer}; +use crate::core::{Type, TypeId, Variable, debruijn}; use crate::error::{ErrorKind, ErrorStep}; /// Input fields for [`check_derive`]. @@ -26,13 +26,6 @@ pub struct CheckDerive<'a> { pub is_newtype: bool, } -struct ElaboratedDerive { - derive_id: DeriveId, - constraints: Vec<(TypeId, TypeId)>, - arguments: Vec<(TypeId, TypeId)>, - class_file: FileId, - class_id: TypeItemId, -} /// Checks a derived instance. pub fn check_derive( @@ -71,7 +64,7 @@ where core_constraints.push((inferred_type, inferred_kind)); } - let elaborated = ElaboratedDerive { + let elaborated = tools::ElaboratedDerive { derive_id, constraints: core_constraints, arguments: core_arguments, @@ -80,7 +73,7 @@ where }; if is_newtype { - check_newtype_derive(state, context, &elaborated)?; + check_newtype_derive(state, context, elaborated)?; } else { let class = (class_file, class_id); @@ -90,11 +83,11 @@ where let is_bifunctor = context.known_types.bifunctor == Some(class); if is_eq || is_ord { - check_derive_class(state, context, &elaborated)?; + check_derive_class(state, context, elaborated)?; } else if is_functor { - functor::check_derive_functor(state, context, &elaborated)?; + functor::check_derive_functor(state, context, elaborated)?; } else if is_bifunctor { - functor::check_derive_bifunctor(state, context, &elaborated)?; + functor::check_derive_bifunctor(state, context, elaborated)?; } else { state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); }; @@ -110,7 +103,7 @@ where fn check_derive_class( state: &mut CheckState, context: &CheckContext, - input: &ElaboratedDerive, + input: tools::ElaboratedDerive, ) -> QueryResult<()> where Q: ExternalQueries, @@ -131,64 +124,19 @@ where return Ok(()); }; - let mut instance = Instance { - arguments: input.arguments.clone(), - constraints: input.constraints.clone(), - resolution: (input.class_file, input.class_id), - kind: InstanceKind::Derive, - kind_variables: debruijn::Size(0), - }; - - quantify::quantify_instance(state, &mut instance); - - let global_arguments = instance.arguments.iter().map(|&(t, k)| { - let t = transfer::globalize(state, context, t); - let k = transfer::globalize(state, context, k); - (t, k) - }); - - let global_arguments = global_arguments.collect_vec(); - - let global_constraints = instance.constraints.iter().map(|&(t, k)| { - let t = transfer::globalize(state, context, t); - let k = transfer::globalize(state, context, k); - (t, k) - }); - - let global_constraints = global_constraints.collect_vec(); - - // Register derived instances to allow recursive references. - state.checked.derived.insert( - input.derive_id, - Instance { - arguments: global_arguments, - constraints: global_constraints, - resolution: (input.class_file, input.class_id), - kind: InstanceKind::Derive, - kind_variables: instance.kind_variables, - }, - ); - - for (constraint_type, _) in &input.constraints { - state.constraints.push_given(*constraint_type); - } - let class = (input.class_file, input.class_id); - generate_field_constraints(state, context, data_file, data_id, derived_type, class)?; + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); - let residual = state.solve_constraints(context)?; - for constraint in residual { - let constraint = transfer::globalize(state, context, constraint); - state.insert_error(ErrorKind::NoInstanceFound { constraint }); - } + generate_field_constraints(state, context, data_file, data_id, derived_type, class)?; - Ok(()) + tools::solve_and_report_constraints(state, context) } fn check_newtype_derive( state: &mut CheckState, context: &CheckContext, - input: &ElaboratedDerive, + input: tools::ElaboratedDerive, ) -> QueryResult<()> where Q: ExternalQueries, @@ -220,47 +168,6 @@ where let inner_type = get_newtype_inner(state, context, newtype_file, newtype_id, newtype_type)?; - let mut instance = Instance { - arguments: input.arguments.clone(), - constraints: input.constraints.clone(), - resolution: (input.class_file, input.class_id), - kind: InstanceKind::Derive, - kind_variables: debruijn::Size(0), - }; - - quantify::quantify_instance(state, &mut instance); - - let global_arguments = instance.arguments.iter().map(|&(t, k)| { - let t = transfer::globalize(state, context, t); - let k = transfer::globalize(state, context, k); - (t, k) - }); - - let global_arguments = global_arguments.collect_vec(); - - let global_constraints = instance.constraints.iter().map(|&(t, k)| { - let t = transfer::globalize(state, context, t); - let k = transfer::globalize(state, context, k); - (t, k) - }); - - let global_constraints = global_constraints.collect_vec(); - - state.checked.derived.insert( - input.derive_id, - Instance { - arguments: global_arguments, - constraints: global_constraints, - resolution: (input.class_file, input.class_id), - kind: InstanceKind::Derive, - kind_variables: instance.kind_variables, - }, - ); - - for (constraint_type, _) in &input.constraints { - state.constraints.push_given(*constraint_type); - } - // Build `Class t1 t2 Inner` given the constraint `Class t1 t2 Newtype` let delegate_constraint = { let class_type = state.storage.intern(Type::Constructor(input.class_file, input.class_id)); @@ -273,15 +180,12 @@ where state.storage.intern(Type::Application(preceding_arguments, inner_type)) }; - state.constraints.push_wanted(delegate_constraint); + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); - let residual = state.solve_constraints(context)?; - for constraint in residual { - let constraint = transfer::globalize(state, context, constraint); - state.insert_error(ErrorKind::NoInstanceFound { constraint }); - } + state.constraints.push_wanted(delegate_constraint); - Ok(()) + tools::solve_and_report_constraints(state, context) } pub(crate) fn extract_type_constructor( @@ -377,12 +281,7 @@ fn get_newtype_inner( where Q: ExternalQueries, { - let constructors = if newtype_file == context.id { - context.indexed.pairs.data_constructors(newtype_id).collect_vec() - } else { - let indexed = context.queries.indexed(newtype_file)?; - indexed.pairs.data_constructors(newtype_id).collect_vec() - }; + let constructors = tools::lookup_data_constructors(context, newtype_file, newtype_id)?; let [constructor_id] = constructors[..] else { return Ok(context.prim.unknown); @@ -413,12 +312,7 @@ fn generate_field_constraints( where Q: ExternalQueries, { - let constructors = if data_file == context.id { - context.indexed.pairs.data_constructors(data_id).collect_vec() - } else { - let indexed = context.queries.indexed(data_file)?; - indexed.pairs.data_constructors(data_id).collect_vec() - }; + let constructors = tools::lookup_data_constructors(context, data_file, data_id)?; let class1 = if context.known_types.eq == Some(class) { context.known_types.eq1 diff --git a/compiler-core/checking/src/algorithm/derive/functor.rs b/compiler-core/checking/src/algorithm/derive/functor.rs index 9d6eef6f..e5d3daab 100644 --- a/compiler-core/checking/src/algorithm/derive/functor.rs +++ b/compiler-core/checking/src/algorithm/derive/functor.rs @@ -9,12 +9,13 @@ use files::FileId; use indexing::TypeItemId; use itertools::Itertools; +use crate::algorithm::derive::tools; use crate::ExternalQueries; -use crate::algorithm::derive::{ElaboratedDerive, extract_type_arguments}; +use crate::algorithm::derive::extract_type_arguments; use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::{quantify, substitute, transfer}; -use crate::core::{Instance, InstanceKind, RowType, Type, TypeId, Variable, debruijn}; +use crate::algorithm::{substitute, transfer}; +use crate::core::{RowType, Type, TypeId, Variable, debruijn}; use crate::error::ErrorKind; /// Variance of a type position. @@ -51,7 +52,7 @@ impl DerivedSkolems { pub fn check_derive_functor( state: &mut CheckState, context: &CheckContext, - input: &ElaboratedDerive, + input: tools::ElaboratedDerive, ) -> QueryResult<()> where Q: ExternalQueries, @@ -72,68 +73,20 @@ where return Ok(()); }; - let mut instance = Instance { - arguments: input.arguments.clone(), - constraints: input.constraints.clone(), - resolution: (input.class_file, input.class_id), - kind: InstanceKind::Derive, - kind_variables: debruijn::Size(0), - }; - - quantify::quantify_instance(state, &mut instance); - - let global_arguments = instance - .arguments - .iter() - .map(|&(t, k)| { - let t = transfer::globalize(state, context, t); - let k = transfer::globalize(state, context, k); - (t, k) - }) - .collect_vec(); - - let global_constraints = instance - .constraints - .iter() - .map(|&(t, k)| { - let t = transfer::globalize(state, context, t); - let k = transfer::globalize(state, context, k); - (t, k) - }) - .collect_vec(); - - state.checked.derived.insert( - input.derive_id, - Instance { - arguments: global_arguments, - constraints: global_constraints, - resolution: (input.class_file, input.class_id), - kind: InstanceKind::Derive, - kind_variables: instance.kind_variables, - }, - ); - - for (constraint_type, _) in &input.constraints { - state.constraints.push_given(*constraint_type); - } - let functor = Some((input.class_file, input.class_id)); - generate_functor_constraints(state, context, data_file, data_id, derived_type, functor, 1)?; + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); - let residual = state.solve_constraints(context)?; - for constraint in residual { - let constraint = transfer::globalize(state, context, constraint); - state.insert_error(ErrorKind::NoInstanceFound { constraint }); - } + generate_functor_constraints(state, context, data_file, data_id, derived_type, functor, 1)?; - Ok(()) + tools::solve_and_report_constraints(state, context) } /// Checks a derive instance for Bifunctor. pub fn check_derive_bifunctor( state: &mut CheckState, context: &CheckContext, - input: &ElaboratedDerive, + input: tools::ElaboratedDerive, ) -> QueryResult<()> where Q: ExternalQueries, @@ -154,62 +107,14 @@ where return Ok(()); }; - let mut instance = Instance { - arguments: input.arguments.clone(), - constraints: input.constraints.clone(), - resolution: (input.class_file, input.class_id), - kind: InstanceKind::Derive, - kind_variables: debruijn::Size(0), - }; - - quantify::quantify_instance(state, &mut instance); - - let global_arguments = instance - .arguments - .iter() - .map(|&(t, k)| { - let t = transfer::globalize(state, context, t); - let k = transfer::globalize(state, context, k); - (t, k) - }) - .collect_vec(); - - let global_constraints = instance - .constraints - .iter() - .map(|&(t, k)| { - let t = transfer::globalize(state, context, t); - let k = transfer::globalize(state, context, k); - (t, k) - }) - .collect_vec(); - - state.checked.derived.insert( - input.derive_id, - Instance { - arguments: global_arguments, - constraints: global_constraints, - resolution: (input.class_file, input.class_id), - kind: InstanceKind::Derive, - kind_variables: instance.kind_variables, - }, - ); - - for (constraint_type, _) in &input.constraints { - state.constraints.push_given(*constraint_type); - } - // Bifunctor derivation emits Functor constraints for wrapped parameters. let functor = context.known_types.functor; - generate_functor_constraints(state, context, data_file, data_id, derived_type, functor, 2)?; + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); - let residual = state.solve_constraints(context)?; - for constraint in residual { - let constraint = transfer::globalize(state, context, constraint); - state.insert_error(ErrorKind::NoInstanceFound { constraint }); - } + generate_functor_constraints(state, context, data_file, data_id, derived_type, functor, 2)?; - Ok(()) + tools::solve_and_report_constraints(state, context) } /// Generates variance-aware constraints for Functor/Bifunctor derivation. @@ -225,12 +130,7 @@ fn generate_functor_constraints( where Q: ExternalQueries, { - let constructors = if data_file == context.id { - context.indexed.pairs.data_constructors(data_id).collect_vec() - } else { - let indexed = context.queries.indexed(data_file)?; - indexed.pairs.data_constructors(data_id).collect_vec() - }; + let constructors = tools::lookup_data_constructors(context, data_file, data_id)?; for constructor_id in constructors { let constructor_type = @@ -338,7 +238,7 @@ fn check_functor_field( let global = transfer::globalize(state, context, type_id); state.insert_error(ErrorKind::ContravariantOccurrence { type_id: global }); } else if let Some(functor) = functor { - emit_constraint(state, functor, function); + tools::emit_constraint(state, functor, function); } else { state.insert_error(ErrorKind::DeriveMissingFunctor); } @@ -396,13 +296,3 @@ fn contains_derived_skolem( } } -/// Emits a constraint `Class type_id`. -fn emit_constraint( - state: &mut CheckState, - (class_file, class_id): (FileId, TypeItemId), - type_id: TypeId, -) { - let class_type = state.storage.intern(Type::Constructor(class_file, class_id)); - let constraint = state.storage.intern(Type::Application(class_type, type_id)); - state.constraints.push_wanted(constraint); -} diff --git a/compiler-core/checking/src/algorithm/derive/higher_kinded.rs b/compiler-core/checking/src/algorithm/derive/higher_kinded.rs index 1595480e..fcb07411 100644 --- a/compiler-core/checking/src/algorithm/derive/higher_kinded.rs +++ b/compiler-core/checking/src/algorithm/derive/higher_kinded.rs @@ -7,6 +7,7 @@ use files::FileId; use indexing::TypeItemId; +use crate::algorithm::derive::tools; use crate::ExternalQueries; use crate::algorithm::fold::Zonk; use crate::algorithm::state::{CheckContext, CheckState}; @@ -45,11 +46,11 @@ pub fn generate_constraint( generate_constraint(state, context, argument, class, class1); } else if is_variable_type_type(state, context, function) { if let Some(class1) = class1 { - emit_constraint(state, class1, function); + tools::emit_constraint(state, class1, function); } generate_constraint(state, context, argument, class, class1); } else { - emit_constraint(state, class, type_id); + tools::emit_constraint(state, class, type_id); } } Type::Row(RowType { ref fields, .. }) => { @@ -58,7 +59,7 @@ pub fn generate_constraint( } } _ => { - emit_constraint(state, class, type_id); + tools::emit_constraint(state, class, type_id); } } } @@ -94,12 +95,3 @@ fn lookup_variable_kind(state: &CheckState, variable: &Variable) -> Option, + pub arguments: Vec<(TypeId, TypeId)>, + pub class_file: FileId, + pub class_id: TypeItemId, +} + +/// Emits a type class constraint `Class type_id`. +pub fn emit_constraint( + state: &mut CheckState, + (class_file, class_id): (FileId, TypeItemId), + type_id: TypeId, +) { + let class_type = state.storage.intern(Type::Constructor(class_file, class_id)); + let constraint = state.storage.intern(Type::Application(class_type, type_id)); + state.constraints.push_wanted(constraint); +} + +/// Pushes given constraints from the instance head onto the constraint stack. +pub fn push_given_constraints(state: &mut CheckState, constraints: &[(TypeId, TypeId)]) { + for (constraint_type, _) in constraints { + state.constraints.push_given(*constraint_type); + } +} + +/// Solves constraints and reports any unsatisfied constraints as errors. +pub fn solve_and_report_constraints( + state: &mut CheckState, + context: &CheckContext, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let residual = state.solve_constraints(context)?; + for constraint in residual { + let constraint = transfer::globalize(state, context, constraint); + state.insert_error(ErrorKind::NoInstanceFound { constraint }); + } + Ok(()) +} + +/// Registers a derived instance in the checked state. +/// +/// This performs generalisation and globalization of the instance +/// types, then stores the result in [`CheckState::checked`]. +pub fn register_derived_instance( + state: &mut CheckState, + context: &CheckContext, + input: ElaboratedDerive, +) where + Q: ExternalQueries, +{ + let ElaboratedDerive { derive_id, constraints, arguments, class_file, class_id } = input; + + let mut instance = Instance { + arguments, + constraints, + resolution: (class_file, class_id), + kind: InstanceKind::Derive, + kind_variables: debruijn::Size(0), + }; + + quantify::quantify_instance(state, &mut instance); + + for (t, k) in instance.arguments.iter_mut() { + *t = transfer::globalize(state, context, *t); + *k = transfer::globalize(state, context, *k); + } + + for (t, k) in instance.constraints.iter_mut() { + *t = transfer::globalize(state, context, *t); + *k = transfer::globalize(state, context, *k); + } + + state.checked.derived.insert(derive_id, instance); +} + +/// Looks up data constructors for a type, handling cross-file lookups. +pub fn lookup_data_constructors( + context: &CheckContext, + data_file: FileId, + data_id: TypeItemId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let constructors = if data_file == context.id { + context.indexed.pairs.data_constructors(data_id).collect_vec() + } else { + let indexed = context.queries.indexed(data_file)?; + indexed.pairs.data_constructors(data_id).collect_vec() + }; + Ok(constructors) +} From 1d8474cdbc567b58581b24d15cba46a002c2afa7 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 18:54:27 +0800 Subject: [PATCH 20/62] Initial derive for Contravariant and Profunctor --- .../checking/src/algorithm/derive.rs | 7 +- .../checking/src/algorithm/derive/functor.rs | 257 +++++++++++++----- .../src/algorithm/derive/higher_kinded.rs | 3 +- compiler-core/checking/src/algorithm/state.rs | 7 +- compiler-core/checking/src/error.rs | 3 + compiler-core/lowering/src/lib.rs | 13 +- .../Data.Functor.Contravariant.purs | 4 + .../152_derive_contravariant_simple/Main.purs | 12 + .../152_derive_contravariant_simple/Main.snap | 32 +++ .../Data.Functor.Contravariant.purs | 4 + .../153_derive_contravariant_error/Main.purs | 11 + .../153_derive_contravariant_error/Main.snap | 29 ++ .../Data.Profunctor.purs | 4 + .../154_derive_profunctor_simple/Main.purs | 15 + .../154_derive_profunctor_simple/Main.snap | 33 +++ .../Data.Profunctor.purs | 4 + .../155_derive_profunctor_error/Main.purs | 11 + .../155_derive_profunctor_error/Main.snap | 30 ++ tests-integration/tests/checking/generated.rs | 8 + 19 files changed, 416 insertions(+), 71 deletions(-) create mode 100644 tests-integration/fixtures/checking/152_derive_contravariant_simple/Data.Functor.Contravariant.purs create mode 100644 tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.snap create mode 100644 tests-integration/fixtures/checking/153_derive_contravariant_error/Data.Functor.Contravariant.purs create mode 100644 tests-integration/fixtures/checking/153_derive_contravariant_error/Main.purs create mode 100644 tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap create mode 100644 tests-integration/fixtures/checking/154_derive_profunctor_simple/Data.Profunctor.purs create mode 100644 tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap create mode 100644 tests-integration/fixtures/checking/155_derive_profunctor_error/Data.Profunctor.purs create mode 100644 tests-integration/fixtures/checking/155_derive_profunctor_error/Main.purs create mode 100644 tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index 6fc91a51..cd4eae79 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -26,7 +26,6 @@ pub struct CheckDerive<'a> { pub is_newtype: bool, } - /// Checks a derived instance. pub fn check_derive( state: &mut CheckState, @@ -81,6 +80,8 @@ where let is_ord = context.known_types.ord == Some(class); let is_functor = context.known_types.functor == Some(class); let is_bifunctor = context.known_types.bifunctor == Some(class); + let is_contravariant = context.known_types.contravariant == Some(class); + let is_profunctor = context.known_types.profunctor == Some(class); if is_eq || is_ord { check_derive_class(state, context, elaborated)?; @@ -88,6 +89,10 @@ where functor::check_derive_functor(state, context, elaborated)?; } else if is_bifunctor { functor::check_derive_bifunctor(state, context, elaborated)?; + } else if is_contravariant { + functor::check_derive_contravariant(state, context, elaborated)?; + } else if is_profunctor { + functor::check_derive_profunctor(state, context, elaborated)?; } else { state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); }; diff --git a/compiler-core/checking/src/algorithm/derive/functor.rs b/compiler-core/checking/src/algorithm/derive/functor.rs index e5d3daab..30cc3668 100644 --- a/compiler-core/checking/src/algorithm/derive/functor.rs +++ b/compiler-core/checking/src/algorithm/derive/functor.rs @@ -1,17 +1,17 @@ -//! Implements derive for Functor and Bifunctor type classes. +//! Implements derive for variance-aware type classes. //! -//! Unlike Eq/Ord which require constraints on all fields, Functor/Bifunctor -//! derivation is variance-aware: the derived parameter must only appear in -//! covariant positions (right of function arrows, type application arguments). +//! This module handles derivation for Functor, Bifunctor, Contravariant, and +//! Profunctor. Unlike Eq/Ord which require constraints on all fields, these +//! classes are variance-aware: each derived parameter has an expected variance +//! (covariant or contravariant) that must be satisfied. use building_types::QueryResult; use files::FileId; use indexing::TypeItemId; use itertools::Itertools; -use crate::algorithm::derive::tools; use crate::ExternalQueries; -use crate::algorithm::derive::extract_type_arguments; +use crate::algorithm::derive::{extract_type_arguments, tools}; use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::{substitute, transfer}; @@ -34,17 +34,31 @@ impl Variance { } } +/// A derived type parameter with its expected variance and wrapper class. +#[derive(Clone, Copy)] +struct DerivedParam { + level: debruijn::Level, + /// Expected variance for this parameter. + expected: Variance, + /// The class to emit when this parameter appears wrapped in a type application. + /// For Functor/Bifunctor this is Functor, for Contravariant/Profunctor's first + /// param this is Contravariant. + wrapper_class: Option<(FileId, TypeItemId)>, +} + /// Tracks the Skolem variables representing derived type parameters. struct DerivedSkolems { - /// Skolem levels for the parameters being mapped over. - /// - `Functor`: one level - /// - `Bifunctor`: two levels - levels: Vec, + /// Parameters being mapped over with their variance requirements. + /// - `Functor`: one param (Covariant, Functor) + /// - `Contravariant`: one param (Contravariant, Contravariant) + /// - `Bifunctor`: two params (Covariant, Functor), (Covariant, Functor) + /// - `Profunctor`: two params (Contravariant, Contravariant), (Covariant, Functor) + params: Vec, } impl DerivedSkolems { - fn contains(&self, level: debruijn::Level) -> bool { - self.levels.contains(&level) + fn get(&self, level: debruijn::Level) -> Option<&DerivedParam> { + self.params.iter().find(|p| p.level == level) } } @@ -77,7 +91,8 @@ where tools::push_given_constraints(state, &input.constraints); tools::register_derived_instance(state, context, input); - generate_functor_constraints(state, context, data_file, data_id, derived_type, functor, 1)?; + let config = VarianceConfig::new([(Variance::Covariant, functor)]); + generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; tools::solve_and_report_constraints(state, context) } @@ -112,20 +127,114 @@ where tools::push_given_constraints(state, &input.constraints); tools::register_derived_instance(state, context, input); - generate_functor_constraints(state, context, data_file, data_id, derived_type, functor, 2)?; + let config = + VarianceConfig::new([(Variance::Covariant, functor), (Variance::Covariant, functor)]); + + generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; + + tools::solve_and_report_constraints(state, context) +} + +/// Checks a derive instance for Contravariant. +pub fn check_derive_contravariant( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 1, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + let contravariant = Some((input.class_file, input.class_id)); + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); + + let config = VarianceConfig::new([(Variance::Contravariant, contravariant)]); + generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; + + tools::solve_and_report_constraints(state, context) +} + +/// Checks a derive instance for Profunctor. +pub fn check_derive_profunctor( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 1, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + // Profunctor: first param is contravariant, second is covariant. + let contravariant = context.known_types.contravariant; + let functor = context.known_types.functor; + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); + + let config = VarianceConfig::new([ + (Variance::Contravariant, contravariant), + (Variance::Covariant, functor), + ]); + + generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; tools::solve_and_report_constraints(state, context) } -/// Generates variance-aware constraints for Functor/Bifunctor derivation. -fn generate_functor_constraints( +/// Expected variance and wrapper class for a derived parameter. +type ParameterConfig = (Variance, Option<(FileId, TypeItemId)>); + +/// Configuration for variance-aware derivation. +struct VarianceConfig { + /// Expected variance and wrapper class for each derived parameter. + /// Ordered from first to last (e.g., for `data T a b`, index 0 is `a`). + params: Vec, +} + +impl VarianceConfig { + fn new(params: impl IntoIterator) -> Self { + Self { params: params.into_iter().collect() } + } +} + +/// Generates variance-aware constraints for Functor-like derivation. +fn generate_variance_constraints( state: &mut CheckState, context: &CheckContext, data_file: FileId, data_id: TypeItemId, derived_type: TypeId, - functor: Option<(FileId, TypeItemId)>, - parameter_count: usize, + config: VarianceConfig, ) -> QueryResult<()> where Q: ExternalQueries, @@ -138,10 +247,10 @@ where let Some(constructor_type) = constructor_type else { continue }; let (fields, skolems) = - extract_fields_with_skolems(state, constructor_type, derived_type, parameter_count); + extract_fields_with_skolems(state, constructor_type, derived_type, &config); for field_type in fields { - check_functor_field(state, context, field_type, Variance::Covariant, &skolems, functor); + check_variance_field(state, context, field_type, Variance::Covariant, &skolems); } } @@ -150,13 +259,13 @@ where /// Extracts constructor fields and tracks Skolem variables for unmapped parameters. /// -/// Similar to `extract_constructor_fields` but also returns the Skolem variables -/// created for type parameters that weren't provided in the instance head. +/// Uses the variance configuration to assign expected variance and wrapper class +/// to each derived parameter's Skolem variable. fn extract_fields_with_skolems( state: &mut CheckState, constructor_type: TypeId, derived_type: TypeId, - parameter_count: usize, + config: &VarianceConfig, ) -> (Vec, DerivedSkolems) { let type_arguments = extract_type_arguments(state, derived_type); let mut arguments_iter = type_arguments.into_iter(); @@ -183,9 +292,16 @@ fn extract_fields_with_skolems( } } - // The last parameter_count variables are used for Functor/Bifunctor. - // When arguments_iter runs out, it creates skolem variables instead. - let levels = levels.into_iter().rev().take(parameter_count).collect_vec(); + // The last N levels correspond to the N derived parameters. + // Take last N via rev().take(N), then restore original order. + let param_count = config.params.len(); + let derived_levels: Vec<_> = levels.into_iter().rev().take(param_count).collect(); + let params = derived_levels + .into_iter() + .rev() + .zip(config.params.iter()) + .map(|(level, &(expected, wrapper_class))| DerivedParam { level, expected, wrapper_class }) + .collect_vec(); let mut fields = vec![]; safe_loop! { @@ -199,100 +315,111 @@ fn extract_fields_with_skolems( } } - (fields, DerivedSkolems { levels }) + (fields, DerivedSkolems { params }) } -/// Checks a field type for variance violations and emits Functor constraints. -fn check_functor_field( +/// Checks a field type for variance violations and emits wrapper class constraints. +fn check_variance_field( state: &mut CheckState, context: &CheckContext, type_id: TypeId, variance: Variance, skolems: &DerivedSkolems, - functor: Option<(FileId, TypeItemId)>, ) where Q: ExternalQueries, { let type_id = state.normalize_type(type_id); match state.storage[type_id].clone() { - Type::Variable(Variable::Skolem(level, _)) if skolems.contains(level) => { - if variance == Variance::Contravariant { - let global = transfer::globalize(state, context, type_id); - state.insert_error(ErrorKind::ContravariantOccurrence { type_id: global }); + Type::Variable(Variable::Skolem(level, _)) => { + if let Some(param) = skolems.get(level) { + if variance != param.expected { + let global = transfer::globalize(state, context, type_id); + if variance == Variance::Covariant { + state.insert_error(ErrorKind::CovariantOccurrence { type_id: global }); + } else { + state.insert_error(ErrorKind::ContravariantOccurrence { type_id: global }); + } + } } } Type::Function(argument, result) => { - check_functor_field(state, context, argument, variance.flip(), skolems, functor); - check_functor_field(state, context, result, variance, skolems, functor); + check_variance_field(state, context, argument, variance.flip(), skolems); + check_variance_field(state, context, result, variance, skolems); } Type::Application(function, argument) => { let function = state.normalize_type(function); if function == context.prim.record { - check_functor_field(state, context, argument, variance, skolems, functor); - } else if contains_derived_skolem(state, argument, skolems) { - if variance == Variance::Contravariant { - let global = transfer::globalize(state, context, type_id); - state.insert_error(ErrorKind::ContravariantOccurrence { type_id: global }); - } else if let Some(functor) = functor { - tools::emit_constraint(state, functor, function); - } else { - state.insert_error(ErrorKind::DeriveMissingFunctor); - } - check_functor_field(state, context, argument, variance, skolems, functor); + check_variance_field(state, context, argument, variance, skolems); } else { - check_functor_field(state, context, argument, variance, skolems, functor); + // Check each derived parameter that appears in the argument + for param in &skolems.params { + if contains_skolem_level(state, argument, param.level) { + if variance != param.expected { + let global = transfer::globalize(state, context, type_id); + if variance == Variance::Covariant { + state.insert_error(ErrorKind::CovariantOccurrence { + type_id: global, + }); + } else { + state.insert_error(ErrorKind::ContravariantOccurrence { + type_id: global, + }); + } + } else if let Some(wrapper_class) = param.wrapper_class { + tools::emit_constraint(state, wrapper_class, function); + } else { + state.insert_error(ErrorKind::DeriveMissingFunctor); + } + } + } + check_variance_field(state, context, argument, variance, skolems); } } Type::Row(RowType { ref fields, .. }) => { for field in fields.iter() { - check_functor_field(state, context, field.id, variance, skolems, functor); + check_variance_field(state, context, field.id, variance, skolems); } } Type::KindApplication(_, argument) => { - check_functor_field(state, context, argument, variance, skolems, functor); + check_variance_field(state, context, argument, variance, skolems); } _ => (), } } -/// Checks if a type contains any of the derived parameter Skolem variables. -fn contains_derived_skolem( - state: &mut CheckState, - type_id: TypeId, - skolems: &DerivedSkolems, -) -> bool { +/// Checks if a type contains a specific Skolem level. +fn contains_skolem_level(state: &mut CheckState, type_id: TypeId, target: debruijn::Level) -> bool { let type_id = state.normalize_type(type_id); match state.storage[type_id].clone() { - Type::Variable(Variable::Skolem(level, _)) => skolems.contains(level), + Type::Variable(Variable::Skolem(level, _)) => level == target, Type::Application(function, argument) | Type::KindApplication(function, argument) => { - contains_derived_skolem(state, function, skolems) - || contains_derived_skolem(state, argument, skolems) + contains_skolem_level(state, function, target) + || contains_skolem_level(state, argument, target) } Type::Function(argument, result) => { - contains_derived_skolem(state, argument, skolems) - || contains_derived_skolem(state, result, skolems) + contains_skolem_level(state, argument, target) + || contains_skolem_level(state, result, target) } Type::Row(RowType { ref fields, tail }) => { - fields.iter().any(|f| contains_derived_skolem(state, f.id, skolems)) - || tail.map_or(false, |t| contains_derived_skolem(state, t, skolems)) + fields.iter().any(|f| contains_skolem_level(state, f.id, target)) + || tail.map_or(false, |t| contains_skolem_level(state, t, target)) } Type::Forall(_, inner) | Type::Constrained(_, inner) | Type::Kinded(inner, _) => { - contains_derived_skolem(state, inner, skolems) + contains_skolem_level(state, inner, target) } _ => false, } } - diff --git a/compiler-core/checking/src/algorithm/derive/higher_kinded.rs b/compiler-core/checking/src/algorithm/derive/higher_kinded.rs index fcb07411..6433c9a0 100644 --- a/compiler-core/checking/src/algorithm/derive/higher_kinded.rs +++ b/compiler-core/checking/src/algorithm/derive/higher_kinded.rs @@ -7,8 +7,8 @@ use files::FileId; use indexing::TypeItemId; -use crate::algorithm::derive::tools; use crate::ExternalQueries; +use crate::algorithm::derive::tools; use crate::algorithm::fold::Zonk; use crate::algorithm::state::{CheckContext, CheckState}; use crate::core::{RowType, Type, TypeId, Variable}; @@ -94,4 +94,3 @@ fn lookup_variable_kind(state: &CheckState, variable: &Variable) -> Option None, } } - diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index c3074f97..fa78678b 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -620,6 +620,8 @@ pub struct KnownTypesCore { pub ord1: Option<(FileId, TypeItemId)>, pub functor: Option<(FileId, TypeItemId)>, pub bifunctor: Option<(FileId, TypeItemId)>, + pub contravariant: Option<(FileId, TypeItemId)>, + pub profunctor: Option<(FileId, TypeItemId)>, } impl KnownTypesCore { @@ -630,7 +632,10 @@ impl KnownTypesCore { let ord1 = fetch_known_type(queries, "Data.Ord", "Ord1")?; let functor = fetch_known_type(queries, "Data.Functor", "Functor")?; let bifunctor = fetch_known_type(queries, "Data.Bifunctor", "Bifunctor")?; - Ok(KnownTypesCore { eq, eq1, ord, ord1, functor, bifunctor }) + let contravariant = + fetch_known_type(queries, "Data.Functor.Contravariant", "Contravariant")?; + let profunctor = fetch_known_type(queries, "Data.Profunctor", "Profunctor")?; + Ok(KnownTypesCore { eq, eq1, ord, ord1, functor, bifunctor, contravariant, profunctor }) } } diff --git a/compiler-core/checking/src/error.rs b/compiler-core/checking/src/error.rs index 42f3fe7f..75ab397f 100644 --- a/compiler-core/checking/src/error.rs +++ b/compiler-core/checking/src/error.rs @@ -35,6 +35,9 @@ pub enum ErrorKind { ContravariantOccurrence { type_id: TypeId, }, + CovariantOccurrence { + type_id: TypeId, + }, CannotUnify { t1: TypeId, t2: TypeId, diff --git a/compiler-core/lowering/src/lib.rs b/compiler-core/lowering/src/lib.rs index abc50841..30d3e0b8 100644 --- a/compiler-core/lowering/src/lib.rs +++ b/compiler-core/lowering/src/lib.rs @@ -53,8 +53,17 @@ pub fn lower_module( indexed: &IndexedModule, resolved: &ResolvedModule, ) -> LoweredModule { - let algorithm::State { info, graph, nodes, term_graph, type_graph, kind_graph, synonym_graph, mut errors, .. } = - algorithm::lower_module(file_id, module, prim, stabilized, indexed, resolved); + let algorithm::State { + info, + graph, + nodes, + term_graph, + type_graph, + kind_graph, + synonym_graph, + mut errors, + .. + } = algorithm::lower_module(file_id, module, prim, stabilized, indexed, resolved); let term_scc = tarjan_scc(&term_graph); let term_scc = term_scc.into_iter().map(into_scc(&term_graph)).collect(); diff --git a/tests-integration/fixtures/checking/152_derive_contravariant_simple/Data.Functor.Contravariant.purs b/tests-integration/fixtures/checking/152_derive_contravariant_simple/Data.Functor.Contravariant.purs new file mode 100644 index 00000000..61246c10 --- /dev/null +++ b/tests-integration/fixtures/checking/152_derive_contravariant_simple/Data.Functor.Contravariant.purs @@ -0,0 +1,4 @@ +module Data.Functor.Contravariant where + +class Contravariant f where + cmap :: forall a b. (b -> a) -> f a -> f b diff --git a/tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.purs b/tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.purs new file mode 100644 index 00000000..d6c05c84 --- /dev/null +++ b/tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.purs @@ -0,0 +1,12 @@ +module Main where + +import Data.Functor.Contravariant (class Contravariant) + +data Predicate a = Predicate (a -> Boolean) +derive instance Contravariant Predicate + +data Comparison a = Comparison (a -> a -> Boolean) +derive instance Contravariant Comparison + +data Op a b = Op (b -> a) +derive instance Contravariant (Op a) diff --git a/tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.snap b/tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.snap new file mode 100644 index 00000000..e0480070 --- /dev/null +++ b/tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.snap @@ -0,0 +1,32 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Predicate :: forall (a :: Type). (a -> Boolean) -> Predicate a +Comparison :: forall (a :: Type). (a -> a -> Boolean) -> Comparison a +Op :: forall (a :: Type) (b :: Type). (b -> a) -> Op a b + +Types +Predicate :: Type -> Type +Comparison :: Type -> Type +Op :: Type -> Type -> Type + +Data +Predicate + Quantified = :0 + Kind = :0 + +Comparison + Quantified = :0 + Kind = :0 + +Op + Quantified = :0 + Kind = :0 + + +Derived +derive Contravariant (Predicate :: Type -> Type) +derive Contravariant (Comparison :: Type -> Type) +derive Contravariant (Op &0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/153_derive_contravariant_error/Data.Functor.Contravariant.purs b/tests-integration/fixtures/checking/153_derive_contravariant_error/Data.Functor.Contravariant.purs new file mode 100644 index 00000000..61246c10 --- /dev/null +++ b/tests-integration/fixtures/checking/153_derive_contravariant_error/Data.Functor.Contravariant.purs @@ -0,0 +1,4 @@ +module Data.Functor.Contravariant where + +class Contravariant f where + cmap :: forall a b. (b -> a) -> f a -> f b diff --git a/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.purs b/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.purs new file mode 100644 index 00000000..dbfa26fd --- /dev/null +++ b/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.purs @@ -0,0 +1,11 @@ +module Main where + +import Data.Functor.Contravariant (class Contravariant) + +-- Should fail: a appears covariantly (directly in constructor) +data Identity a = Identity a +derive instance Contravariant Identity + +-- Should fail: a appears covariantly (in result of function) +data Producer a = Producer (Int -> a) +derive instance Contravariant Producer diff --git a/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap b/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap new file mode 100644 index 00000000..5c095781 --- /dev/null +++ b/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap @@ -0,0 +1,29 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Identity :: forall (a :: Type). a -> Identity a +Producer :: forall (a :: Type). (Int -> a) -> Producer a + +Types +Identity :: Type -> Type +Producer :: Type -> Type + +Data +Identity + Quantified = :0 + Kind = :0 + +Producer + Quantified = :0 + Kind = :0 + + +Derived +derive Contravariant (Identity :: Type -> Type) +derive Contravariant (Producer :: Type -> Type) + +Errors +CovariantOccurrence { type_id: Id(19) } at [TermDeclaration(Idx::(1))] +CovariantOccurrence { type_id: Id(19) } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/154_derive_profunctor_simple/Data.Profunctor.purs b/tests-integration/fixtures/checking/154_derive_profunctor_simple/Data.Profunctor.purs new file mode 100644 index 00000000..4eaf5dcf --- /dev/null +++ b/tests-integration/fixtures/checking/154_derive_profunctor_simple/Data.Profunctor.purs @@ -0,0 +1,4 @@ +module Data.Profunctor where + +class Profunctor p where + dimap :: forall a b c d. (a -> b) -> (c -> d) -> p b c -> p a d diff --git a/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.purs b/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.purs new file mode 100644 index 00000000..0b1d65bf --- /dev/null +++ b/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.purs @@ -0,0 +1,15 @@ +module Main where + +import Data.Profunctor (class Profunctor) + +-- Simple function wrapper: a is contravariant, b is covariant +data Fn a b = Fn (a -> b) +derive instance Profunctor Fn + +-- Const-like for second param: a is contravariant (consumed), b is phantom +data ConstR r a b = ConstR (a -> r) +derive instance Profunctor (ConstR r) + +-- Multiple constructors +data Choice a b = GoLeft (a -> Int) | GoRight b +derive instance Profunctor Choice diff --git a/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap b/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap new file mode 100644 index 00000000..41072f02 --- /dev/null +++ b/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap @@ -0,0 +1,33 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Fn :: forall (a :: Type) (b :: Type). (a -> b) -> Fn a b +ConstR :: forall (t4 :: Type) (r :: Type) (a :: Type) (b :: t4). (a -> r) -> ConstR @t4 r a b +GoLeft :: forall (a :: Type) (b :: Type). (a -> Int) -> Choice a b +GoRight :: forall (a :: Type) (b :: Type). b -> Choice a b + +Types +Fn :: Type -> Type -> Type +ConstR :: forall (t4 :: Type). Type -> Type -> t4 -> Type +Choice :: Type -> Type -> Type + +Data +Fn + Quantified = :0 + Kind = :0 + +ConstR + Quantified = :1 + Kind = :0 + +Choice + Quantified = :0 + Kind = :0 + + +Derived +derive Profunctor (Fn :: Type -> Type -> Type) +derive Profunctor (ConstR @&0 &1 :: Type -> &0 -> Type) +derive Profunctor (Choice :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/155_derive_profunctor_error/Data.Profunctor.purs b/tests-integration/fixtures/checking/155_derive_profunctor_error/Data.Profunctor.purs new file mode 100644 index 00000000..4eaf5dcf --- /dev/null +++ b/tests-integration/fixtures/checking/155_derive_profunctor_error/Data.Profunctor.purs @@ -0,0 +1,4 @@ +module Data.Profunctor where + +class Profunctor p where + dimap :: forall a b c d. (a -> b) -> (c -> d) -> p b c -> p a d diff --git a/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.purs b/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.purs new file mode 100644 index 00000000..58141555 --- /dev/null +++ b/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.purs @@ -0,0 +1,11 @@ +module Main where + +import Data.Profunctor (class Profunctor) + +-- Should fail: first param (a) appears covariantly +data WrongFirst a b = WrongFirst a b +derive instance Profunctor WrongFirst + +-- Should fail: second param (b) appears contravariantly +data WrongSecond a b = WrongSecond (b -> a) +derive instance Profunctor WrongSecond diff --git a/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap b/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap new file mode 100644 index 00000000..b5b0ddad --- /dev/null +++ b/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap @@ -0,0 +1,30 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +WrongFirst :: forall (a :: Type) (b :: Type). a -> b -> WrongFirst a b +WrongSecond :: forall (a :: Type) (b :: Type). (b -> a) -> WrongSecond a b + +Types +WrongFirst :: Type -> Type -> Type +WrongSecond :: Type -> Type -> Type + +Data +WrongFirst + Quantified = :0 + Kind = :0 + +WrongSecond + Quantified = :0 + Kind = :0 + + +Derived +derive Profunctor (WrongFirst :: Type -> Type -> Type) +derive Profunctor (WrongSecond :: Type -> Type -> Type) + +Errors +CovariantOccurrence { type_id: Id(24) } at [TermDeclaration(Idx::(1))] +ContravariantOccurrence { type_id: Id(25) } at [TermDeclaration(Idx::(3))] +CovariantOccurrence { type_id: Id(24) } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 23d4f5f1..8fd3cecf 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -313,3 +313,11 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_150_derive_bifunctor_higher_kinded_main() { run_test("150_derive_bifunctor_higher_kinded", "Main"); } #[rustfmt::skip] #[test] fn test_151_derive_bifunctor_missing_functor_main() { run_test("151_derive_bifunctor_missing_functor", "Main"); } + +#[rustfmt::skip] #[test] fn test_152_derive_contravariant_simple_main() { run_test("152_derive_contravariant_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_153_derive_contravariant_error_main() { run_test("153_derive_contravariant_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_154_derive_profunctor_simple_main() { run_test("154_derive_profunctor_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_155_derive_profunctor_error_main() { run_test("155_derive_profunctor_error", "Main"); } From 395a10b3f3a7796a3347b92d575aa64aac761eaf Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 19:01:16 +0800 Subject: [PATCH 21/62] Use an enum for DerivedSkolems --- .../checking/src/algorithm/derive/functor.rs | 113 ++++++++++-------- .../Data.Bifunctor.purs | 4 + .../Main.purs | 6 + .../Main.snap | 21 ++++ .../Data.Functor.purs | 4 + .../Main.purs | 9 ++ .../Main.snap | 29 +++++ tests-integration/tests/checking/generated.rs | 4 + 8 files changed, 143 insertions(+), 47 deletions(-) create mode 100644 tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Data.Bifunctor.purs create mode 100644 tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.purs create mode 100644 tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap create mode 100644 tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Data.Functor.purs create mode 100644 tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.purs create mode 100644 tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap diff --git a/compiler-core/checking/src/algorithm/derive/functor.rs b/compiler-core/checking/src/algorithm/derive/functor.rs index 30cc3668..cde626a9 100644 --- a/compiler-core/checking/src/algorithm/derive/functor.rs +++ b/compiler-core/checking/src/algorithm/derive/functor.rs @@ -8,7 +8,6 @@ use building_types::QueryResult; use files::FileId; use indexing::TypeItemId; -use itertools::Itertools; use crate::ExternalQueries; use crate::algorithm::derive::{extract_type_arguments, tools}; @@ -36,29 +35,45 @@ impl Variance { /// A derived type parameter with its expected variance and wrapper class. #[derive(Clone, Copy)] -struct DerivedParam { +struct DerivedParameter { level: debruijn::Level, /// Expected variance for this parameter. expected: Variance, /// The class to emit when this parameter appears wrapped in a type application. /// For Functor/Bifunctor this is Functor, for Contravariant/Profunctor's first /// param this is Contravariant. - wrapper_class: Option<(FileId, TypeItemId)>, + class: Option<(FileId, TypeItemId)>, +} + +impl DerivedParameter { + fn new(level: debruijn::Level, (expected, class): ParameterConfig) -> DerivedParameter { + DerivedParameter { level, expected, class } + } } /// Tracks the Skolem variables representing derived type parameters. -struct DerivedSkolems { - /// Parameters being mapped over with their variance requirements. - /// - `Functor`: one param (Covariant, Functor) - /// - `Contravariant`: one param (Contravariant, Contravariant) - /// - `Bifunctor`: two params (Covariant, Functor), (Covariant, Functor) - /// - `Profunctor`: two params (Contravariant, Contravariant), (Covariant, Functor) - params: Vec, +/// +/// - `Invalid`: Insufficient type parameters for derivation +/// - `Single`: Functor (covariant) or Contravariant (contravariant) +/// - `Pair`: Bifunctor (both covariant) or Profunctor (contra, covariant) +enum DerivedSkolems { + Invalid, + Single(DerivedParameter), + Pair(DerivedParameter, DerivedParameter), } impl DerivedSkolems { - fn get(&self, level: debruijn::Level) -> Option<&DerivedParam> { - self.params.iter().find(|p| p.level == level) + fn get(&self, level: debruijn::Level) -> Option<&DerivedParameter> { + self.iter().find(|p| p.level == level) + } + + fn iter(&self) -> impl Iterator { + let (first, second) = match self { + DerivedSkolems::Invalid => (None, None), + DerivedSkolems::Single(a) => (Some(a), None), + DerivedSkolems::Pair(a, b) => (Some(a), Some(b)), + }; + first.into_iter().chain(second) } } @@ -91,7 +106,7 @@ where tools::push_given_constraints(state, &input.constraints); tools::register_derived_instance(state, context, input); - let config = VarianceConfig::new([(Variance::Covariant, functor)]); + let config = VarianceConfig::Single((Variance::Covariant, functor)); generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; tools::solve_and_report_constraints(state, context) @@ -128,7 +143,7 @@ where tools::register_derived_instance(state, context, input); let config = - VarianceConfig::new([(Variance::Covariant, functor), (Variance::Covariant, functor)]); + VarianceConfig::Pair((Variance::Covariant, functor), (Variance::Covariant, functor)); generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; @@ -164,7 +179,7 @@ where tools::push_given_constraints(state, &input.constraints); tools::register_derived_instance(state, context, input); - let config = VarianceConfig::new([(Variance::Contravariant, contravariant)]); + let config = VarianceConfig::Single((Variance::Contravariant, contravariant)); generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; tools::solve_and_report_constraints(state, context) @@ -201,10 +216,10 @@ where tools::push_given_constraints(state, &input.constraints); tools::register_derived_instance(state, context, input); - let config = VarianceConfig::new([ + let config = VarianceConfig::Pair( (Variance::Contravariant, contravariant), (Variance::Covariant, functor), - ]); + ); generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; @@ -215,16 +230,9 @@ where type ParameterConfig = (Variance, Option<(FileId, TypeItemId)>); /// Configuration for variance-aware derivation. -struct VarianceConfig { - /// Expected variance and wrapper class for each derived parameter. - /// Ordered from first to last (e.g., for `data T a b`, index 0 is `a`). - params: Vec, -} - -impl VarianceConfig { - fn new(params: impl IntoIterator) -> Self { - Self { params: params.into_iter().collect() } - } +enum VarianceConfig { + Single(ParameterConfig), + Pair(ParameterConfig, ParameterConfig), } /// Generates variance-aware constraints for Functor-like derivation. @@ -244,10 +252,13 @@ where for constructor_id in constructors { let constructor_type = super::lookup_local_term_type(state, context, data_file, constructor_id)?; - let Some(constructor_type) = constructor_type else { continue }; + + let Some(constructor_type) = constructor_type else { + continue; + }; let (fields, skolems) = - extract_fields_with_skolems(state, constructor_type, derived_type, &config); + extract_fields_with_skolems(state, context, constructor_type, derived_type, &config); for field_type in fields { check_variance_field(state, context, field_type, Variance::Covariant, &skolems); @@ -261,12 +272,16 @@ where /// /// Uses the variance configuration to assign expected variance and wrapper class /// to each derived parameter's Skolem variable. -fn extract_fields_with_skolems( +fn extract_fields_with_skolems( state: &mut CheckState, + context: &CheckContext, constructor_type: TypeId, derived_type: TypeId, config: &VarianceConfig, -) -> (Vec, DerivedSkolems) { +) -> (Vec, DerivedSkolems) +where + Q: ExternalQueries, +{ let type_arguments = extract_type_arguments(state, derived_type); let mut arguments_iter = type_arguments.into_iter(); let mut current_id = constructor_type; @@ -293,15 +308,20 @@ fn extract_fields_with_skolems( } // The last N levels correspond to the N derived parameters. - // Take last N via rev().take(N), then restore original order. - let param_count = config.params.len(); - let derived_levels: Vec<_> = levels.into_iter().rev().take(param_count).collect(); - let params = derived_levels - .into_iter() - .rev() - .zip(config.params.iter()) - .map(|(level, &(expected, wrapper_class))| DerivedParam { level, expected, wrapper_class }) - .collect_vec(); + let skolems = match (config, &levels[..]) { + (VarianceConfig::Single(config), [.., a]) => { + DerivedSkolems::Single(DerivedParameter::new(*a, *config)) + } + (VarianceConfig::Pair(a_config, b_config), [.., a, b]) => DerivedSkolems::Pair( + DerivedParameter::new(*a, *a_config), + DerivedParameter::new(*b, *b_config), + ), + _ => { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + DerivedSkolems::Invalid + } + }; let mut fields = vec![]; safe_loop! { @@ -315,7 +335,7 @@ fn extract_fields_with_skolems( } } - (fields, DerivedSkolems { params }) + (fields, skolems) } /// Checks a field type for variance violations and emits wrapper class constraints. @@ -355,10 +375,9 @@ fn check_variance_field( if function == context.prim.record { check_variance_field(state, context, argument, variance, skolems); } else { - // Check each derived parameter that appears in the argument - for param in &skolems.params { - if contains_skolem_level(state, argument, param.level) { - if variance != param.expected { + for parameter in skolems.iter() { + if contains_skolem_level(state, argument, parameter.level) { + if variance != parameter.expected { let global = transfer::globalize(state, context, type_id); if variance == Variance::Covariant { state.insert_error(ErrorKind::CovariantOccurrence { @@ -369,8 +388,8 @@ fn check_variance_field( type_id: global, }); } - } else if let Some(wrapper_class) = param.wrapper_class { - tools::emit_constraint(state, wrapper_class, function); + } else if let Some(class) = parameter.class { + tools::emit_constraint(state, class, function); } else { state.insert_error(ErrorKind::DeriveMissingFunctor); } diff --git a/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Data.Bifunctor.purs b/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Data.Bifunctor.purs new file mode 100644 index 00000000..54b5e06b --- /dev/null +++ b/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Data.Bifunctor.purs @@ -0,0 +1,4 @@ +module Data.Bifunctor where + +class Bifunctor f where + bimap :: forall a b c d. (a -> b) -> (c -> d) -> f a c -> f b d diff --git a/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.purs b/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.purs new file mode 100644 index 00000000..9a1af2ee --- /dev/null +++ b/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.purs @@ -0,0 +1,6 @@ +module Main where + +import Data.Bifunctor (class Bifunctor) + +data Triple a b c = Triple a b c +derive instance Bifunctor (Triple Int String) diff --git a/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap b/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap new file mode 100644 index 00000000..a195b630 --- /dev/null +++ b/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Triple :: forall (a :: Type) (b :: Type) (c :: Type). a -> b -> c -> Triple a b c + +Types +Triple :: Type -> Type -> Type -> Type + +Data +Triple + Quantified = :0 + Kind = :0 + + +Derived +derive Bifunctor (Triple Int String :: Type -> Type) + +Errors +CannotDeriveForType { type_id: Id(25) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Data.Functor.purs b/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Data.Functor.purs new file mode 100644 index 00000000..354b844e --- /dev/null +++ b/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Data.Functor.purs @@ -0,0 +1,4 @@ +module Data.Functor where + +class Functor f where + map :: forall a b. (a -> b) -> f a -> f b diff --git a/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.purs b/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.purs new file mode 100644 index 00000000..b5309b6b --- /dev/null +++ b/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.purs @@ -0,0 +1,9 @@ +module Main where + +import Data.Functor (class Functor) + +data Pair a b = Pair a b +derive instance Functor (Pair Int String) + +data Unit = Unit +derive instance Functor Unit diff --git a/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap b/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap new file mode 100644 index 00000000..f3f341b3 --- /dev/null +++ b/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap @@ -0,0 +1,29 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Pair :: forall (a :: Type) (b :: Type). a -> b -> Pair a b +Unit :: Unit + +Types +Pair :: Type -> Type -> Type +Unit :: Type + +Data +Pair + Quantified = :0 + Kind = :0 + +Unit + Quantified = :0 + Kind = :0 + + +Derived +derive Functor (Pair Int String :: Type) +derive Functor (Unit :: Type) + +Errors +CannotDeriveForType { type_id: Id(21) } at [TermDeclaration(Idx::(1))] +CannotDeriveForType { type_id: Id(17) } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 8fd3cecf..0e030f10 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -321,3 +321,7 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_154_derive_profunctor_simple_main() { run_test("154_derive_profunctor_simple", "Main"); } #[rustfmt::skip] #[test] fn test_155_derive_profunctor_error_main() { run_test("155_derive_profunctor_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_156_derive_bifunctor_insufficient_params_main() { run_test("156_derive_bifunctor_insufficient_params", "Main"); } + +#[rustfmt::skip] #[test] fn test_157_derive_functor_insufficient_params_main() { run_test("157_derive_functor_insufficient_params", "Main"); } From 11a4e66f44e8b2c089dbb551c4678b5411794706 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 19:07:11 +0800 Subject: [PATCH 22/62] Perform kind checking on 'derive instance' --- compiler-core/checking/src/algorithm/derive.rs | 18 +++++++++++++++--- .../checking/src/algorithm/term_item.rs | 2 +- .../146_derive_functor_simple/Main.snap | 2 +- .../147_derive_functor_higher_kinded/Main.snap | 2 +- .../Main.snap | 2 +- .../149_derive_bifunctor_simple/Main.snap | 2 +- .../Main.snap | 2 +- .../Main.snap | 2 +- .../153_derive_contravariant_error/Main.snap | 4 ++-- .../154_derive_profunctor_simple/Main.snap | 2 +- .../155_derive_profunctor_error/Main.snap | 6 +++--- .../Main.snap | 3 ++- .../Main.snap | 4 +++- 13 files changed, 33 insertions(+), 18 deletions(-) diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index cd4eae79..2f3a6f09 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -11,7 +11,7 @@ use indexing::{DeriveId, TermItemId, TypeItemId}; use crate::ExternalQueries; use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::{kind, substitute, transfer}; +use crate::algorithm::{kind, substitute, term_item, transfer}; use crate::core::{Type, TypeId, Variable, debruijn}; use crate::error::{ErrorKind, ErrorStep}; @@ -49,10 +49,22 @@ where // Save the current size of the environment for unbinding. let size = state.type_scope.size(); + let class_kind = kind::lookup_file_type(state, context, class_file, class_id)?; + let expected_kinds = term_item::instantiate_class_kind(state, context, class_kind)?; + + if expected_kinds.len() != arguments.len() { + state.insert_error(ErrorKind::InstanceHeadMismatch { + class_file, + class_item: class_id, + expected: expected_kinds.len(), + actual: arguments.len(), + }); + } + let mut core_arguments = vec![]; - for argument in arguments.iter() { + for (argument, expected_kind) in arguments.iter().zip(expected_kinds) { let (inferred_type, inferred_kind) = - kind::infer_surface_kind(state, context, *argument)?; + kind::check_surface_kind(state, context, *argument, expected_kind)?; core_arguments.push((inferred_type, inferred_kind)); } diff --git a/compiler-core/checking/src/algorithm/term_item.rs b/compiler-core/checking/src/algorithm/term_item.rs index 3b74be92..c2303545 100644 --- a/compiler-core/checking/src/algorithm/term_item.rs +++ b/compiler-core/checking/src/algorithm/term_item.rs @@ -194,7 +194,7 @@ where /// /// This function instantiates kind variables as unification variables and /// extracts the argument kinds to check them against instance arguments. -fn instantiate_class_kind( +pub fn instantiate_class_kind( state: &mut CheckState, context: &CheckContext, class_kind: TypeId, diff --git a/tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap b/tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap index 6abf739c..16403041 100644 --- a/tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap +++ b/tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap @@ -29,5 +29,5 @@ Maybe Derived derive Functor (Identity :: Type -> Type) -derive Functor (Const @&0 &1 :: &0 -> Type) +derive Functor (Const @Type &0 :: Type -> Type) derive Functor (Maybe :: Type -> Type) diff --git a/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap b/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap index a85da0e3..9d6c4842 100644 --- a/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap @@ -22,7 +22,7 @@ WrapNoFunctor Derived derive Functor &0 => Functor (Wrap @Type &0 :: Type -> Type) -derive Functor (WrapNoFunctor @&0 &1 :: &0 -> Type) +derive Functor (WrapNoFunctor @Type &0 :: Type -> Type) Errors NoInstanceFound { Functor &0 } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap b/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap index fa6173a0..f8dad8ed 100644 --- a/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap +++ b/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap @@ -32,4 +32,4 @@ derive Functor (Reader &0 :: Type -> Type) derive Functor (Cont &0 :: Type -> Type) Errors -ContravariantOccurrence { type_id: Id(31) } at [TermDeclaration(Idx::(1))] +ContravariantOccurrence { type_id: Id(44) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap b/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap index b2576844..2a082a21 100644 --- a/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap +++ b/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap @@ -31,4 +31,4 @@ Const2 Derived derive Bifunctor (Either :: Type -> Type -> Type) derive Bifunctor (Pair :: Type -> Type -> Type) -derive Bifunctor (Const2 @&0 @&1 &2 :: &0 -> &1 -> Type) +derive Bifunctor (Const2 @Type @Type &0 :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap index bac235fc..a89e53e8 100644 --- a/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap @@ -27,7 +27,7 @@ WrapBothNoConstraint Derived derive (Functor &0, Functor &1) => Bifunctor (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) -derive Bifunctor (WrapBothNoConstraint @&0 @&1 &2 &3 :: &0 -> &1 -> Type) +derive Bifunctor (WrapBothNoConstraint @Type @Type &0 &1 :: Type -> Type -> Type) Errors NoInstanceFound { Functor &0 } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap index 3526e92e..cbaef58f 100644 --- a/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap +++ b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap @@ -17,7 +17,7 @@ WrapBoth Derived -derive Bifunctor (WrapBoth @&0 @&1 &2 &3 :: &0 -> &1 -> Type) +derive Bifunctor (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) Errors DeriveMissingFunctor at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap b/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap index 5c095781..bf9a55ad 100644 --- a/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap +++ b/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap @@ -25,5 +25,5 @@ derive Contravariant (Identity :: Type -> Type) derive Contravariant (Producer :: Type -> Type) Errors -CovariantOccurrence { type_id: Id(19) } at [TermDeclaration(Idx::(1))] -CovariantOccurrence { type_id: Id(19) } at [TermDeclaration(Idx::(3))] +CovariantOccurrence { type_id: Id(33) } at [TermDeclaration(Idx::(1))] +CovariantOccurrence { type_id: Id(33) } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap b/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap index 41072f02..59aa56f9 100644 --- a/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap +++ b/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap @@ -29,5 +29,5 @@ Choice Derived derive Profunctor (Fn :: Type -> Type -> Type) -derive Profunctor (ConstR @&0 &1 :: Type -> &0 -> Type) +derive Profunctor (ConstR @Type &0 :: Type -> Type -> Type) derive Profunctor (Choice :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap b/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap index b5b0ddad..3b4ea232 100644 --- a/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap +++ b/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap @@ -25,6 +25,6 @@ derive Profunctor (WrongFirst :: Type -> Type -> Type) derive Profunctor (WrongSecond :: Type -> Type -> Type) Errors -CovariantOccurrence { type_id: Id(24) } at [TermDeclaration(Idx::(1))] -ContravariantOccurrence { type_id: Id(25) } at [TermDeclaration(Idx::(3))] -CovariantOccurrence { type_id: Id(24) } at [TermDeclaration(Idx::(3))] +CovariantOccurrence { type_id: Id(45) } at [TermDeclaration(Idx::(1))] +ContravariantOccurrence { type_id: Id(46) } at [TermDeclaration(Idx::(3))] +CovariantOccurrence { type_id: Id(45) } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap b/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap index a195b630..9d229420 100644 --- a/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap +++ b/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap @@ -18,4 +18,5 @@ Derived derive Bifunctor (Triple Int String :: Type -> Type) Errors -CannotDeriveForType { type_id: Id(25) } at [TermDeclaration(Idx::(1))] +CannotUnify { Type, Type -> Type } at [TermDeclaration(Idx::(1)), CheckingKind(AstId(21))] +CannotDeriveForType { type_id: Id(45) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap b/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap index f3f341b3..28391ae8 100644 --- a/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap +++ b/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap @@ -25,5 +25,7 @@ derive Functor (Pair Int String :: Type) derive Functor (Unit :: Type) Errors -CannotDeriveForType { type_id: Id(21) } at [TermDeclaration(Idx::(1))] +CannotUnify { Type, Type -> Type } at [TermDeclaration(Idx::(1)), CheckingKind(AstId(19))] +CannotDeriveForType { type_id: Id(34) } at [TermDeclaration(Idx::(1))] +CannotUnify { Type, Type -> Type } at [TermDeclaration(Idx::(3)), CheckingKind(AstId(28))] CannotDeriveForType { type_id: Id(17) } at [TermDeclaration(Idx::(3))] From 2c85a663a66faf88a06f86ffde2ab789697dbc82 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 19:08:09 +0800 Subject: [PATCH 23/62] Fix clippy --- .../checking/src/algorithm/derive/functor.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler-core/checking/src/algorithm/derive/functor.rs b/compiler-core/checking/src/algorithm/derive/functor.rs index cde626a9..50afd81c 100644 --- a/compiler-core/checking/src/algorithm/derive/functor.rs +++ b/compiler-core/checking/src/algorithm/derive/functor.rs @@ -352,14 +352,14 @@ fn check_variance_field( match state.storage[type_id].clone() { Type::Variable(Variable::Skolem(level, _)) => { - if let Some(param) = skolems.get(level) { - if variance != param.expected { - let global = transfer::globalize(state, context, type_id); - if variance == Variance::Covariant { - state.insert_error(ErrorKind::CovariantOccurrence { type_id: global }); - } else { - state.insert_error(ErrorKind::ContravariantOccurrence { type_id: global }); - } + if let Some(parameter) = skolems.get(level) + && variance != parameter.expected + { + let global = transfer::globalize(state, context, type_id); + if variance == Variance::Covariant { + state.insert_error(ErrorKind::CovariantOccurrence { type_id: global }); + } else { + state.insert_error(ErrorKind::ContravariantOccurrence { type_id: global }); } } } @@ -432,7 +432,7 @@ fn contains_skolem_level(state: &mut CheckState, type_id: TypeId, target: debrui Type::Row(RowType { ref fields, tail }) => { fields.iter().any(|f| contains_skolem_level(state, f.id, target)) - || tail.map_or(false, |t| contains_skolem_level(state, t, target)) + || tail.is_some_and(|t| contains_skolem_level(state, t, target)) } Type::Forall(_, inner) | Type::Constrained(_, inner) | Type::Kinded(inner, _) => { From daf85d8e10c90a5b8d913d0ed142e06db29542d2 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 19:44:18 +0800 Subject: [PATCH 24/62] Initial derive for Foldable and Bifoldable --- .../checking/src/algorithm/derive.rs | 31 +- .../src/algorithm/derive/contravariant.rs | 86 ++++ .../checking/src/algorithm/derive/foldable.rs | 83 ++++ .../checking/src/algorithm/derive/functor.rs | 369 +----------------- .../checking/src/algorithm/derive/variance.rs | 291 ++++++++++++++ compiler-core/checking/src/algorithm/state.rs | 17 +- .../Data.Foldable.purs | 5 + .../158_derive_foldable_simple/Main.purs | 12 + .../158_derive_foldable_simple/Main.snap | 33 ++ .../Data.Foldable.purs | 5 + .../Main.purs | 9 + .../Main.snap | 28 ++ .../Data.Bifoldable.purs | 5 + .../Data.Foldable.purs | 5 + .../160_derive_bifoldable_simple/Main.purs | 12 + .../160_derive_bifoldable_simple/Main.snap | 34 ++ .../Data.Bifoldable.purs | 5 + .../Data.Foldable.purs | 5 + .../Main.purs | 10 + .../Main.snap | 34 ++ tests-integration/tests/checking/generated.rs | 8 + 21 files changed, 706 insertions(+), 381 deletions(-) create mode 100644 compiler-core/checking/src/algorithm/derive/contravariant.rs create mode 100644 compiler-core/checking/src/algorithm/derive/foldable.rs create mode 100644 compiler-core/checking/src/algorithm/derive/variance.rs create mode 100644 tests-integration/fixtures/checking/158_derive_foldable_simple/Data.Foldable.purs create mode 100644 tests-integration/fixtures/checking/158_derive_foldable_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/158_derive_foldable_simple/Main.snap create mode 100644 tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Data.Foldable.purs create mode 100644 tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.purs create mode 100644 tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.snap create mode 100644 tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Bifoldable.purs create mode 100644 tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Foldable.purs create mode 100644 tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.snap create mode 100644 tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Bifoldable.purs create mode 100644 tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Foldable.purs create mode 100644 tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.purs create mode 100644 tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.snap diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index 2f3a6f09..a96417db 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -1,8 +1,11 @@ //! Implements type class deriving for PureScript. +mod contravariant; +mod foldable; mod functor; mod higher_kinded; mod tools; +mod variance; use building_types::QueryResult; use files::FileId; @@ -86,25 +89,23 @@ where if is_newtype { check_newtype_derive(state, context, elaborated)?; } else { - let class = (class_file, class_id); + let class_is = |known| Some((class_file, class_id)) == known; + let known_types = &context.known_types; - let is_eq = context.known_types.eq == Some(class); - let is_ord = context.known_types.ord == Some(class); - let is_functor = context.known_types.functor == Some(class); - let is_bifunctor = context.known_types.bifunctor == Some(class); - let is_contravariant = context.known_types.contravariant == Some(class); - let is_profunctor = context.known_types.profunctor == Some(class); - - if is_eq || is_ord { + if class_is(known_types.eq) || class_is(known_types.ord) { check_derive_class(state, context, elaborated)?; - } else if is_functor { + } else if class_is(known_types.functor) { functor::check_derive_functor(state, context, elaborated)?; - } else if is_bifunctor { + } else if class_is(known_types.bifunctor) { functor::check_derive_bifunctor(state, context, elaborated)?; - } else if is_contravariant { - functor::check_derive_contravariant(state, context, elaborated)?; - } else if is_profunctor { - functor::check_derive_profunctor(state, context, elaborated)?; + } else if class_is(known_types.contravariant) { + contravariant::check_derive_contravariant(state, context, elaborated)?; + } else if class_is(known_types.profunctor) { + contravariant::check_derive_profunctor(state, context, elaborated)?; + } else if class_is(known_types.foldable) { + foldable::check_derive_foldable(state, context, elaborated)?; + } else if class_is(known_types.bifoldable) { + foldable::check_derive_bifoldable(state, context, elaborated)?; } else { state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); }; diff --git a/compiler-core/checking/src/algorithm/derive/contravariant.rs b/compiler-core/checking/src/algorithm/derive/contravariant.rs new file mode 100644 index 00000000..da54dc6b --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/contravariant.rs @@ -0,0 +1,86 @@ +//! Implements derive for Contravariant and Profunctor. + +use building_types::QueryResult; + +use super::variance::{Variance, VarianceConfig, generate_variance_constraints}; +use super::tools; +use crate::ExternalQueries; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::transfer; +use crate::error::ErrorKind; + +/// Checks a derive instance for Contravariant. +pub fn check_derive_contravariant( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 1, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + let contravariant = Some((input.class_file, input.class_id)); + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); + + let config = VarianceConfig::Single((Variance::Contravariant, contravariant)); + generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; + + tools::solve_and_report_constraints(state, context) +} + +/// Checks a derive instance for Profunctor. +pub fn check_derive_profunctor( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 1, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + // Profunctor: first param is contravariant, second is covariant. + let contravariant = context.known_types.contravariant; + let functor = context.known_types.functor; + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); + + let config = VarianceConfig::Pair( + (Variance::Contravariant, contravariant), + (Variance::Covariant, functor), + ); + + generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; + + tools::solve_and_report_constraints(state, context) +} diff --git a/compiler-core/checking/src/algorithm/derive/foldable.rs b/compiler-core/checking/src/algorithm/derive/foldable.rs new file mode 100644 index 00000000..6b5a6c5d --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/foldable.rs @@ -0,0 +1,83 @@ +//! Implements derive for Foldable and Bifoldable. + +use building_types::QueryResult; + +use super::variance::{Variance, VarianceConfig, generate_variance_constraints}; +use super::tools; +use crate::ExternalQueries; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::transfer; +use crate::error::ErrorKind; + +/// Checks a derive instance for Foldable. +pub fn check_derive_foldable( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 1, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + let foldable = Some((input.class_file, input.class_id)); + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); + + let config = VarianceConfig::Single((Variance::Covariant, foldable)); + generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; + + tools::solve_and_report_constraints(state, context) +} + +/// Checks a derive instance for Bifoldable. +pub fn check_derive_bifoldable( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 1, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + // Bifoldable derivation emits Foldable constraints for wrapped parameters. + let foldable = context.known_types.foldable; + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); + + let config = + VarianceConfig::Pair((Variance::Covariant, foldable), (Variance::Covariant, foldable)); + + generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; + + tools::solve_and_report_constraints(state, context) +} diff --git a/compiler-core/checking/src/algorithm/derive/functor.rs b/compiler-core/checking/src/algorithm/derive/functor.rs index 50afd81c..d7944ee2 100644 --- a/compiler-core/checking/src/algorithm/derive/functor.rs +++ b/compiler-core/checking/src/algorithm/derive/functor.rs @@ -1,82 +1,14 @@ -//! Implements derive for variance-aware type classes. -//! -//! This module handles derivation for Functor, Bifunctor, Contravariant, and -//! Profunctor. Unlike Eq/Ord which require constraints on all fields, these -//! classes are variance-aware: each derived parameter has an expected variance -//! (covariant or contravariant) that must be satisfied. +//! Implements derive for Functor and Bifunctor. use building_types::QueryResult; -use files::FileId; -use indexing::TypeItemId; +use super::variance::{Variance, VarianceConfig, generate_variance_constraints}; +use super::tools; use crate::ExternalQueries; -use crate::algorithm::derive::{extract_type_arguments, tools}; -use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::{substitute, transfer}; -use crate::core::{RowType, Type, TypeId, Variable, debruijn}; +use crate::algorithm::transfer; use crate::error::ErrorKind; -/// Variance of a type position. -#[derive(Clone, Copy, PartialEq, Eq)] -enum Variance { - Covariant, - Contravariant, -} - -impl Variance { - fn flip(self) -> Variance { - match self { - Variance::Covariant => Variance::Contravariant, - Variance::Contravariant => Variance::Covariant, - } - } -} - -/// A derived type parameter with its expected variance and wrapper class. -#[derive(Clone, Copy)] -struct DerivedParameter { - level: debruijn::Level, - /// Expected variance for this parameter. - expected: Variance, - /// The class to emit when this parameter appears wrapped in a type application. - /// For Functor/Bifunctor this is Functor, for Contravariant/Profunctor's first - /// param this is Contravariant. - class: Option<(FileId, TypeItemId)>, -} - -impl DerivedParameter { - fn new(level: debruijn::Level, (expected, class): ParameterConfig) -> DerivedParameter { - DerivedParameter { level, expected, class } - } -} - -/// Tracks the Skolem variables representing derived type parameters. -/// -/// - `Invalid`: Insufficient type parameters for derivation -/// - `Single`: Functor (covariant) or Contravariant (contravariant) -/// - `Pair`: Bifunctor (both covariant) or Profunctor (contra, covariant) -enum DerivedSkolems { - Invalid, - Single(DerivedParameter), - Pair(DerivedParameter, DerivedParameter), -} - -impl DerivedSkolems { - fn get(&self, level: debruijn::Level) -> Option<&DerivedParameter> { - self.iter().find(|p| p.level == level) - } - - fn iter(&self) -> impl Iterator { - let (first, second) = match self { - DerivedSkolems::Invalid => (None, None), - DerivedSkolems::Single(a) => (Some(a), None), - DerivedSkolems::Pair(a, b) => (Some(a), Some(b)), - }; - first.into_iter().chain(second) - } -} - /// Checks a derive instance for Functor. pub fn check_derive_functor( state: &mut CheckState, @@ -149,296 +81,3 @@ where tools::solve_and_report_constraints(state, context) } - -/// Checks a derive instance for Contravariant. -pub fn check_derive_contravariant( - state: &mut CheckState, - context: &CheckContext, - input: tools::ElaboratedDerive, -) -> QueryResult<()> -where - Q: ExternalQueries, -{ - let [(derived_type, _)] = input.arguments[..] else { - state.insert_error(ErrorKind::DeriveInvalidArity { - class_file: input.class_file, - class_id: input.class_id, - expected: 1, - actual: input.arguments.len(), - }); - return Ok(()); - }; - - let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { - let global_type = transfer::globalize(state, context, derived_type); - state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); - return Ok(()); - }; - - let contravariant = Some((input.class_file, input.class_id)); - tools::push_given_constraints(state, &input.constraints); - tools::register_derived_instance(state, context, input); - - let config = VarianceConfig::Single((Variance::Contravariant, contravariant)); - generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; - - tools::solve_and_report_constraints(state, context) -} - -/// Checks a derive instance for Profunctor. -pub fn check_derive_profunctor( - state: &mut CheckState, - context: &CheckContext, - input: tools::ElaboratedDerive, -) -> QueryResult<()> -where - Q: ExternalQueries, -{ - let [(derived_type, _)] = input.arguments[..] else { - state.insert_error(ErrorKind::DeriveInvalidArity { - class_file: input.class_file, - class_id: input.class_id, - expected: 1, - actual: input.arguments.len(), - }); - return Ok(()); - }; - - let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { - let global_type = transfer::globalize(state, context, derived_type); - state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); - return Ok(()); - }; - - // Profunctor: first param is contravariant, second is covariant. - let contravariant = context.known_types.contravariant; - let functor = context.known_types.functor; - tools::push_given_constraints(state, &input.constraints); - tools::register_derived_instance(state, context, input); - - let config = VarianceConfig::Pair( - (Variance::Contravariant, contravariant), - (Variance::Covariant, functor), - ); - - generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; - - tools::solve_and_report_constraints(state, context) -} - -/// Expected variance and wrapper class for a derived parameter. -type ParameterConfig = (Variance, Option<(FileId, TypeItemId)>); - -/// Configuration for variance-aware derivation. -enum VarianceConfig { - Single(ParameterConfig), - Pair(ParameterConfig, ParameterConfig), -} - -/// Generates variance-aware constraints for Functor-like derivation. -fn generate_variance_constraints( - state: &mut CheckState, - context: &CheckContext, - data_file: FileId, - data_id: TypeItemId, - derived_type: TypeId, - config: VarianceConfig, -) -> QueryResult<()> -where - Q: ExternalQueries, -{ - let constructors = tools::lookup_data_constructors(context, data_file, data_id)?; - - for constructor_id in constructors { - let constructor_type = - super::lookup_local_term_type(state, context, data_file, constructor_id)?; - - let Some(constructor_type) = constructor_type else { - continue; - }; - - let (fields, skolems) = - extract_fields_with_skolems(state, context, constructor_type, derived_type, &config); - - for field_type in fields { - check_variance_field(state, context, field_type, Variance::Covariant, &skolems); - } - } - - Ok(()) -} - -/// Extracts constructor fields and tracks Skolem variables for unmapped parameters. -/// -/// Uses the variance configuration to assign expected variance and wrapper class -/// to each derived parameter's Skolem variable. -fn extract_fields_with_skolems( - state: &mut CheckState, - context: &CheckContext, - constructor_type: TypeId, - derived_type: TypeId, - config: &VarianceConfig, -) -> (Vec, DerivedSkolems) -where - Q: ExternalQueries, -{ - let type_arguments = extract_type_arguments(state, derived_type); - let mut arguments_iter = type_arguments.into_iter(); - let mut current_id = constructor_type; - let mut levels = vec![]; - - safe_loop! { - current_id = state.normalize_type(current_id); - match &state.storage[current_id] { - Type::Forall(binder, inner) => { - let binder_level = binder.level; - let binder_kind = binder.kind; - let inner = *inner; - - let argument_type = arguments_iter.next().unwrap_or_else(|| { - levels.push(binder_level); - let skolem = Variable::Skolem(binder_level, binder_kind); - state.storage.intern(Type::Variable(skolem)) - }); - - current_id = substitute::SubstituteBound::on(state, binder_level, argument_type, inner); - } - _ => break, - } - } - - // The last N levels correspond to the N derived parameters. - let skolems = match (config, &levels[..]) { - (VarianceConfig::Single(config), [.., a]) => { - DerivedSkolems::Single(DerivedParameter::new(*a, *config)) - } - (VarianceConfig::Pair(a_config, b_config), [.., a, b]) => DerivedSkolems::Pair( - DerivedParameter::new(*a, *a_config), - DerivedParameter::new(*b, *b_config), - ), - _ => { - let global_type = transfer::globalize(state, context, derived_type); - state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); - DerivedSkolems::Invalid - } - }; - - let mut fields = vec![]; - safe_loop! { - current_id = state.normalize_type(current_id); - match state.storage[current_id] { - Type::Function(argument, result) => { - fields.push(argument); - current_id = result; - } - _ => break, - } - } - - (fields, skolems) -} - -/// Checks a field type for variance violations and emits wrapper class constraints. -fn check_variance_field( - state: &mut CheckState, - context: &CheckContext, - type_id: TypeId, - variance: Variance, - skolems: &DerivedSkolems, -) where - Q: ExternalQueries, -{ - let type_id = state.normalize_type(type_id); - - match state.storage[type_id].clone() { - Type::Variable(Variable::Skolem(level, _)) => { - if let Some(parameter) = skolems.get(level) - && variance != parameter.expected - { - let global = transfer::globalize(state, context, type_id); - if variance == Variance::Covariant { - state.insert_error(ErrorKind::CovariantOccurrence { type_id: global }); - } else { - state.insert_error(ErrorKind::ContravariantOccurrence { type_id: global }); - } - } - } - - Type::Function(argument, result) => { - check_variance_field(state, context, argument, variance.flip(), skolems); - check_variance_field(state, context, result, variance, skolems); - } - - Type::Application(function, argument) => { - let function = state.normalize_type(function); - - if function == context.prim.record { - check_variance_field(state, context, argument, variance, skolems); - } else { - for parameter in skolems.iter() { - if contains_skolem_level(state, argument, parameter.level) { - if variance != parameter.expected { - let global = transfer::globalize(state, context, type_id); - if variance == Variance::Covariant { - state.insert_error(ErrorKind::CovariantOccurrence { - type_id: global, - }); - } else { - state.insert_error(ErrorKind::ContravariantOccurrence { - type_id: global, - }); - } - } else if let Some(class) = parameter.class { - tools::emit_constraint(state, class, function); - } else { - state.insert_error(ErrorKind::DeriveMissingFunctor); - } - } - } - check_variance_field(state, context, argument, variance, skolems); - } - } - - Type::Row(RowType { ref fields, .. }) => { - for field in fields.iter() { - check_variance_field(state, context, field.id, variance, skolems); - } - } - - Type::KindApplication(_, argument) => { - check_variance_field(state, context, argument, variance, skolems); - } - - _ => (), - } -} - -/// Checks if a type contains a specific Skolem level. -fn contains_skolem_level(state: &mut CheckState, type_id: TypeId, target: debruijn::Level) -> bool { - let type_id = state.normalize_type(type_id); - - match state.storage[type_id].clone() { - Type::Variable(Variable::Skolem(level, _)) => level == target, - - Type::Application(function, argument) | Type::KindApplication(function, argument) => { - contains_skolem_level(state, function, target) - || contains_skolem_level(state, argument, target) - } - - Type::Function(argument, result) => { - contains_skolem_level(state, argument, target) - || contains_skolem_level(state, result, target) - } - - Type::Row(RowType { ref fields, tail }) => { - fields.iter().any(|f| contains_skolem_level(state, f.id, target)) - || tail.is_some_and(|t| contains_skolem_level(state, t, target)) - } - - Type::Forall(_, inner) | Type::Constrained(_, inner) | Type::Kinded(inner, _) => { - contains_skolem_level(state, inner, target) - } - - _ => false, - } -} diff --git a/compiler-core/checking/src/algorithm/derive/variance.rs b/compiler-core/checking/src/algorithm/derive/variance.rs new file mode 100644 index 00000000..acce51e9 --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/variance.rs @@ -0,0 +1,291 @@ +//! Shared infrastructure for variance-aware type class derivation. +//! +//! This module provides the core types and functions used by Functor, Bifunctor, +//! Contravariant, Profunctor, Foldable, and Bifoldable derivation. + +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; + +use crate::ExternalQueries; +use crate::algorithm::derive::{extract_type_arguments, tools}; +use crate::algorithm::safety::safe_loop; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::{substitute, transfer}; +use crate::core::{RowType, Type, TypeId, Variable, debruijn}; +use crate::error::ErrorKind; + +/// Variance of a type position. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Variance { + Covariant, + Contravariant, +} + +impl Variance { + fn flip(self) -> Variance { + match self { + Variance::Covariant => Variance::Contravariant, + Variance::Contravariant => Variance::Covariant, + } + } +} + +/// A derived type parameter with its expected variance and wrapper class. +#[derive(Clone, Copy)] +struct DerivedParameter { + level: debruijn::Level, + /// Expected variance for this parameter. + expected: Variance, + /// The class to emit when this parameter appears wrapped in a type application. + class: Option<(FileId, TypeItemId)>, +} + +impl DerivedParameter { + fn new(level: debruijn::Level, (expected, class): ParameterConfig) -> DerivedParameter { + DerivedParameter { level, expected, class } + } +} + +/// Tracks the Skolem variables representing derived type parameters. +/// +/// - `Invalid`: Insufficient type parameters for derivation +/// - `Single`: Functor/Foldable (covariant) or Contravariant (contravariant) +/// - `Pair`: Bifunctor/Bifoldable (both covariant) or Profunctor (contra, covariant) +enum DerivedSkolems { + Invalid, + Single(DerivedParameter), + Pair(DerivedParameter, DerivedParameter), +} + +impl DerivedSkolems { + fn get(&self, level: debruijn::Level) -> Option<&DerivedParameter> { + self.iter().find(|p| p.level == level) + } + + fn iter(&self) -> impl Iterator { + let (first, second) = match self { + DerivedSkolems::Invalid => (None, None), + DerivedSkolems::Single(a) => (Some(a), None), + DerivedSkolems::Pair(a, b) => (Some(a), Some(b)), + }; + first.into_iter().chain(second) + } +} + +/// Expected variance and wrapper class for a derived parameter. +pub type ParameterConfig = (Variance, Option<(FileId, TypeItemId)>); + +/// Configuration for variance-aware derivation. +pub enum VarianceConfig { + Single(ParameterConfig), + Pair(ParameterConfig, ParameterConfig), +} + +/// Generates variance-aware constraints for Functor-like derivation. +pub fn generate_variance_constraints( + state: &mut CheckState, + context: &CheckContext, + data_file: FileId, + data_id: TypeItemId, + derived_type: TypeId, + config: VarianceConfig, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let constructors = tools::lookup_data_constructors(context, data_file, data_id)?; + + for constructor_id in constructors { + let constructor_type = + super::lookup_local_term_type(state, context, data_file, constructor_id)?; + + let Some(constructor_type) = constructor_type else { + continue; + }; + + let (fields, skolems) = + extract_fields_with_skolems(state, context, constructor_type, derived_type, &config); + + for field_type in fields { + check_variance_field(state, context, field_type, Variance::Covariant, &skolems); + } + } + + Ok(()) +} + +/// Extracts constructor fields and tracks Skolem variables for unmapped parameters. +/// +/// Uses the variance configuration to assign expected variance and wrapper class +/// to each derived parameter's Skolem variable. +fn extract_fields_with_skolems( + state: &mut CheckState, + context: &CheckContext, + constructor_type: TypeId, + derived_type: TypeId, + config: &VarianceConfig, +) -> (Vec, DerivedSkolems) +where + Q: ExternalQueries, +{ + let type_arguments = extract_type_arguments(state, derived_type); + let mut arguments_iter = type_arguments.into_iter(); + let mut current_id = constructor_type; + let mut levels = vec![]; + + safe_loop! { + current_id = state.normalize_type(current_id); + match &state.storage[current_id] { + Type::Forall(binder, inner) => { + let binder_level = binder.level; + let binder_kind = binder.kind; + let inner = *inner; + + let argument_type = arguments_iter.next().unwrap_or_else(|| { + levels.push(binder_level); + let skolem = Variable::Skolem(binder_level, binder_kind); + state.storage.intern(Type::Variable(skolem)) + }); + + current_id = substitute::SubstituteBound::on(state, binder_level, argument_type, inner); + } + _ => break, + } + } + + // The last N levels correspond to the N derived parameters. + let skolems = match (config, &levels[..]) { + (VarianceConfig::Single(config), [.., a]) => { + DerivedSkolems::Single(DerivedParameter::new(*a, *config)) + } + (VarianceConfig::Pair(a_config, b_config), [.., a, b]) => DerivedSkolems::Pair( + DerivedParameter::new(*a, *a_config), + DerivedParameter::new(*b, *b_config), + ), + _ => { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + DerivedSkolems::Invalid + } + }; + + let mut fields = vec![]; + safe_loop! { + current_id = state.normalize_type(current_id); + match state.storage[current_id] { + Type::Function(argument, result) => { + fields.push(argument); + current_id = result; + } + _ => break, + } + } + + (fields, skolems) +} + +/// Checks a field type for variance violations and emits wrapper class constraints. +fn check_variance_field( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, + variance: Variance, + skolems: &DerivedSkolems, +) where + Q: ExternalQueries, +{ + let type_id = state.normalize_type(type_id); + + match state.storage[type_id].clone() { + Type::Variable(Variable::Skolem(level, _)) => { + if let Some(parameter) = skolems.get(level) + && variance != parameter.expected + { + let global = transfer::globalize(state, context, type_id); + if variance == Variance::Covariant { + state.insert_error(ErrorKind::CovariantOccurrence { type_id: global }); + } else { + state.insert_error(ErrorKind::ContravariantOccurrence { type_id: global }); + } + } + } + + Type::Function(argument, result) => { + check_variance_field(state, context, argument, variance.flip(), skolems); + check_variance_field(state, context, result, variance, skolems); + } + + Type::Application(function, argument) => { + let function = state.normalize_type(function); + + if function == context.prim.record { + check_variance_field(state, context, argument, variance, skolems); + } else { + for parameter in skolems.iter() { + if contains_skolem_level(state, argument, parameter.level) { + if variance != parameter.expected { + let global = transfer::globalize(state, context, type_id); + if variance == Variance::Covariant { + state.insert_error(ErrorKind::CovariantOccurrence { + type_id: global, + }); + } else { + state.insert_error(ErrorKind::ContravariantOccurrence { + type_id: global, + }); + } + } else if let Some(class) = parameter.class { + tools::emit_constraint(state, class, function); + } else { + state.insert_error(ErrorKind::DeriveMissingFunctor); + } + } + } + check_variance_field(state, context, argument, variance, skolems); + } + } + + Type::Row(RowType { ref fields, .. }) => { + for field in fields.iter() { + check_variance_field(state, context, field.id, variance, skolems); + } + } + + Type::KindApplication(_, argument) => { + check_variance_field(state, context, argument, variance, skolems); + } + + _ => (), + } +} + +/// Checks if a type contains a specific Skolem level. +fn contains_skolem_level(state: &mut CheckState, type_id: TypeId, target: debruijn::Level) -> bool { + let type_id = state.normalize_type(type_id); + + match state.storage[type_id].clone() { + Type::Variable(Variable::Skolem(level, _)) => level == target, + + Type::Application(function, argument) | Type::KindApplication(function, argument) => { + contains_skolem_level(state, function, target) + || contains_skolem_level(state, argument, target) + } + + Type::Function(argument, result) => { + contains_skolem_level(state, argument, target) + || contains_skolem_level(state, result, target) + } + + Type::Row(RowType { ref fields, tail }) => { + fields.iter().any(|f| contains_skolem_level(state, f.id, target)) + || tail.is_some_and(|t| contains_skolem_level(state, t, target)) + } + + Type::Forall(_, inner) | Type::Constrained(_, inner) | Type::Kinded(inner, _) => { + contains_skolem_level(state, inner, target) + } + + _ => false, + } +} diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index fa78678b..81e5007b 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -622,6 +622,8 @@ pub struct KnownTypesCore { pub bifunctor: Option<(FileId, TypeItemId)>, pub contravariant: Option<(FileId, TypeItemId)>, pub profunctor: Option<(FileId, TypeItemId)>, + pub foldable: Option<(FileId, TypeItemId)>, + pub bifoldable: Option<(FileId, TypeItemId)>, } impl KnownTypesCore { @@ -635,7 +637,20 @@ impl KnownTypesCore { let contravariant = fetch_known_type(queries, "Data.Functor.Contravariant", "Contravariant")?; let profunctor = fetch_known_type(queries, "Data.Profunctor", "Profunctor")?; - Ok(KnownTypesCore { eq, eq1, ord, ord1, functor, bifunctor, contravariant, profunctor }) + let foldable = fetch_known_type(queries, "Data.Foldable", "Foldable")?; + let bifoldable = fetch_known_type(queries, "Data.Bifoldable", "Bifoldable")?; + Ok(KnownTypesCore { + eq, + eq1, + ord, + ord1, + functor, + bifunctor, + contravariant, + profunctor, + foldable, + bifoldable, + }) } } diff --git a/tests-integration/fixtures/checking/158_derive_foldable_simple/Data.Foldable.purs b/tests-integration/fixtures/checking/158_derive_foldable_simple/Data.Foldable.purs new file mode 100644 index 00000000..103fe1eb --- /dev/null +++ b/tests-integration/fixtures/checking/158_derive_foldable_simple/Data.Foldable.purs @@ -0,0 +1,5 @@ +module Data.Foldable where + +class Foldable f where + foldr :: forall a b. (a -> b -> b) -> b -> f a -> b + foldl :: forall a b. (b -> a -> b) -> b -> f a -> b diff --git a/tests-integration/fixtures/checking/158_derive_foldable_simple/Main.purs b/tests-integration/fixtures/checking/158_derive_foldable_simple/Main.purs new file mode 100644 index 00000000..3b377612 --- /dev/null +++ b/tests-integration/fixtures/checking/158_derive_foldable_simple/Main.purs @@ -0,0 +1,12 @@ +module Main where + +import Data.Foldable (class Foldable) + +data Identity a = Identity a +derive instance Foldable Identity + +data Maybe a = Nothing | Just a +derive instance Foldable Maybe + +data Const e a = Const e +derive instance Foldable (Const e) diff --git a/tests-integration/fixtures/checking/158_derive_foldable_simple/Main.snap b/tests-integration/fixtures/checking/158_derive_foldable_simple/Main.snap new file mode 100644 index 00000000..0686cbb7 --- /dev/null +++ b/tests-integration/fixtures/checking/158_derive_foldable_simple/Main.snap @@ -0,0 +1,33 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Identity :: forall (a :: Type). a -> Identity a +Nothing :: forall (a :: Type). Maybe a +Just :: forall (a :: Type). a -> Maybe a +Const :: forall (t3 :: Type) (e :: Type) (a :: t3). e -> Const @t3 e a + +Types +Identity :: Type -> Type +Maybe :: Type -> Type +Const :: forall (t3 :: Type). Type -> t3 -> Type + +Data +Identity + Quantified = :0 + Kind = :0 + +Maybe + Quantified = :0 + Kind = :0 + +Const + Quantified = :1 + Kind = :0 + + +Derived +derive Foldable (Identity :: Type -> Type) +derive Foldable (Maybe :: Type -> Type) +derive Foldable (Const @Type &0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Data.Foldable.purs b/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Data.Foldable.purs new file mode 100644 index 00000000..103fe1eb --- /dev/null +++ b/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Data.Foldable.purs @@ -0,0 +1,5 @@ +module Data.Foldable where + +class Foldable f where + foldr :: forall a b. (a -> b -> b) -> b -> f a -> b + foldl :: forall a b. (b -> a -> b) -> b -> f a -> b diff --git a/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.purs b/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.purs new file mode 100644 index 00000000..259227a9 --- /dev/null +++ b/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.purs @@ -0,0 +1,9 @@ +module Main where + +import Data.Foldable (class Foldable) + +data Wrap f a = Wrap (f a) +derive instance Foldable f => Foldable (Wrap f) + +data WrapNoFoldable f a = WrapNoFoldable (f a) +derive instance Foldable (WrapNoFoldable f) diff --git a/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.snap b/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.snap new file mode 100644 index 00000000..453fce55 --- /dev/null +++ b/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.snap @@ -0,0 +1,28 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Wrap :: forall (t4 :: Type) (f :: t4 -> Type) (a :: t4). f a -> Wrap @t4 f a +WrapNoFoldable :: forall (t10 :: Type) (f :: t10 -> Type) (a :: t10). f a -> WrapNoFoldable @t10 f a + +Types +Wrap :: forall (t4 :: Type). (t4 -> Type) -> t4 -> Type +WrapNoFoldable :: forall (t10 :: Type). (t10 -> Type) -> t10 -> Type + +Data +Wrap + Quantified = :1 + Kind = :0 + +WrapNoFoldable + Quantified = :1 + Kind = :0 + + +Derived +derive Foldable &0 => Foldable (Wrap @Type &0 :: Type -> Type) +derive Foldable (WrapNoFoldable @Type &0 :: Type -> Type) + +Errors +NoInstanceFound { Foldable &0 } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Bifoldable.purs b/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Bifoldable.purs new file mode 100644 index 00000000..df7bf51e --- /dev/null +++ b/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Bifoldable.purs @@ -0,0 +1,5 @@ +module Data.Bifoldable where + +class Bifoldable p where + bifoldr :: forall a b c. (a -> c -> c) -> (b -> c -> c) -> c -> p a b -> c + bifoldl :: forall a b c. (c -> a -> c) -> (c -> b -> c) -> c -> p a b -> c diff --git a/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Foldable.purs b/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Foldable.purs new file mode 100644 index 00000000..103fe1eb --- /dev/null +++ b/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Foldable.purs @@ -0,0 +1,5 @@ +module Data.Foldable where + +class Foldable f where + foldr :: forall a b. (a -> b -> b) -> b -> f a -> b + foldl :: forall a b. (b -> a -> b) -> b -> f a -> b diff --git a/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.purs b/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.purs new file mode 100644 index 00000000..f7557039 --- /dev/null +++ b/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.purs @@ -0,0 +1,12 @@ +module Main where + +import Data.Bifoldable (class Bifoldable) + +data Either a b = Left a | Right b +derive instance Bifoldable Either + +data Pair a b = Pair a b +derive instance Bifoldable Pair + +data Const2 e a b = Const2 e +derive instance Bifoldable (Const2 e) diff --git a/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.snap b/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.snap new file mode 100644 index 00000000..fd2799de --- /dev/null +++ b/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.snap @@ -0,0 +1,34 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Left :: forall (a :: Type) (b :: Type). a -> Either a b +Right :: forall (a :: Type) (b :: Type). b -> Either a b +Pair :: forall (a :: Type) (b :: Type). a -> b -> Pair a b +Const2 :: + forall (t5 :: Type) (t6 :: Type) (e :: Type) (a :: t5) (b :: t6). e -> Const2 @t5 @t6 e a b + +Types +Either :: Type -> Type -> Type +Pair :: Type -> Type -> Type +Const2 :: forall (t5 :: Type) (t6 :: Type). Type -> t5 -> t6 -> Type + +Data +Either + Quantified = :0 + Kind = :0 + +Pair + Quantified = :0 + Kind = :0 + +Const2 + Quantified = :2 + Kind = :0 + + +Derived +derive Bifoldable (Either :: Type -> Type -> Type) +derive Bifoldable (Pair :: Type -> Type -> Type) +derive Bifoldable (Const2 @Type @Type &0 :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Bifoldable.purs b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Bifoldable.purs new file mode 100644 index 00000000..df7bf51e --- /dev/null +++ b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Bifoldable.purs @@ -0,0 +1,5 @@ +module Data.Bifoldable where + +class Bifoldable p where + bifoldr :: forall a b c. (a -> c -> c) -> (b -> c -> c) -> c -> p a b -> c + bifoldl :: forall a b c. (c -> a -> c) -> (c -> b -> c) -> c -> p a b -> c diff --git a/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Foldable.purs b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Foldable.purs new file mode 100644 index 00000000..103fe1eb --- /dev/null +++ b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Foldable.purs @@ -0,0 +1,5 @@ +module Data.Foldable where + +class Foldable f where + foldr :: forall a b. (a -> b -> b) -> b -> f a -> b + foldl :: forall a b. (b -> a -> b) -> b -> f a -> b diff --git a/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.purs b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.purs new file mode 100644 index 00000000..d6f1526a --- /dev/null +++ b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.purs @@ -0,0 +1,10 @@ +module Main where + +import Data.Foldable (class Foldable) +import Data.Bifoldable (class Bifoldable) + +data WrapBoth f g a b = WrapBoth (f a) (g b) +derive instance (Foldable f, Foldable g) => Bifoldable (WrapBoth f g) + +data WrapBothNoConstraint f g a b = WrapBothNoConstraint (f a) (g b) +derive instance Bifoldable (WrapBothNoConstraint f g) diff --git a/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.snap b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.snap new file mode 100644 index 00000000..110ade98 --- /dev/null +++ b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.snap @@ -0,0 +1,34 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +WrapBoth :: + forall (t6 :: Type) (t10 :: Type) (f :: t6 -> Type) (g :: t10 -> Type) (a :: t6) (b :: t10). + f a -> g b -> WrapBoth @t6 @t10 f g a b +WrapBothNoConstraint :: + forall (t18 :: Type) (t22 :: Type) (f :: t18 -> Type) (g :: t22 -> Type) (a :: t18) (b :: t22). + f a -> g b -> WrapBothNoConstraint @t18 @t22 f g a b + +Types +WrapBoth :: forall (t6 :: Type) (t10 :: Type). (t6 -> Type) -> (t10 -> Type) -> t6 -> t10 -> Type +WrapBothNoConstraint :: + forall (t18 :: Type) (t22 :: Type). (t18 -> Type) -> (t22 -> Type) -> t18 -> t22 -> Type + +Data +WrapBoth + Quantified = :2 + Kind = :0 + +WrapBothNoConstraint + Quantified = :2 + Kind = :0 + + +Derived +derive (Foldable &0, Foldable &1) => Bifoldable (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) +derive Bifoldable (WrapBothNoConstraint @Type @Type &0 &1 :: Type -> Type -> Type) + +Errors +NoInstanceFound { Foldable &0 } at [TermDeclaration(Idx::(3))] +NoInstanceFound { Foldable &1 } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 0e030f10..6b473eee 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -325,3 +325,11 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_156_derive_bifunctor_insufficient_params_main() { run_test("156_derive_bifunctor_insufficient_params", "Main"); } #[rustfmt::skip] #[test] fn test_157_derive_functor_insufficient_params_main() { run_test("157_derive_functor_insufficient_params", "Main"); } + +#[rustfmt::skip] #[test] fn test_158_derive_foldable_simple_main() { run_test("158_derive_foldable_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_159_derive_foldable_higher_kinded_main() { run_test("159_derive_foldable_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_160_derive_bifoldable_simple_main() { run_test("160_derive_bifoldable_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_161_derive_bifoldable_higher_kinded_main() { run_test("161_derive_bifoldable_higher_kinded", "Main"); } From f9b014bff4f2010de8a9ee14c92f61dc04e9d537 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 20:05:19 +0800 Subject: [PATCH 25/62] Add prelude folder for checking fixtures --- tests-integration/build.rs | 4 ++++ .../032_recursive_synonym_expansion/Main.snap | 18 +++++++++--------- .../123_incomplete_instance_head/Main.snap | 2 +- .../checking/127_derive_eq_simple/Data.Eq.purs | 4 ---- .../129_derive_eq_with_fields/Data.Eq.purs | 10 ---------- .../130_derive_eq_parameterized/Data.Eq.purs | 7 ------- .../Data.Eq.purs | 4 ---- .../131_derive_eq_missing_instance/Main.purs | 4 +++- .../131_derive_eq_missing_instance/Main.snap | 10 ++++++++-- .../133_derive_eq_partial/Data.Eq.purs | 7 ------- .../134_derive_ord_simple/Data.Eq.purs | 10 ---------- .../134_derive_ord_simple/Data.Ord.purs | 12 ------------ .../Data.Eq.purs | 10 ---------- .../Data.Ord.purs | 12 ------------ .../Data.Eq.purs | 10 ---------- .../Data.Ord.purs | 12 ------------ .../137_derive_newtype_simple/Data.Show.purs | 7 ------- .../Data.Show.purs | 7 ------- .../Data.Show.purs | 7 ------- .../Data.Show.purs | 7 ------- .../Data.Show.purs | 7 ------- .../Data.Show.purs | 7 ------- .../Data.Show.purs | 7 ------- .../Data.Show.purs | 7 ------- .../Data.Functor.purs | 4 ---- .../Data.Functor.purs | 4 ---- .../Data.Bifunctor.purs | 4 ---- .../Data.Functor.purs | 4 ---- .../Data.Bifunctor.purs | 4 ---- .../Data.Functor.purs | 3 +++ .../Data.Functor.Contravariant.purs | 4 ---- .../Data.Profunctor.purs | 4 ---- .../Data.Bifunctor.purs | 4 ---- .../Data.Functor.purs | 4 ---- .../Data.Foldable.purs | 5 ----- .../Data.Foldable.purs | 5 ----- .../Data.Bifoldable.purs | 5 ----- .../Data.Foldable.purs | 5 ----- .../Data.Bifoldable.purs | 0 .../Data.Bifunctor.purs | 0 .../Data.Eq.purs | 3 +++ .../Data.Foldable.purs | 0 .../Data.Functor.Contravariant.purs | 0 .../Data.Functor.purs | 0 .../fixtures/checking/prelude/Data.Ord.purs | 17 +++++++++++++++++ .../Data.Profunctor.purs | 0 .../Data.Show.purs | 0 tests-integration/src/lib.rs | 8 ++++++++ 48 files changed, 56 insertions(+), 223 deletions(-) delete mode 100644 tests-integration/fixtures/checking/127_derive_eq_simple/Data.Eq.purs delete mode 100644 tests-integration/fixtures/checking/129_derive_eq_with_fields/Data.Eq.purs delete mode 100644 tests-integration/fixtures/checking/130_derive_eq_parameterized/Data.Eq.purs delete mode 100644 tests-integration/fixtures/checking/131_derive_eq_missing_instance/Data.Eq.purs delete mode 100644 tests-integration/fixtures/checking/133_derive_eq_partial/Data.Eq.purs delete mode 100644 tests-integration/fixtures/checking/134_derive_ord_simple/Data.Eq.purs delete mode 100644 tests-integration/fixtures/checking/134_derive_ord_simple/Data.Ord.purs delete mode 100644 tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Eq.purs delete mode 100644 tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Ord.purs delete mode 100644 tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Eq.purs delete mode 100644 tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Ord.purs delete mode 100644 tests-integration/fixtures/checking/137_derive_newtype_simple/Data.Show.purs delete mode 100644 tests-integration/fixtures/checking/138_derive_newtype_parameterized/Data.Show.purs delete mode 100644 tests-integration/fixtures/checking/139_derive_newtype_with_given/Data.Show.purs delete mode 100644 tests-integration/fixtures/checking/140_derive_newtype_recursive/Data.Show.purs delete mode 100644 tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Data.Show.purs delete mode 100644 tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Data.Show.purs delete mode 100644 tests-integration/fixtures/checking/144_derive_newtype_missing_given/Data.Show.purs delete mode 100644 tests-integration/fixtures/checking/145_derive_newtype_multi_param/Data.Show.purs delete mode 100644 tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Data.Functor.purs delete mode 100644 tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Data.Functor.purs delete mode 100644 tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Bifunctor.purs delete mode 100644 tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Functor.purs delete mode 100644 tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Data.Bifunctor.purs create mode 100644 tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Data.Functor.purs delete mode 100644 tests-integration/fixtures/checking/153_derive_contravariant_error/Data.Functor.Contravariant.purs delete mode 100644 tests-integration/fixtures/checking/155_derive_profunctor_error/Data.Profunctor.purs delete mode 100644 tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Data.Bifunctor.purs delete mode 100644 tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Data.Functor.purs delete mode 100644 tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Data.Foldable.purs delete mode 100644 tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Foldable.purs delete mode 100644 tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Bifoldable.purs delete mode 100644 tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Foldable.purs rename tests-integration/fixtures/checking/{160_derive_bifoldable_simple => prelude}/Data.Bifoldable.purs (100%) rename tests-integration/fixtures/checking/{149_derive_bifunctor_simple => prelude}/Data.Bifunctor.purs (100%) rename tests-integration/fixtures/checking/{132_derive_eq_1_higher_kinded => prelude}/Data.Eq.purs (80%) rename tests-integration/fixtures/checking/{158_derive_foldable_simple => prelude}/Data.Foldable.purs (100%) rename tests-integration/fixtures/checking/{152_derive_contravariant_simple => prelude}/Data.Functor.Contravariant.purs (100%) rename tests-integration/fixtures/checking/{146_derive_functor_simple => prelude}/Data.Functor.purs (100%) create mode 100644 tests-integration/fixtures/checking/prelude/Data.Ord.purs rename tests-integration/fixtures/checking/{154_derive_profunctor_simple => prelude}/Data.Profunctor.purs (100%) rename tests-integration/fixtures/checking/{141_derive_newtype_phantom => prelude}/Data.Show.purs (100%) diff --git a/tests-integration/build.rs b/tests-integration/build.rs index 1fcf1f5b..bae8f5ce 100644 --- a/tests-integration/build.rs +++ b/tests-integration/build.rs @@ -145,6 +145,10 @@ fn run_test(folder: &str, file: &str) {{ for folder in read_dir(Path::new("./fixtures/checking")) { let Some(stem) = folder.file_stem() else { continue }; let folder_name = converter.convert(stem.to_os_string().into_string().unwrap()); + // Skip the prelude folder - it's shared setup, not a test + if folder_name == "prelude" { + continue; + } writeln!( buffer, r#" diff --git a/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap b/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap index b6116fbc..a7be6b1f 100644 --- a/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap +++ b/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap @@ -37,12 +37,12 @@ Valid = Int Errors -RecursiveSynonymExpansion { file_id: Idx::(9), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(9), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(9), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(9), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(9), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(9), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(9), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] -RecursiveSynonymExpansion { file_id: Idx::(9), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] -RecursiveSynonymExpansion { file_id: Idx::(9), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap b/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap index 4769eab6..50ac5685 100644 --- a/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap +++ b/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap @@ -18,4 +18,4 @@ instance Pair (Int :: Type) chain: 0 Errors -InstanceHeadMismatch { class_file: Idx::(9), class_item: Idx::(0), expected: 2, actual: 1 } at [TermDeclaration(Idx::(2))] +InstanceHeadMismatch { class_file: Idx::(18), class_item: Idx::(0), expected: 2, actual: 1 } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/127_derive_eq_simple/Data.Eq.purs b/tests-integration/fixtures/checking/127_derive_eq_simple/Data.Eq.purs deleted file mode 100644 index c59d0788..00000000 --- a/tests-integration/fixtures/checking/127_derive_eq_simple/Data.Eq.purs +++ /dev/null @@ -1,4 +0,0 @@ -module Data.Eq where - -class Eq a where - eq :: a -> a -> Boolean diff --git a/tests-integration/fixtures/checking/129_derive_eq_with_fields/Data.Eq.purs b/tests-integration/fixtures/checking/129_derive_eq_with_fields/Data.Eq.purs deleted file mode 100644 index eca9493e..00000000 --- a/tests-integration/fixtures/checking/129_derive_eq_with_fields/Data.Eq.purs +++ /dev/null @@ -1,10 +0,0 @@ -module Data.Eq where - -class Eq a where - eq :: a -> a -> Boolean - -instance Eq Int where - eq _ _ = true - -instance Eq Boolean where - eq _ _ = true diff --git a/tests-integration/fixtures/checking/130_derive_eq_parameterized/Data.Eq.purs b/tests-integration/fixtures/checking/130_derive_eq_parameterized/Data.Eq.purs deleted file mode 100644 index b30c2e37..00000000 --- a/tests-integration/fixtures/checking/130_derive_eq_parameterized/Data.Eq.purs +++ /dev/null @@ -1,7 +0,0 @@ -module Data.Eq where - -class Eq a where - eq :: a -> a -> Boolean - -instance Eq Int where - eq _ _ = true diff --git a/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Data.Eq.purs b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Data.Eq.purs deleted file mode 100644 index c59d0788..00000000 --- a/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Data.Eq.purs +++ /dev/null @@ -1,4 +0,0 @@ -module Data.Eq where - -class Eq a where - eq :: a -> a -> Boolean diff --git a/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.purs b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.purs index 4af36bd3..8aaea864 100644 --- a/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.purs +++ b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.purs @@ -2,6 +2,8 @@ module Main where import Data.Eq (class Eq) -data Box = MkBox Int +data NoEq = MkNoEq + +data Box = MkBox NoEq derive instance Eq Box diff --git a/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap index 42f0ab59..a3861cb3 100644 --- a/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap +++ b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap @@ -3,12 +3,18 @@ source: tests-integration/tests/checking/generated.rs expression: report --- Terms -MkBox :: Int -> Box +MkNoEq :: NoEq +MkBox :: NoEq -> Box Types +NoEq :: Type Box :: Type Data +NoEq + Quantified = :0 + Kind = :0 + Box Quantified = :0 Kind = :0 @@ -18,4 +24,4 @@ Derived derive Eq (Box :: Type) Errors -NoInstanceFound { Eq Int } at [TermDeclaration(Idx::(1))] +NoInstanceFound { Eq NoEq } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/133_derive_eq_partial/Data.Eq.purs b/tests-integration/fixtures/checking/133_derive_eq_partial/Data.Eq.purs deleted file mode 100644 index b30c2e37..00000000 --- a/tests-integration/fixtures/checking/133_derive_eq_partial/Data.Eq.purs +++ /dev/null @@ -1,7 +0,0 @@ -module Data.Eq where - -class Eq a where - eq :: a -> a -> Boolean - -instance Eq Int where - eq _ _ = true diff --git a/tests-integration/fixtures/checking/134_derive_ord_simple/Data.Eq.purs b/tests-integration/fixtures/checking/134_derive_ord_simple/Data.Eq.purs deleted file mode 100644 index eca9493e..00000000 --- a/tests-integration/fixtures/checking/134_derive_ord_simple/Data.Eq.purs +++ /dev/null @@ -1,10 +0,0 @@ -module Data.Eq where - -class Eq a where - eq :: a -> a -> Boolean - -instance Eq Int where - eq _ _ = true - -instance Eq Boolean where - eq _ _ = true diff --git a/tests-integration/fixtures/checking/134_derive_ord_simple/Data.Ord.purs b/tests-integration/fixtures/checking/134_derive_ord_simple/Data.Ord.purs deleted file mode 100644 index 7566f8e4..00000000 --- a/tests-integration/fixtures/checking/134_derive_ord_simple/Data.Ord.purs +++ /dev/null @@ -1,12 +0,0 @@ -module Data.Ord where - -import Data.Eq (class Eq) - -class Eq a <= Ord a where - compare :: a -> a -> Int - -instance Ord Int where - compare _ _ = 0 - -instance Ord Boolean where - compare _ _ = 0 diff --git a/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Eq.purs b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Eq.purs deleted file mode 100644 index 601c73ef..00000000 --- a/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Eq.purs +++ /dev/null @@ -1,10 +0,0 @@ -module Data.Eq where - -class Eq a where - eq :: a -> a -> Boolean - -class Eq1 f where - eq1 :: forall a. Eq a => f a -> f a -> Boolean - -instance Eq Int where - eq _ _ = true diff --git a/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Ord.purs b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Ord.purs deleted file mode 100644 index 5d17e073..00000000 --- a/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Data.Ord.purs +++ /dev/null @@ -1,12 +0,0 @@ -module Data.Ord where - -import Data.Eq (class Eq, class Eq1) - -class Eq a <= Ord a where - compare :: a -> a -> Int - -class Eq1 f <= Ord1 f where - compare1 :: forall a. Ord a => f a -> f a -> Int - -instance Ord Int where - compare _ _ = 0 diff --git a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Eq.purs b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Eq.purs deleted file mode 100644 index 601c73ef..00000000 --- a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Eq.purs +++ /dev/null @@ -1,10 +0,0 @@ -module Data.Eq where - -class Eq a where - eq :: a -> a -> Boolean - -class Eq1 f where - eq1 :: forall a. Eq a => f a -> f a -> Boolean - -instance Eq Int where - eq _ _ = true diff --git a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Ord.purs b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Ord.purs deleted file mode 100644 index 5d17e073..00000000 --- a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Data.Ord.purs +++ /dev/null @@ -1,12 +0,0 @@ -module Data.Ord where - -import Data.Eq (class Eq, class Eq1) - -class Eq a <= Ord a where - compare :: a -> a -> Int - -class Eq1 f <= Ord1 f where - compare1 :: forall a. Ord a => f a -> f a -> Int - -instance Ord Int where - compare _ _ = 0 diff --git a/tests-integration/fixtures/checking/137_derive_newtype_simple/Data.Show.purs b/tests-integration/fixtures/checking/137_derive_newtype_simple/Data.Show.purs deleted file mode 100644 index ace9fabe..00000000 --- a/tests-integration/fixtures/checking/137_derive_newtype_simple/Data.Show.purs +++ /dev/null @@ -1,7 +0,0 @@ -module Data.Show where - -class Show a where - show :: a -> String - -instance Show Int where - show _ = "" diff --git a/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Data.Show.purs b/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Data.Show.purs deleted file mode 100644 index ace9fabe..00000000 --- a/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Data.Show.purs +++ /dev/null @@ -1,7 +0,0 @@ -module Data.Show where - -class Show a where - show :: a -> String - -instance Show Int where - show _ = "" diff --git a/tests-integration/fixtures/checking/139_derive_newtype_with_given/Data.Show.purs b/tests-integration/fixtures/checking/139_derive_newtype_with_given/Data.Show.purs deleted file mode 100644 index ace9fabe..00000000 --- a/tests-integration/fixtures/checking/139_derive_newtype_with_given/Data.Show.purs +++ /dev/null @@ -1,7 +0,0 @@ -module Data.Show where - -class Show a where - show :: a -> String - -instance Show Int where - show _ = "" diff --git a/tests-integration/fixtures/checking/140_derive_newtype_recursive/Data.Show.purs b/tests-integration/fixtures/checking/140_derive_newtype_recursive/Data.Show.purs deleted file mode 100644 index ace9fabe..00000000 --- a/tests-integration/fixtures/checking/140_derive_newtype_recursive/Data.Show.purs +++ /dev/null @@ -1,7 +0,0 @@ -module Data.Show where - -class Show a where - show :: a -> String - -instance Show Int where - show _ = "" diff --git a/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Data.Show.purs b/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Data.Show.purs deleted file mode 100644 index ace9fabe..00000000 --- a/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Data.Show.purs +++ /dev/null @@ -1,7 +0,0 @@ -module Data.Show where - -class Show a where - show :: a -> String - -instance Show Int where - show _ = "" diff --git a/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Data.Show.purs b/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Data.Show.purs deleted file mode 100644 index ace9fabe..00000000 --- a/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Data.Show.purs +++ /dev/null @@ -1,7 +0,0 @@ -module Data.Show where - -class Show a where - show :: a -> String - -instance Show Int where - show _ = "" diff --git a/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Data.Show.purs b/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Data.Show.purs deleted file mode 100644 index ace9fabe..00000000 --- a/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Data.Show.purs +++ /dev/null @@ -1,7 +0,0 @@ -module Data.Show where - -class Show a where - show :: a -> String - -instance Show Int where - show _ = "" diff --git a/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Data.Show.purs b/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Data.Show.purs deleted file mode 100644 index ace9fabe..00000000 --- a/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Data.Show.purs +++ /dev/null @@ -1,7 +0,0 @@ -module Data.Show where - -class Show a where - show :: a -> String - -instance Show Int where - show _ = "" diff --git a/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Data.Functor.purs b/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Data.Functor.purs deleted file mode 100644 index 354b844e..00000000 --- a/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Data.Functor.purs +++ /dev/null @@ -1,4 +0,0 @@ -module Data.Functor where - -class Functor f where - map :: forall a b. (a -> b) -> f a -> f b diff --git a/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Data.Functor.purs b/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Data.Functor.purs deleted file mode 100644 index 354b844e..00000000 --- a/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Data.Functor.purs +++ /dev/null @@ -1,4 +0,0 @@ -module Data.Functor where - -class Functor f where - map :: forall a b. (a -> b) -> f a -> f b diff --git a/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Bifunctor.purs b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Bifunctor.purs deleted file mode 100644 index 54b5e06b..00000000 --- a/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Bifunctor.purs +++ /dev/null @@ -1,4 +0,0 @@ -module Data.Bifunctor where - -class Bifunctor f where - bimap :: forall a b c d. (a -> b) -> (c -> d) -> f a c -> f b d diff --git a/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Functor.purs b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Functor.purs deleted file mode 100644 index 354b844e..00000000 --- a/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Data.Functor.purs +++ /dev/null @@ -1,4 +0,0 @@ -module Data.Functor where - -class Functor f where - map :: forall a b. (a -> b) -> f a -> f b diff --git a/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Data.Bifunctor.purs b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Data.Bifunctor.purs deleted file mode 100644 index 54b5e06b..00000000 --- a/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Data.Bifunctor.purs +++ /dev/null @@ -1,4 +0,0 @@ -module Data.Bifunctor where - -class Bifunctor f where - bimap :: forall a b c d. (a -> b) -> (c -> d) -> f a c -> f b d diff --git a/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Data.Functor.purs b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Data.Functor.purs new file mode 100644 index 00000000..546236f5 --- /dev/null +++ b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Data.Functor.purs @@ -0,0 +1,3 @@ +-- This empty module overrides the prelude's Data.Functor to test +-- that the Bifunctor derive correctly reports DeriveMissingFunctor. +module Data.Functor where diff --git a/tests-integration/fixtures/checking/153_derive_contravariant_error/Data.Functor.Contravariant.purs b/tests-integration/fixtures/checking/153_derive_contravariant_error/Data.Functor.Contravariant.purs deleted file mode 100644 index 61246c10..00000000 --- a/tests-integration/fixtures/checking/153_derive_contravariant_error/Data.Functor.Contravariant.purs +++ /dev/null @@ -1,4 +0,0 @@ -module Data.Functor.Contravariant where - -class Contravariant f where - cmap :: forall a b. (b -> a) -> f a -> f b diff --git a/tests-integration/fixtures/checking/155_derive_profunctor_error/Data.Profunctor.purs b/tests-integration/fixtures/checking/155_derive_profunctor_error/Data.Profunctor.purs deleted file mode 100644 index 4eaf5dcf..00000000 --- a/tests-integration/fixtures/checking/155_derive_profunctor_error/Data.Profunctor.purs +++ /dev/null @@ -1,4 +0,0 @@ -module Data.Profunctor where - -class Profunctor p where - dimap :: forall a b c d. (a -> b) -> (c -> d) -> p b c -> p a d diff --git a/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Data.Bifunctor.purs b/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Data.Bifunctor.purs deleted file mode 100644 index 54b5e06b..00000000 --- a/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Data.Bifunctor.purs +++ /dev/null @@ -1,4 +0,0 @@ -module Data.Bifunctor where - -class Bifunctor f where - bimap :: forall a b c d. (a -> b) -> (c -> d) -> f a c -> f b d diff --git a/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Data.Functor.purs b/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Data.Functor.purs deleted file mode 100644 index 354b844e..00000000 --- a/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Data.Functor.purs +++ /dev/null @@ -1,4 +0,0 @@ -module Data.Functor where - -class Functor f where - map :: forall a b. (a -> b) -> f a -> f b diff --git a/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Data.Foldable.purs b/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Data.Foldable.purs deleted file mode 100644 index 103fe1eb..00000000 --- a/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Data.Foldable.purs +++ /dev/null @@ -1,5 +0,0 @@ -module Data.Foldable where - -class Foldable f where - foldr :: forall a b. (a -> b -> b) -> b -> f a -> b - foldl :: forall a b. (b -> a -> b) -> b -> f a -> b diff --git a/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Foldable.purs b/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Foldable.purs deleted file mode 100644 index 103fe1eb..00000000 --- a/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Foldable.purs +++ /dev/null @@ -1,5 +0,0 @@ -module Data.Foldable where - -class Foldable f where - foldr :: forall a b. (a -> b -> b) -> b -> f a -> b - foldl :: forall a b. (b -> a -> b) -> b -> f a -> b diff --git a/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Bifoldable.purs b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Bifoldable.purs deleted file mode 100644 index df7bf51e..00000000 --- a/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Bifoldable.purs +++ /dev/null @@ -1,5 +0,0 @@ -module Data.Bifoldable where - -class Bifoldable p where - bifoldr :: forall a b c. (a -> c -> c) -> (b -> c -> c) -> c -> p a b -> c - bifoldl :: forall a b c. (c -> a -> c) -> (c -> b -> c) -> c -> p a b -> c diff --git a/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Foldable.purs b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Foldable.purs deleted file mode 100644 index 103fe1eb..00000000 --- a/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Data.Foldable.purs +++ /dev/null @@ -1,5 +0,0 @@ -module Data.Foldable where - -class Foldable f where - foldr :: forall a b. (a -> b -> b) -> b -> f a -> b - foldl :: forall a b. (b -> a -> b) -> b -> f a -> b diff --git a/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Bifoldable.purs b/tests-integration/fixtures/checking/prelude/Data.Bifoldable.purs similarity index 100% rename from tests-integration/fixtures/checking/160_derive_bifoldable_simple/Data.Bifoldable.purs rename to tests-integration/fixtures/checking/prelude/Data.Bifoldable.purs diff --git a/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Data.Bifunctor.purs b/tests-integration/fixtures/checking/prelude/Data.Bifunctor.purs similarity index 100% rename from tests-integration/fixtures/checking/149_derive_bifunctor_simple/Data.Bifunctor.purs rename to tests-integration/fixtures/checking/prelude/Data.Bifunctor.purs diff --git a/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Data.Eq.purs b/tests-integration/fixtures/checking/prelude/Data.Eq.purs similarity index 80% rename from tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Data.Eq.purs rename to tests-integration/fixtures/checking/prelude/Data.Eq.purs index 601c73ef..9235fde9 100644 --- a/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Data.Eq.purs +++ b/tests-integration/fixtures/checking/prelude/Data.Eq.purs @@ -8,3 +8,6 @@ class Eq1 f where instance Eq Int where eq _ _ = true + +instance Eq Boolean where + eq _ _ = true diff --git a/tests-integration/fixtures/checking/158_derive_foldable_simple/Data.Foldable.purs b/tests-integration/fixtures/checking/prelude/Data.Foldable.purs similarity index 100% rename from tests-integration/fixtures/checking/158_derive_foldable_simple/Data.Foldable.purs rename to tests-integration/fixtures/checking/prelude/Data.Foldable.purs diff --git a/tests-integration/fixtures/checking/152_derive_contravariant_simple/Data.Functor.Contravariant.purs b/tests-integration/fixtures/checking/prelude/Data.Functor.Contravariant.purs similarity index 100% rename from tests-integration/fixtures/checking/152_derive_contravariant_simple/Data.Functor.Contravariant.purs rename to tests-integration/fixtures/checking/prelude/Data.Functor.Contravariant.purs diff --git a/tests-integration/fixtures/checking/146_derive_functor_simple/Data.Functor.purs b/tests-integration/fixtures/checking/prelude/Data.Functor.purs similarity index 100% rename from tests-integration/fixtures/checking/146_derive_functor_simple/Data.Functor.purs rename to tests-integration/fixtures/checking/prelude/Data.Functor.purs diff --git a/tests-integration/fixtures/checking/prelude/Data.Ord.purs b/tests-integration/fixtures/checking/prelude/Data.Ord.purs new file mode 100644 index 00000000..7cfe55f4 --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Data.Ord.purs @@ -0,0 +1,17 @@ +module Data.Ord where + +import Data.Eq (class Eq, class Eq1) + +data Ordering = LT | EQ | GT + +class Eq a <= Ord a where + compare :: a -> a -> Ordering + +class Eq1 f <= Ord1 f where + compare1 :: forall a. Ord a => f a -> f a -> Ordering + +instance Ord Int where + compare _ _ = EQ + +instance Ord Boolean where + compare _ _ = EQ diff --git a/tests-integration/fixtures/checking/154_derive_profunctor_simple/Data.Profunctor.purs b/tests-integration/fixtures/checking/prelude/Data.Profunctor.purs similarity index 100% rename from tests-integration/fixtures/checking/154_derive_profunctor_simple/Data.Profunctor.purs rename to tests-integration/fixtures/checking/prelude/Data.Profunctor.purs diff --git a/tests-integration/fixtures/checking/141_derive_newtype_phantom/Data.Show.purs b/tests-integration/fixtures/checking/prelude/Data.Show.purs similarity index 100% rename from tests-integration/fixtures/checking/141_derive_newtype_phantom/Data.Show.purs rename to tests-integration/fixtures/checking/prelude/Data.Show.purs diff --git a/tests-integration/src/lib.rs b/tests-integration/src/lib.rs index f13f1af4..07a54087 100644 --- a/tests-integration/src/lib.rs +++ b/tests-integration/src/lib.rs @@ -37,6 +37,14 @@ pub fn load_compiler(folder: &Path) -> (QueryEngine, Files) { let mut engine = QueryEngine::default(); let mut files = Files::default(); prim::configure(&mut engine, &mut files); + + if folder.starts_with("fixtures/checking/") { + let prelude = Path::new("fixtures/checking/prelude"); + load_folder(prelude).for_each(|path| { + load_file(&mut engine, &mut files, &path); + }); + } + load_folder(folder).for_each(|path| { load_file(&mut engine, &mut files, &path); }); From b7a3fccd73dadfff0a4ae089943f42cb49e357d5 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 20:32:28 +0800 Subject: [PATCH 26/62] Initial derive for Traversable and Bitraversable --- .../checking/src/algorithm/derive.rs | 5 ++ .../src/algorithm/derive/traversable.rs | 85 +++++++++++++++++++ compiler-core/checking/src/algorithm/state.rs | 6 ++ .../032_recursive_synonym_expansion/Main.snap | 18 ++-- .../123_incomplete_instance_head/Main.snap | 2 +- .../162_derive_traversable_simple/Main.purs | 12 +++ .../162_derive_traversable_simple/Main.snap | 33 +++++++ .../Main.purs | 6 ++ .../Main.snap | 20 +++++ .../164_derive_bitraversable_simple/Main.purs | 9 ++ .../164_derive_bitraversable_simple/Main.snap | 26 ++++++ .../Main.purs | 7 ++ .../Main.snap | 20 +++++ .../checking/prelude/Control.Applicative.purs | 6 ++ .../checking/prelude/Control.Apply.purs | 6 ++ .../checking/prelude/Data.Bitraversable.purs | 10 +++ .../checking/prelude/Data.Traversable.purs | 9 ++ tests-integration/tests/checking/generated.rs | 8 ++ 18 files changed, 278 insertions(+), 10 deletions(-) create mode 100644 compiler-core/checking/src/algorithm/derive/traversable.rs create mode 100644 tests-integration/fixtures/checking/162_derive_traversable_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap create mode 100644 tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.purs create mode 100644 tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap create mode 100644 tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.snap create mode 100644 tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.purs create mode 100644 tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap create mode 100644 tests-integration/fixtures/checking/prelude/Control.Applicative.purs create mode 100644 tests-integration/fixtures/checking/prelude/Control.Apply.purs create mode 100644 tests-integration/fixtures/checking/prelude/Data.Bitraversable.purs create mode 100644 tests-integration/fixtures/checking/prelude/Data.Traversable.purs diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index a96417db..54770a06 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -5,6 +5,7 @@ mod foldable; mod functor; mod higher_kinded; mod tools; +mod traversable; mod variance; use building_types::QueryResult; @@ -106,6 +107,10 @@ where foldable::check_derive_foldable(state, context, elaborated)?; } else if class_is(known_types.bifoldable) { foldable::check_derive_bifoldable(state, context, elaborated)?; + } else if class_is(known_types.traversable) { + traversable::check_derive_traversable(state, context, elaborated)?; + } else if class_is(known_types.bitraversable) { + traversable::check_derive_bitraversable(state, context, elaborated)?; } else { state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); }; diff --git a/compiler-core/checking/src/algorithm/derive/traversable.rs b/compiler-core/checking/src/algorithm/derive/traversable.rs new file mode 100644 index 00000000..58ab720d --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/traversable.rs @@ -0,0 +1,85 @@ +//! Implements derive for Traversable and Bitraversable. + +use building_types::QueryResult; + +use super::tools; +use super::variance::{Variance, VarianceConfig, generate_variance_constraints}; +use crate::ExternalQueries; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::transfer; +use crate::error::ErrorKind; + +/// Checks a derive instance for Traversable. +pub fn check_derive_traversable( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 1, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + let traversable = Some((input.class_file, input.class_id)); + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); + + let config = VarianceConfig::Single((Variance::Covariant, traversable)); + generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; + + tools::solve_and_report_constraints(state, context) +} + +/// Checks a derive instance for Bitraversable. +pub fn check_derive_bitraversable( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 1, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + // Bitraversable derivation emits Traversable constraints for wrapped parameters. + let traversable = context.known_types.traversable; + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); + + let config = VarianceConfig::Pair( + (Variance::Covariant, traversable), + (Variance::Covariant, traversable), + ); + + generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; + + tools::solve_and_report_constraints(state, context) +} diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index 81e5007b..556fd237 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -624,6 +624,8 @@ pub struct KnownTypesCore { pub profunctor: Option<(FileId, TypeItemId)>, pub foldable: Option<(FileId, TypeItemId)>, pub bifoldable: Option<(FileId, TypeItemId)>, + pub traversable: Option<(FileId, TypeItemId)>, + pub bitraversable: Option<(FileId, TypeItemId)>, } impl KnownTypesCore { @@ -639,6 +641,8 @@ impl KnownTypesCore { let profunctor = fetch_known_type(queries, "Data.Profunctor", "Profunctor")?; let foldable = fetch_known_type(queries, "Data.Foldable", "Foldable")?; let bifoldable = fetch_known_type(queries, "Data.Bifoldable", "Bifoldable")?; + let traversable = fetch_known_type(queries, "Data.Traversable", "Traversable")?; + let bitraversable = fetch_known_type(queries, "Data.Bitraversable", "Bitraversable")?; Ok(KnownTypesCore { eq, eq1, @@ -650,6 +654,8 @@ impl KnownTypesCore { profunctor, foldable, bifoldable, + traversable, + bitraversable, }) } } diff --git a/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap b/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap index a7be6b1f..29001447 100644 --- a/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap +++ b/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap @@ -37,12 +37,12 @@ Valid = Int Errors -RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] -RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] -RecursiveSynonymExpansion { file_id: Idx::(18), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap b/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap index 50ac5685..e4d009ff 100644 --- a/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap +++ b/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap @@ -18,4 +18,4 @@ instance Pair (Int :: Type) chain: 0 Errors -InstanceHeadMismatch { class_file: Idx::(18), class_item: Idx::(0), expected: 2, actual: 1 } at [TermDeclaration(Idx::(2))] +InstanceHeadMismatch { class_file: Idx::(22), class_item: Idx::(0), expected: 2, actual: 1 } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.purs b/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.purs new file mode 100644 index 00000000..6c94b4a7 --- /dev/null +++ b/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.purs @@ -0,0 +1,12 @@ +module Main where + +import Data.Traversable (class Traversable) + +data Identity a = Identity a +derive instance Traversable Identity + +data Maybe a = Nothing | Just a +derive instance Traversable Maybe + +data Const e a = Const e +derive instance Traversable (Const e) diff --git a/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap b/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap new file mode 100644 index 00000000..afd8ca07 --- /dev/null +++ b/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap @@ -0,0 +1,33 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Identity :: forall (a :: Type). a -> Identity a +Nothing :: forall (a :: Type). Maybe a +Just :: forall (a :: Type). a -> Maybe a +Const :: forall (t3 :: Type) (e :: Type) (a :: t3). e -> Const @t3 e a + +Types +Identity :: Type -> Type +Maybe :: Type -> Type +Const :: forall (t3 :: Type). Type -> t3 -> Type + +Data +Identity + Quantified = :0 + Kind = :0 + +Maybe + Quantified = :0 + Kind = :0 + +Const + Quantified = :1 + Kind = :0 + + +Derived +derive Traversable (Identity :: Type -> Type) +derive Traversable (Maybe :: Type -> Type) +derive Traversable (Const @Type &0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.purs b/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.purs new file mode 100644 index 00000000..cc6bb64b --- /dev/null +++ b/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.purs @@ -0,0 +1,6 @@ +module Main where + +import Data.Traversable (class Traversable) + +data Compose f g a = Compose (f (g a)) +derive instance (Traversable f, Traversable g) => Traversable (Compose f g) diff --git a/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap b/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap new file mode 100644 index 00000000..8b6a588a --- /dev/null +++ b/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Compose :: + forall (t5 :: Type) (t9 :: Type) (f :: t5 -> Type) (g :: t9 -> t5) (a :: t9). + f (g a) -> Compose @t9 f g a + +Types +Compose :: forall (t5 :: Type) (t9 :: Type). (t5 -> Type) -> (t9 -> t5) -> t9 -> Type + +Data +Compose + Quantified = :2 + Kind = :0 + + +Derived +derive (Traversable &0, Traversable &1) => Traversable (Compose @Type @Type &0 &1 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.purs b/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.purs new file mode 100644 index 00000000..cc5f2be1 --- /dev/null +++ b/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.purs @@ -0,0 +1,9 @@ +module Main where + +import Data.Bitraversable (class Bitraversable) + +data Either a b = Left a | Right b +derive instance Bitraversable Either + +data Pair a b = Pair a b +derive instance Bitraversable Pair diff --git a/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.snap b/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.snap new file mode 100644 index 00000000..a452764d --- /dev/null +++ b/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.snap @@ -0,0 +1,26 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Left :: forall (a :: Type) (b :: Type). a -> Either a b +Right :: forall (a :: Type) (b :: Type). b -> Either a b +Pair :: forall (a :: Type) (b :: Type). a -> b -> Pair a b + +Types +Either :: Type -> Type -> Type +Pair :: Type -> Type -> Type + +Data +Either + Quantified = :0 + Kind = :0 + +Pair + Quantified = :0 + Kind = :0 + + +Derived +derive Bitraversable (Either :: Type -> Type -> Type) +derive Bitraversable (Pair :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.purs b/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.purs new file mode 100644 index 00000000..ebb3e5bc --- /dev/null +++ b/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Traversable (class Traversable) +import Data.Bitraversable (class Bitraversable) + +data WrapBoth f g a b = WrapBoth (f a) (g b) +derive instance (Traversable f, Traversable g) => Bitraversable (WrapBoth f g) diff --git a/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap b/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap new file mode 100644 index 00000000..ba36ab68 --- /dev/null +++ b/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +WrapBoth :: + forall (t6 :: Type) (t10 :: Type) (f :: t6 -> Type) (g :: t10 -> Type) (a :: t6) (b :: t10). + f a -> g b -> WrapBoth @t6 @t10 f g a b + +Types +WrapBoth :: forall (t6 :: Type) (t10 :: Type). (t6 -> Type) -> (t10 -> Type) -> t6 -> t10 -> Type + +Data +WrapBoth + Quantified = :2 + Kind = :0 + + +Derived +derive (Traversable &0, Traversable &1) => Bitraversable (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/prelude/Control.Applicative.purs b/tests-integration/fixtures/checking/prelude/Control.Applicative.purs new file mode 100644 index 00000000..ebf69273 --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Control.Applicative.purs @@ -0,0 +1,6 @@ +module Control.Applicative where + +import Control.Apply (class Apply) + +class Apply f <= Applicative f where + pure :: forall a. a -> f a diff --git a/tests-integration/fixtures/checking/prelude/Control.Apply.purs b/tests-integration/fixtures/checking/prelude/Control.Apply.purs new file mode 100644 index 00000000..cf319bbe --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Control.Apply.purs @@ -0,0 +1,6 @@ +module Control.Apply where + +import Data.Functor (class Functor) + +class Functor f <= Apply f where + apply :: forall a b. f (a -> b) -> f a -> f b diff --git a/tests-integration/fixtures/checking/prelude/Data.Bitraversable.purs b/tests-integration/fixtures/checking/prelude/Data.Bitraversable.purs new file mode 100644 index 00000000..ee07681e --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Data.Bitraversable.purs @@ -0,0 +1,10 @@ +module Data.Bitraversable where + +import Data.Bifunctor (class Bifunctor) +import Data.Bifoldable (class Bifoldable) +import Data.Traversable (class Traversable) +import Control.Applicative (class Applicative) + +class (Bifunctor t, Bifoldable t) <= Bitraversable t where + bitraverse :: forall f a b c d. Applicative f => (a -> f c) -> (b -> f d) -> t a b -> f (t c d) + bisequence :: forall f a b. Applicative f => t (f a) (f b) -> f (t a b) diff --git a/tests-integration/fixtures/checking/prelude/Data.Traversable.purs b/tests-integration/fixtures/checking/prelude/Data.Traversable.purs new file mode 100644 index 00000000..4ed5999d --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Data.Traversable.purs @@ -0,0 +1,9 @@ +module Data.Traversable where + +import Data.Functor (class Functor) +import Data.Foldable (class Foldable) +import Control.Applicative (class Applicative) + +class (Functor t, Foldable t) <= Traversable t where + traverse :: forall a b m. Applicative m => (a -> m b) -> t a -> m (t b) + sequence :: forall a m. Applicative m => t (m a) -> m (t a) diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 6b473eee..844fb483 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -333,3 +333,11 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_160_derive_bifoldable_simple_main() { run_test("160_derive_bifoldable_simple", "Main"); } #[rustfmt::skip] #[test] fn test_161_derive_bifoldable_higher_kinded_main() { run_test("161_derive_bifoldable_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_162_derive_traversable_simple_main() { run_test("162_derive_traversable_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_163_derive_traversable_higher_kinded_main() { run_test("163_derive_traversable_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_164_derive_bitraversable_simple_main() { run_test("164_derive_bitraversable_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_165_derive_bitraversable_higher_kinded_main() { run_test("165_derive_bitraversable_higher_kinded", "Main"); } From dd70932ef25effb19d0e62248a471a2f795b78f3 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 20:33:47 +0800 Subject: [PATCH 27/62] Stylistic import changes --- compiler-core/checking/src/algorithm/derive/contravariant.rs | 4 ++-- compiler-core/checking/src/algorithm/derive/foldable.rs | 4 ++-- compiler-core/checking/src/algorithm/derive/functor.rs | 4 ++-- compiler-core/checking/src/algorithm/derive/traversable.rs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler-core/checking/src/algorithm/derive/contravariant.rs b/compiler-core/checking/src/algorithm/derive/contravariant.rs index da54dc6b..123e9369 100644 --- a/compiler-core/checking/src/algorithm/derive/contravariant.rs +++ b/compiler-core/checking/src/algorithm/derive/contravariant.rs @@ -2,9 +2,9 @@ use building_types::QueryResult; -use super::variance::{Variance, VarianceConfig, generate_variance_constraints}; -use super::tools; use crate::ExternalQueries; +use crate::algorithm::derive::tools; +use crate::algorithm::derive::variance::{Variance, VarianceConfig, generate_variance_constraints}; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::transfer; use crate::error::ErrorKind; diff --git a/compiler-core/checking/src/algorithm/derive/foldable.rs b/compiler-core/checking/src/algorithm/derive/foldable.rs index 6b5a6c5d..2c2129bc 100644 --- a/compiler-core/checking/src/algorithm/derive/foldable.rs +++ b/compiler-core/checking/src/algorithm/derive/foldable.rs @@ -2,9 +2,9 @@ use building_types::QueryResult; -use super::variance::{Variance, VarianceConfig, generate_variance_constraints}; -use super::tools; use crate::ExternalQueries; +use crate::algorithm::derive::tools; +use crate::algorithm::derive::variance::{Variance, VarianceConfig, generate_variance_constraints}; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::transfer; use crate::error::ErrorKind; diff --git a/compiler-core/checking/src/algorithm/derive/functor.rs b/compiler-core/checking/src/algorithm/derive/functor.rs index d7944ee2..c99670b3 100644 --- a/compiler-core/checking/src/algorithm/derive/functor.rs +++ b/compiler-core/checking/src/algorithm/derive/functor.rs @@ -2,9 +2,9 @@ use building_types::QueryResult; -use super::variance::{Variance, VarianceConfig, generate_variance_constraints}; -use super::tools; use crate::ExternalQueries; +use crate::algorithm::derive::tools; +use crate::algorithm::derive::variance::{Variance, VarianceConfig, generate_variance_constraints}; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::transfer; use crate::error::ErrorKind; diff --git a/compiler-core/checking/src/algorithm/derive/traversable.rs b/compiler-core/checking/src/algorithm/derive/traversable.rs index 58ab720d..1cdd71df 100644 --- a/compiler-core/checking/src/algorithm/derive/traversable.rs +++ b/compiler-core/checking/src/algorithm/derive/traversable.rs @@ -2,9 +2,9 @@ use building_types::QueryResult; -use super::tools; -use super::variance::{Variance, VarianceConfig, generate_variance_constraints}; use crate::ExternalQueries; +use crate::algorithm::derive::tools; +use crate::algorithm::derive::variance::{Variance, VarianceConfig, generate_variance_constraints}; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::transfer; use crate::error::ErrorKind; From d210b0a205984bfa745ffc7f05c9b5872378f596 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Tue, 13 Jan 2026 22:02:03 +0800 Subject: [PATCH 28/62] Consider superclass constraints when deriving This commit also changes the instance chain search in the constraint solver to properly consider instances that appear in the files of any Type::Constructor involved in a constraint being solved. --- compiler-core/checking/src/algorithm.rs | 3 + .../checking/src/algorithm/constraint.rs | 91 ++++++++-------- .../checking/src/algorithm/derive.rs | 1 + .../src/algorithm/derive/contravariant.rs | 2 + .../checking/src/algorithm/derive/foldable.rs | 2 + .../checking/src/algorithm/derive/functor.rs | 2 + .../checking/src/algorithm/derive/tools.rs | 45 +++++++- .../src/algorithm/derive/traversable.rs | 2 + compiler-core/checking/src/algorithm/fold.rs | 2 - .../checking/src/algorithm/substitute.rs | 30 ++++++ compiler-core/checking/src/algorithm/visit.rs | 102 ++++++++++++++++++ .../checking/134_derive_ord_simple/Main.snap | 1 - .../135_derive_ord_1_higher_kinded/Main.snap | 1 + .../136_derive_nested_higher_kinded/Main.snap | 2 - .../162_derive_traversable_simple/Main.purs | 8 ++ .../162_derive_traversable_simple/Main.snap | 6 ++ .../Main.purs | 4 + .../Main.snap | 2 + .../164_derive_bitraversable_simple/Main.purs | 6 ++ .../164_derive_bitraversable_simple/Main.snap | 4 + .../Main.purs | 6 ++ .../Main.snap | 2 + .../Main.purs | 7 ++ .../Main.snap | 24 +++++ tests-integration/tests/checking/generated.rs | 2 + 25 files changed, 305 insertions(+), 52 deletions(-) create mode 100644 compiler-core/checking/src/algorithm/visit.rs create mode 100644 tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.purs create mode 100644 tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.snap diff --git a/compiler-core/checking/src/algorithm.rs b/compiler-core/checking/src/algorithm.rs index f800d0b8..73b1f6ee 100644 --- a/compiler-core/checking/src/algorithm.rs +++ b/compiler-core/checking/src/algorithm.rs @@ -18,6 +18,9 @@ pub mod derive; /// Implements type folding for traversals that modify. pub mod fold; +/// Implements type visiting for read-only traversals. +pub mod visit; + /// Implements type signature inspection. pub mod inspect; diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index 0627b141..8f845e64 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -15,10 +15,11 @@ use building_types::QueryResult; use files::FileId; use indexing::TypeItemId; use itertools::Itertools; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use crate::algorithm::fold::{FoldAction, TypeFold, fold_type}; use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::visit::CollectFileReferences; use crate::algorithm::{transfer, unification}; use crate::core::{Class, Instance, InstanceKind, Variable, debruijn}; use crate::{CheckedModule, ExternalQueries, Type, TypeId}; @@ -77,8 +78,7 @@ where } } - let instance_chains = - collect_instance_chains(state, context, application.file_id, application.item_id)?; + let instance_chains = collect_instance_chains(state, context, &application)?; for chain in instance_chains { 'chain: for instance in chain { @@ -221,68 +221,67 @@ where aux(state, context, constraint, constraints, &mut seen) } +/// Collects instance chains for a constraint from all eligible files. fn collect_instance_chains( state: &mut CheckState, context: &CheckContext, - file_id: FileId, - item_id: TypeItemId, + application: &ConstraintApplication, ) -> QueryResult>> where Q: ExternalQueries, { - let (instances, derived) = if file_id == context.id { - let instances = state - .checked - .instances - .values() - .filter(|instance| instance.resolution == (file_id, item_id)) - .cloned() - .collect_vec(); - let derived = state - .checked - .derived - .values() - .filter(|instance| instance.resolution == (file_id, item_id)) - .cloned() - .collect_vec(); - (instances, derived) - } else { - let checked = context.queries.checked(file_id)?; - let instances = checked - .instances - .values() - .filter(|instance| instance.resolution == (file_id, item_id)) - .cloned() - .collect_vec(); - let derived = checked - .derived - .values() - .filter(|instance| instance.resolution == (file_id, item_id)) - .cloned() - .collect_vec(); - (instances, derived) + let class_file = application.file_id; + let class_id = application.item_id; + + let mut files_to_search = FxHashSet::default(); + files_to_search.insert(class_file); + + for &argument in &application.arguments { + CollectFileReferences::on(state, argument, &mut files_to_search); + } + + let mut all_instances = vec![]; + + let mut copy_from = |checked: &CheckedModule| { + let instances = checked.instances.values(); + let derived = checked.derived.values(); + + let matching = iter::chain(instances, derived) + .filter(|instance| instance.resolution == (class_file, class_id)) + .cloned(); + + all_instances.extend(matching); }; - let mut grouped: FxHashMap<_, Vec<_>> = FxHashMap::default(); - for instance in instances { + for &file_id in &files_to_search { + if file_id == context.id { + copy_from(&state.checked); + } else { + let checked = context.queries.checked(file_id)?; + copy_from(&checked); + } + } + + let mut chain_map: FxHashMap<_, Vec<_>> = FxHashMap::default(); + let mut all_chains: Vec> = vec![]; + + for instance in all_instances { if let InstanceKind::Chain { id, .. } = instance.kind { - grouped.entry(id).or_default().push(instance); + chain_map.entry(id).or_default().push(instance) + } else { + all_chains.push(vec![instance]) } } - let mut result: Vec> = grouped.into_values().collect(); - for chain in &mut result { + for (_, mut chain) in chain_map { chain.sort_by_key(|instance| match instance.kind { InstanceKind::Chain { position, .. } => position, InstanceKind::Derive => 0, }); + all_chains.push(chain); } - for instance in derived { - result.push(vec![instance]); - } - - Ok(result) + Ok(all_chains) } fn localize_class(state: &mut CheckState, context: &CheckContext, class: &Class) -> Class diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index 54770a06..229340e4 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -149,6 +149,7 @@ where let class = (input.class_file, input.class_id); tools::push_given_constraints(state, &input.constraints); + tools::emit_superclass_constraints(state, context, &input)?; tools::register_derived_instance(state, context, input); generate_field_constraints(state, context, data_file, data_id, derived_type, class)?; diff --git a/compiler-core/checking/src/algorithm/derive/contravariant.rs b/compiler-core/checking/src/algorithm/derive/contravariant.rs index 123e9369..59426a0f 100644 --- a/compiler-core/checking/src/algorithm/derive/contravariant.rs +++ b/compiler-core/checking/src/algorithm/derive/contravariant.rs @@ -36,6 +36,7 @@ where let contravariant = Some((input.class_file, input.class_id)); tools::push_given_constraints(state, &input.constraints); + tools::emit_superclass_constraints(state, context, &input)?; tools::register_derived_instance(state, context, input); let config = VarianceConfig::Single((Variance::Contravariant, contravariant)); @@ -73,6 +74,7 @@ where let contravariant = context.known_types.contravariant; let functor = context.known_types.functor; tools::push_given_constraints(state, &input.constraints); + tools::emit_superclass_constraints(state, context, &input)?; tools::register_derived_instance(state, context, input); let config = VarianceConfig::Pair( diff --git a/compiler-core/checking/src/algorithm/derive/foldable.rs b/compiler-core/checking/src/algorithm/derive/foldable.rs index 2c2129bc..ff9005c8 100644 --- a/compiler-core/checking/src/algorithm/derive/foldable.rs +++ b/compiler-core/checking/src/algorithm/derive/foldable.rs @@ -36,6 +36,7 @@ where let foldable = Some((input.class_file, input.class_id)); tools::push_given_constraints(state, &input.constraints); + tools::emit_superclass_constraints(state, context, &input)?; tools::register_derived_instance(state, context, input); let config = VarianceConfig::Single((Variance::Covariant, foldable)); @@ -72,6 +73,7 @@ where // Bifoldable derivation emits Foldable constraints for wrapped parameters. let foldable = context.known_types.foldable; tools::push_given_constraints(state, &input.constraints); + tools::emit_superclass_constraints(state, context, &input)?; tools::register_derived_instance(state, context, input); let config = diff --git a/compiler-core/checking/src/algorithm/derive/functor.rs b/compiler-core/checking/src/algorithm/derive/functor.rs index c99670b3..43cbc7cc 100644 --- a/compiler-core/checking/src/algorithm/derive/functor.rs +++ b/compiler-core/checking/src/algorithm/derive/functor.rs @@ -36,6 +36,7 @@ where let functor = Some((input.class_file, input.class_id)); tools::push_given_constraints(state, &input.constraints); + tools::emit_superclass_constraints(state, context, &input)?; tools::register_derived_instance(state, context, input); let config = VarianceConfig::Single((Variance::Covariant, functor)); @@ -72,6 +73,7 @@ where // Bifunctor derivation emits Functor constraints for wrapped parameters. let functor = context.known_types.functor; tools::push_given_constraints(state, &input.constraints); + tools::emit_superclass_constraints(state, context, &input)?; tools::register_derived_instance(state, context, input); let config = diff --git a/compiler-core/checking/src/algorithm/derive/tools.rs b/compiler-core/checking/src/algorithm/derive/tools.rs index 76beede7..7753b407 100644 --- a/compiler-core/checking/src/algorithm/derive/tools.rs +++ b/compiler-core/checking/src/algorithm/derive/tools.rs @@ -5,9 +5,11 @@ use files::FileId; use indexing::{DeriveId, TermItemId, TypeItemId}; use itertools::Itertools; +use rustc_hash::FxHashMap; + use crate::ExternalQueries; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::{quantify, transfer}; +use crate::algorithm::{constraint, quantify, substitute, transfer}; use crate::core::{Instance, InstanceKind, Type, TypeId, debruijn}; use crate::error::ErrorKind; @@ -38,6 +40,47 @@ pub fn push_given_constraints(state: &mut CheckState, constraints: &[(TypeId, Ty } } +/// Emits wanted constraints for the superclasses of the class being derived. +/// +/// When deriving `Traversable (Compose f g)`, this emits: +/// - `Functor (Compose f g)` +/// - `Foldable (Compose f g)` +/// +/// These constraints ensure that the derived instance has all required +/// instances available, which is a pre-requisite for code generation. +pub fn emit_superclass_constraints( + state: &mut CheckState, + context: &CheckContext, + input: &ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some(class_info) = + constraint::lookup_file_class(state, context, input.class_file, input.class_id)? + else { + return Ok(()); + }; + + if class_info.superclasses.is_empty() { + return Ok(()); + } + + let initial_level = class_info.quantified_variables.0 + class_info.kind_variables.0; + let mut bindings = FxHashMap::default(); + for (index, &(argument_type, _)) in input.arguments.iter().enumerate() { + let level = debruijn::Level(initial_level + index as u32); + bindings.insert(level, argument_type); + } + + for &(superclass, _) in class_info.superclasses.iter() { + let specialized = substitute::SubstituteBindings::on(state, &bindings, superclass); + state.constraints.push_wanted(specialized); + } + + Ok(()) +} + /// Solves constraints and reports any unsatisfied constraints as errors. pub fn solve_and_report_constraints( state: &mut CheckState, diff --git a/compiler-core/checking/src/algorithm/derive/traversable.rs b/compiler-core/checking/src/algorithm/derive/traversable.rs index 1cdd71df..b59e9d14 100644 --- a/compiler-core/checking/src/algorithm/derive/traversable.rs +++ b/compiler-core/checking/src/algorithm/derive/traversable.rs @@ -36,6 +36,7 @@ where let traversable = Some((input.class_file, input.class_id)); tools::push_given_constraints(state, &input.constraints); + tools::emit_superclass_constraints(state, context, &input)?; tools::register_derived_instance(state, context, input); let config = VarianceConfig::Single((Variance::Covariant, traversable)); @@ -72,6 +73,7 @@ where // Bitraversable derivation emits Traversable constraints for wrapped parameters. let traversable = context.known_types.traversable; tools::push_given_constraints(state, &input.constraints); + tools::emit_superclass_constraints(state, context, &input)?; tools::register_derived_instance(state, context, input); let config = VarianceConfig::Pair( diff --git a/compiler-core/checking/src/algorithm/fold.rs b/compiler-core/checking/src/algorithm/fold.rs index dee50daa..1294d787 100644 --- a/compiler-core/checking/src/algorithm/fold.rs +++ b/compiler-core/checking/src/algorithm/fold.rs @@ -5,9 +5,7 @@ use crate::core::{ForallBinder, RowType, Type, TypeId}; /// Controls behavior during type folding. pub enum FoldAction { - /// Replace this node entirely (skip recursion) Replace(TypeId), - /// Continue with default recursion Continue, } diff --git a/compiler-core/checking/src/algorithm/substitute.rs b/compiler-core/checking/src/algorithm/substitute.rs index 3ef52bae..3f0f94a5 100644 --- a/compiler-core/checking/src/algorithm/substitute.rs +++ b/compiler-core/checking/src/algorithm/substitute.rs @@ -134,3 +134,33 @@ impl TypeFold for SubstituteUnification<'_> { FoldAction::Continue } } + +pub type LevelToType = FxHashMap; + +pub struct SubstituteBindings<'a> { + bindings: &'a LevelToType, +} + +impl SubstituteBindings<'_> { + /// Substitutes bound and implicit variables using a level-based mapping. + /// + /// This is used to specialize class superclasses with instance arguments. + /// For example, when deriving `Traversable (Compose f g)`, the superclass + /// `Functor t` becomes `Functor (Compose f g)` by binding `t`'s level to + /// `Compose f g`. + pub fn on(state: &mut CheckState, bindings: &LevelToType, id: TypeId) -> TypeId { + fold_type(state, id, &mut SubstituteBindings { bindings }) + } +} + +impl TypeFold for SubstituteBindings<'_> { + fn transform(&mut self, _state: &mut CheckState, id: TypeId, t: &Type) -> FoldAction { + match t { + Type::Variable(Variable::Implicit(level) | Variable::Bound(level)) => { + let id = self.bindings.get(level).copied().unwrap_or(id); + FoldAction::Replace(id) + } + _ => FoldAction::Continue, + } + } +} diff --git a/compiler-core/checking/src/algorithm/visit.rs b/compiler-core/checking/src/algorithm/visit.rs new file mode 100644 index 00000000..02b3fd9f --- /dev/null +++ b/compiler-core/checking/src/algorithm/visit.rs @@ -0,0 +1,102 @@ +use files::FileId; +use rustc_hash::FxHashSet; + +use crate::algorithm::state::CheckState; +use crate::core::{ForallBinder, RowType, Type, TypeId}; + +/// Controls behavior during type visiting. +pub enum VisitAction { + Stop, + Continue, +} + +/// Trait for implementing read-only type traversal. +pub trait TypeVisitor { + /// Called before recursing into a type. Return `Stop` to short-circuit. + fn visit(&mut self, state: &mut CheckState, id: TypeId, t: &Type) -> VisitAction; + + /// Called when visiting a Forall binder. + fn visit_binder(&mut self, _binder: &ForallBinder) {} +} + +/// Collects all files referenced through [`Type::Constructor`]. +pub struct CollectFileReferences<'a> { + pub files: &'a mut FxHashSet, +} + +impl CollectFileReferences<'_> { + pub fn on(state: &mut CheckState, id: TypeId, files: &mut FxHashSet) { + visit_type(state, id, &mut CollectFileReferences { files }); + } +} + +impl TypeVisitor for CollectFileReferences<'_> { + fn visit(&mut self, _state: &mut CheckState, _id: TypeId, t: &Type) -> VisitAction { + if let Type::Constructor(file_id, _) = t { + self.files.insert(*file_id); + } + VisitAction::Continue + } +} + +/// Recursively visit a type without transforming it. +pub fn visit_type(state: &mut CheckState, id: TypeId, visitor: &mut V) { + let id = state.normalize_type(id); + let ty = state.storage[id].clone(); + + if let VisitAction::Stop = visitor.visit(state, id, &ty) { + return; + } + + match ty { + Type::Application(function, argument) => { + visit_type(state, function, visitor); + visit_type(state, argument, visitor); + } + Type::Constrained(constraint, inner) => { + visit_type(state, constraint, visitor); + visit_type(state, inner, visitor); + } + Type::Constructor(_, _) => {} + Type::Forall(binder, inner) => { + visitor.visit_binder(&binder); + visit_type(state, binder.kind, visitor); + visit_type(state, inner, visitor); + } + Type::Function(argument, result) => { + visit_type(state, argument, visitor); + visit_type(state, result, visitor); + } + Type::Integer(_) => {} + Type::KindApplication(function, argument) => { + visit_type(state, function, visitor); + visit_type(state, argument, visitor); + } + Type::Kinded(inner, kind) => { + visit_type(state, inner, visitor); + visit_type(state, kind, visitor); + } + Type::Operator(_, _) => {} + Type::OperatorApplication(_, _, left, right) => { + visit_type(state, left, visitor); + visit_type(state, right, visitor); + } + Type::Row(RowType { fields, tail }) => { + for field in fields.iter() { + visit_type(state, field.id, visitor); + } + if let Some(tail) = tail { + visit_type(state, tail, visitor); + } + } + Type::String(_, _) => {} + Type::SynonymApplication(_, _, _, arguments) => { + for &argument in arguments.iter() { + visit_type(state, argument, visitor); + } + } + Type::Unification(_) => {} + Type::Variable(_) => {} + Type::Unknown => {} + } +} diff --git a/tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap b/tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap index 439d94fd..2d58992c 100644 --- a/tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap +++ b/tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap @@ -42,5 +42,4 @@ derive Eq (ContainsNoOrd :: Type) derive Ord (ContainsNoOrd :: Type) Errors -NoInstanceFound { Eq NoOrd } at [TermDeclaration(Idx::(9))] NoInstanceFound { Ord NoOrd } at [TermDeclaration(Idx::(10))] diff --git a/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap index fe4107be..78625c0b 100644 --- a/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap @@ -28,3 +28,4 @@ derive Ord &1 => Ord (WrapNoOrd1 @Type &0 &1 :: Type) Errors NoInstanceFound { Ord1 &0 } at [TermDeclaration(Idx::(5))] +NoInstanceFound { Eq1 &0 } at [TermDeclaration(Idx::(5))] diff --git a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap index 9ac9f6d1..593d55f2 100644 --- a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap @@ -46,7 +46,5 @@ derive Ord1 &0 => Ord (U &0 :: Type) Errors NoInstanceFound { Eq1 &1 } at [TermDeclaration(Idx::(1))] NoInstanceFound { Ord1 &1 } at [TermDeclaration(Idx::(2))] -NoInstanceFound { Eq (Maybe Int) } at [TermDeclaration(Idx::(8))] -NoInstanceFound { Ord (Maybe Int) } at [TermDeclaration(Idx::(9))] NoInstanceFound { Eq (&1 42) } at [TermDeclaration(Idx::(11))] NoInstanceFound { Ord (&1 42) } at [TermDeclaration(Idx::(12))] diff --git a/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.purs b/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.purs index 6c94b4a7..a374bd51 100644 --- a/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.purs +++ b/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.purs @@ -1,12 +1,20 @@ module Main where +import Data.Functor (class Functor) +import Data.Foldable (class Foldable) import Data.Traversable (class Traversable) data Identity a = Identity a +derive instance Functor Identity +derive instance Foldable Identity derive instance Traversable Identity data Maybe a = Nothing | Just a +derive instance Functor Maybe +derive instance Foldable Maybe derive instance Traversable Maybe data Const e a = Const e +derive instance Functor (Const e) +derive instance Foldable (Const e) derive instance Traversable (Const e) diff --git a/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap b/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap index afd8ca07..bfae14f5 100644 --- a/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap +++ b/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap @@ -28,6 +28,12 @@ Const Derived +derive Functor (Identity :: Type -> Type) +derive Foldable (Identity :: Type -> Type) derive Traversable (Identity :: Type -> Type) +derive Functor (Maybe :: Type -> Type) +derive Foldable (Maybe :: Type -> Type) derive Traversable (Maybe :: Type -> Type) +derive Functor (Const @Type &0 :: Type -> Type) +derive Foldable (Const @Type &0 :: Type -> Type) derive Traversable (Const @Type &0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.purs b/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.purs index cc6bb64b..d2402adf 100644 --- a/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.purs +++ b/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.purs @@ -1,6 +1,10 @@ module Main where +import Data.Functor (class Functor) +import Data.Foldable (class Foldable) import Data.Traversable (class Traversable) data Compose f g a = Compose (f (g a)) +derive instance (Functor f, Functor g) => Functor (Compose f g) +derive instance (Foldable f, Foldable g) => Foldable (Compose f g) derive instance (Traversable f, Traversable g) => Traversable (Compose f g) diff --git a/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap b/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap index 8b6a588a..7fc586bb 100644 --- a/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap @@ -17,4 +17,6 @@ Compose Derived +derive (Functor &0, Functor &1) => Functor (Compose @Type @Type &0 &1 :: Type -> Type) +derive (Foldable &0, Foldable &1) => Foldable (Compose @Type @Type &0 &1 :: Type -> Type) derive (Traversable &0, Traversable &1) => Traversable (Compose @Type @Type &0 &1 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.purs b/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.purs index cc5f2be1..9f0dc4ba 100644 --- a/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.purs +++ b/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.purs @@ -1,9 +1,15 @@ module Main where +import Data.Bifunctor (class Bifunctor) +import Data.Bifoldable (class Bifoldable) import Data.Bitraversable (class Bitraversable) data Either a b = Left a | Right b +derive instance Bifunctor Either +derive instance Bifoldable Either derive instance Bitraversable Either data Pair a b = Pair a b +derive instance Bifunctor Pair +derive instance Bifoldable Pair derive instance Bitraversable Pair diff --git a/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.snap b/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.snap index a452764d..e8f14c9e 100644 --- a/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.snap +++ b/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.snap @@ -22,5 +22,9 @@ Pair Derived +derive Bifunctor (Either :: Type -> Type -> Type) +derive Bifoldable (Either :: Type -> Type -> Type) derive Bitraversable (Either :: Type -> Type -> Type) +derive Bifunctor (Pair :: Type -> Type -> Type) +derive Bifoldable (Pair :: Type -> Type -> Type) derive Bitraversable (Pair :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.purs b/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.purs index ebb3e5bc..00777010 100644 --- a/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.purs +++ b/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.purs @@ -1,7 +1,13 @@ module Main where +import Data.Functor (class Functor) +import Data.Bifunctor (class Bifunctor) +import Data.Foldable (class Foldable) +import Data.Bifoldable (class Bifoldable) import Data.Traversable (class Traversable) import Data.Bitraversable (class Bitraversable) data WrapBoth f g a b = WrapBoth (f a) (g b) +derive instance (Functor f, Functor g) => Bifunctor (WrapBoth f g) +derive instance (Foldable f, Foldable g) => Bifoldable (WrapBoth f g) derive instance (Traversable f, Traversable g) => Bitraversable (WrapBoth f g) diff --git a/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap b/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap index ba36ab68..d01f925e 100644 --- a/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap @@ -17,4 +17,6 @@ WrapBoth Derived +derive (Functor &0, Functor &1) => Bifunctor (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) +derive (Foldable &0, Foldable &1) => Bifoldable (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) derive (Traversable &0, Traversable &1) => Bitraversable (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.purs b/tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.purs new file mode 100644 index 00000000..18134ca6 --- /dev/null +++ b/tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Traversable (class Traversable) + +-- This should fail because Functor and Foldable instances are missing. +data Compose f g a = Compose (f (g a)) +derive instance (Traversable f, Traversable g) => Traversable (Compose f g) diff --git a/tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.snap b/tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.snap new file mode 100644 index 00000000..11ae2bb4 --- /dev/null +++ b/tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.snap @@ -0,0 +1,24 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Compose :: + forall (t5 :: Type) (t9 :: Type) (f :: t5 -> Type) (g :: t9 -> t5) (a :: t9). + f (g a) -> Compose @t9 f g a + +Types +Compose :: forall (t5 :: Type) (t9 :: Type). (t5 -> Type) -> (t9 -> t5) -> t9 -> Type + +Data +Compose + Quantified = :2 + Kind = :0 + + +Derived +derive (Traversable &0, Traversable &1) => Traversable (Compose @Type @Type &0 &1 :: Type -> Type) + +Errors +NoInstanceFound { Functor (Compose @Type @Type &0 &1) } at [TermDeclaration(Idx::(1))] +NoInstanceFound { Foldable (Compose @Type @Type &0 &1) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 844fb483..15ec96de 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -341,3 +341,5 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_164_derive_bitraversable_simple_main() { run_test("164_derive_bitraversable_simple", "Main"); } #[rustfmt::skip] #[test] fn test_165_derive_bitraversable_higher_kinded_main() { run_test("165_derive_bitraversable_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_166_derive_traversable_missing_superclass_main() { run_test("166_derive_traversable_missing_superclass", "Main"); } From 3d262f41876f85fb753e053b2beb4db8f691e01e Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 14 Jan 2026 02:09:50 +0800 Subject: [PATCH 29/62] Remove Variable::Implicit and fix invalid levels The removal of Variable::Implicit uncovered a bug in instance checking where promote_type began throwing an escaped skolem error in previously valid types. Variable::Implicit was excluded from this check, and it turns out that the instance head checking was producing invalid ones. Instance head checking would use ShiftImplicit to correctly adjust the levels after generalisation. The problem was that in instance member checking, this was not applied; thus, the unification of Implicit and Bound exposed the issue through promote_type. --- compiler-core/checking/src/algorithm.rs | 2 + .../checking/src/algorithm/constraint.rs | 41 ++++++----------- .../src/algorithm/derive/higher_kinded.rs | 4 +- .../checking/src/algorithm/derive/tools.rs | 2 +- compiler-core/checking/src/algorithm/kind.rs | 4 +- .../checking/src/algorithm/quantify.rs | 10 +++- compiler-core/checking/src/algorithm/state.rs | 7 +++ .../checking/src/algorithm/substitute.rs | 6 +-- .../checking/src/algorithm/term_item.rs | 30 ++++++++---- .../checking/src/algorithm/unification.rs | 33 ++----------- compiler-core/checking/src/core.rs | 3 +- compiler-core/checking/src/core/debruijn.rs | 5 ++ compiler-core/checking/src/core/pretty.rs | 1 - .../checking/097_instance_chains/Main.snap | 4 +- .../Main.snap | 2 +- .../Main.snap | 2 +- .../checking/126_instance_phantom/Main.snap | 2 +- .../checking/127_derive_eq_simple/Main.snap | 2 +- tests-integration/src/generated/basic.rs | 46 ++++++++++++++++++- 19 files changed, 118 insertions(+), 88 deletions(-) diff --git a/compiler-core/checking/src/algorithm.rs b/compiler-core/checking/src/algorithm.rs index 73b1f6ee..22257422 100644 --- a/compiler-core/checking/src/algorithm.rs +++ b/compiler-core/checking/src/algorithm.rs @@ -252,6 +252,7 @@ where let instance_arguments = instance.arguments.clone(); let instance_constraints = instance.constraints.clone(); + let kind_variables = instance.kind_variables.clone(); let check_members = term_item::CheckInstanceMembers { instance_id: item_id, @@ -260,6 +261,7 @@ where class_id, instance_arguments: &instance_arguments, instance_constraints: &instance_constraints, + kind_variables: &kind_variables, }; term_item::check_instance_members(state, context, check_members)?; diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index 8f845e64..fab8b694 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -485,23 +485,19 @@ fn match_type( let given_core = &state.storage[given]; match (wanted_core, given_core) { - (_, Type::Variable(variable)) => { - if let Variable::Implicit(level) | Variable::Bound(level) = variable { - if let Some(&bound) = bindings.get(level) { - match can_unify(state, wanted, bound) { - CanUnify::Equal => MatchType::Match, - CanUnify::Apart => MatchType::Apart, - CanUnify::Unify => { - equalities.push((wanted, bound)); - MatchType::Match - } + (_, Type::Variable(Variable::Bound(level))) => { + if let Some(&bound) = bindings.get(level) { + match can_unify(state, wanted, bound) { + CanUnify::Equal => MatchType::Match, + CanUnify::Apart => MatchType::Apart, + CanUnify::Unify => { + equalities.push((wanted, bound)); + MatchType::Match } - } else { - bindings.insert(*level, wanted); - MatchType::Match } } else { - MatchType::Apart + bindings.insert(*level, wanted); + MatchType::Match } } @@ -612,15 +608,6 @@ fn match_given_type(state: &mut CheckState, wanted: TypeId, given: TypeId) -> Ma } } - // Implicit and Bound variables at the same level represent the same - // logical variable; Implicit binds the variable, Bound consumes it. - (Type::Variable(Variable::Implicit(w_level)), Type::Variable(Variable::Bound(g_level))) - | (Type::Variable(Variable::Bound(w_level)), Type::Variable(Variable::Implicit(g_level))) - if w_level == g_level => - { - MatchType::Match - } - _ => MatchType::Apart, } } @@ -628,9 +615,9 @@ fn match_given_type(state: &mut CheckState, wanted: TypeId, given: TypeId) -> Ma /// Determines if two types [`CanUnify`]. /// /// This is used in [`match_type`], where if two different types bind to the -/// same [`Variable::Implicit`] or [`Variable::Bound`] variable, we determine -/// if the types can actually unify before generating an equality. This is -/// effectively a pure version of the [`unify`] function. +/// same [`Variable::Bound`] variable, we determine if the types can actually +/// unify before generating an equality. This is effectively a pure version +/// of the [`unify`] function. /// /// [`unify`]: crate::algorithm::unification::unify fn can_unify(state: &mut CheckState, t1: TypeId, t2: TypeId) -> CanUnify { @@ -899,7 +886,7 @@ impl<'a> ApplyBindings<'a> { impl TypeFold for ApplyBindings<'_> { fn transform(&mut self, _state: &mut CheckState, id: TypeId, t: &Type) -> FoldAction { match t { - Type::Variable(Variable::Implicit(level) | Variable::Bound(level)) => { + Type::Variable(Variable::Bound(level)) => { let id = self.bindings.get(level).copied().unwrap_or(id); FoldAction::Replace(id) } diff --git a/compiler-core/checking/src/algorithm/derive/higher_kinded.rs b/compiler-core/checking/src/algorithm/derive/higher_kinded.rs index 6433c9a0..3af1ad15 100644 --- a/compiler-core/checking/src/algorithm/derive/higher_kinded.rs +++ b/compiler-core/checking/src/algorithm/derive/higher_kinded.rs @@ -88,9 +88,7 @@ where fn lookup_variable_kind(state: &CheckState, variable: &Variable) -> Option { match variable { Variable::Skolem(_, kind) => Some(*kind), - Variable::Bound(level) | Variable::Implicit(level) => { - state.type_scope.kinds.get(*level).copied() - } + Variable::Bound(level) => state.type_scope.kinds.get(*level).copied(), Variable::Free(_) => None, } } diff --git a/compiler-core/checking/src/algorithm/derive/tools.rs b/compiler-core/checking/src/algorithm/derive/tools.rs index 7753b407..b2ad20c1 100644 --- a/compiler-core/checking/src/algorithm/derive/tools.rs +++ b/compiler-core/checking/src/algorithm/derive/tools.rs @@ -115,7 +115,7 @@ pub fn register_derived_instance( constraints, resolution: (class_file, class_id), kind: InstanceKind::Derive, - kind_variables: debruijn::Size(0), + kind_variables: vec![], }; quantify::quantify_instance(state, &mut instance); diff --git a/compiler-core/checking/src/algorithm/kind.rs b/compiler-core/checking/src/algorithm/kind.rs index c96b01ed..6367a3d4 100644 --- a/compiler-core/checking/src/algorithm/kind.rs +++ b/compiler-core/checking/src/algorithm/kind.rs @@ -274,7 +274,7 @@ fn infer_implicit_variable( let kind = state.fresh_unification(context); let level = state.type_scope.bind_implicit(implicit.node, implicit.id, kind); - let variable = Variable::Implicit(level); + let variable = Variable::Bound(level); state.storage.intern(Type::Variable(variable)) } else { @@ -504,7 +504,7 @@ where Type::Unification(unification_id) => state.unification.get(unification_id).kind, Type::Variable(ref variable) => match variable { - Variable::Implicit(level) | Variable::Bound(level) => { + Variable::Bound(level) => { state.type_scope.kinds.get(*level).copied().unwrap_or(unknown) } Variable::Skolem(_, kind) => *kind, diff --git a/compiler-core/checking/src/algorithm/quantify.rs b/compiler-core/checking/src/algorithm/quantify.rs index 0279fd9a..7b7ad723 100644 --- a/compiler-core/checking/src/algorithm/quantify.rs +++ b/compiler-core/checking/src/algorithm/quantify.rs @@ -280,6 +280,14 @@ pub fn quantify_instance(state: &mut CheckState, instance: &mut Instance) -> Opt substitutions.insert(id, level); } + let kind_variables = substitutions.iter().map(|(&id, &level)| { + let kind = state.unification.get(id).kind; + (level, kind) + }); + + let kind_variables = kind_variables.sorted_by_key(|(level, _)| *level); + let kind_variables = kind_variables.map(|(_, kind)| kind).collect_vec(); + let arguments = instance.arguments.iter().map(|&(t, k)| { let t = ShiftImplicit::on(state, t, size.0); let t = SubstituteUnification::on(&substitutions, state, t); @@ -300,7 +308,7 @@ pub fn quantify_instance(state: &mut CheckState, instance: &mut Instance) -> Opt instance.constraints = constraints.collect(); - instance.kind_variables = size; + instance.kind_variables = kind_variables; Some(()) } diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index 556fd237..b8eec030 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -37,6 +37,13 @@ impl TypeScope { level } + pub fn bind_core(&mut self, kind: TypeId) -> debruijn::Level { + let variable = debruijn::Variable::Core; + let level = self.bound.bind(variable); + self.kinds.insert(level, kind); + level + } + pub fn lookup_forall(&self, id: TypeVariableBindingId) -> Option { let variable = debruijn::Variable::Forall(id); self.bound.level_of(variable) diff --git a/compiler-core/checking/src/algorithm/substitute.rs b/compiler-core/checking/src/algorithm/substitute.rs index 3f0f94a5..eef90dc2 100644 --- a/compiler-core/checking/src/algorithm/substitute.rs +++ b/compiler-core/checking/src/algorithm/substitute.rs @@ -92,10 +92,6 @@ impl TypeFold for ShiftImplicit { let level = debruijn::Level(level.0 + self.offset); FoldAction::Replace(state.storage.intern(Type::Variable(Variable::Bound(level)))) } - Type::Variable(Variable::Implicit(level)) => { - let level = debruijn::Level(level.0 + self.offset); - FoldAction::Replace(state.storage.intern(Type::Variable(Variable::Implicit(level)))) - } _ => FoldAction::Continue, } } @@ -156,7 +152,7 @@ impl SubstituteBindings<'_> { impl TypeFold for SubstituteBindings<'_> { fn transform(&mut self, _state: &mut CheckState, id: TypeId, t: &Type) -> FoldAction { match t { - Type::Variable(Variable::Implicit(level) | Variable::Bound(level)) => { + Type::Variable(Variable::Bound(level)) => { let id = self.bindings.get(level).copied().unwrap_or(id); FoldAction::Replace(id) } diff --git a/compiler-core/checking/src/algorithm/term_item.rs b/compiler-core/checking/src/algorithm/term_item.rs index c2303545..a7fdb104 100644 --- a/compiler-core/checking/src/algorithm/term_item.rs +++ b/compiler-core/checking/src/algorithm/term_item.rs @@ -155,7 +155,7 @@ where constraints: core_constraints, resolution: (class_file, class_item), kind: InstanceKind::Chain { id: chain_id, position: chain_position }, - kind_variables: debruijn::Size(0), + kind_variables: vec![], }; quantify::quantify_instance(state, &mut instance); @@ -314,6 +314,7 @@ pub struct CheckInstanceMembers<'a> { pub class_id: TypeItemId, pub instance_arguments: &'a [(TypeId, TypeId)], pub instance_constraints: &'a [(TypeId, TypeId)], + pub kind_variables: &'a [TypeId], } /// Checks instance member declarations. @@ -340,6 +341,7 @@ where class_id, instance_arguments, instance_constraints, + kind_variables, } = input; // Fetch implicit variables captured by check_instance. @@ -358,6 +360,7 @@ where class_id, instance_arguments, instance_constraints, + kind_variables, }, )?; } @@ -396,6 +399,7 @@ pub struct CheckInstanceMemberGroup<'a> { class_id: TypeItemId, instance_arguments: &'a [(TypeId, TypeId)], instance_constraints: &'a [(TypeId, TypeId)], + kind_variables: &'a [TypeId], } /// Checks an instance member group against its specialized class member type. @@ -427,12 +431,19 @@ where class_id, instance_arguments, instance_constraints, + kind_variables, } = input; state.with_error_step(ErrorStep::TermDeclaration(instance_id), |state| { // Save the current size of the environment for unbinding. let size = state.type_scope.size(); + // Bind kind variables generalised after instance head checking. + for &kind_variable in kind_variables { + let kind = transfer::localize(state, context, kind_variable); + state.type_scope.bind_core(kind); + } + for binding in instance_bindings { state.type_scope.bind_implicit(binding.node, binding.id, binding.kind); } @@ -477,10 +488,9 @@ where if let Some(specialized_type) = specialized_type { let unified = unification::unify(state, context, member_type, specialized_type)?; if !unified { - state.insert_error(ErrorKind::InstanceMemberTypeMismatch { - expected: specialized_type, - actual: member_type, - }); + let expected = transfer::globalize(state, context, specialized_type); + let actual = transfer::globalize(state, context, member_type); + state.insert_error(ErrorKind::InstanceMemberTypeMismatch { expected, actual }); } } @@ -494,10 +504,9 @@ where let matches = unification::subtype(state, context, inferred_type, specialized_type)?; if !matches { - state.insert_error(ErrorKind::InstanceMemberTypeMismatch { - expected: specialized_type, - actual: inferred_type, - }); + let expected = transfer::globalize(state, context, specialized_type); + let actual = transfer::globalize(state, context, inferred_type); + state.insert_error(ErrorKind::InstanceMemberTypeMismatch { expected, actual }); } let residual = state.solve_constraints(context)?; @@ -555,10 +564,11 @@ where (t, k) }); + let arguments = arguments.collect_vec(); let kind_variables = class_info.quantified_variables.0 + class_info.kind_variables.0; let mut kind_variables = 0..kind_variables; - let mut arguments = arguments.collect_vec().into_iter(); + let mut arguments = arguments.into_iter(); while let normalized = state.normalize_type(specialized) && let Type::Forall(binder, inner) = &state.storage[normalized] diff --git a/compiler-core/checking/src/algorithm/unification.rs b/compiler-core/checking/src/algorithm/unification.rs index ec3aa5cc..c4f54208 100644 --- a/compiler-core/checking/src/algorithm/unification.rs +++ b/compiler-core/checking/src/algorithm/unification.rs @@ -83,17 +83,6 @@ where subtype(state, context, inner, t2) } - // Implicit and Bound variables at the same level represent the same - // logical variable; Implicit binds the variable, Bound consumes it. - ( - Type::Variable(Variable::Implicit(t1_level)), - Type::Variable(Variable::Bound(t2_level)), - ) - | ( - Type::Variable(Variable::Bound(t1_level)), - Type::Variable(Variable::Implicit(t2_level)), - ) => Ok(t1_level == t2_level), - (_, _) => unify(state, context, t1, t2), } } @@ -156,17 +145,6 @@ where solve(state, context, unification_id, t1)?.is_some() } - // Implicit and Bound variables at the same level represent the same - // logical variable; Implicit binds the variable, Bound consumes it. - ( - Type::Variable(Variable::Implicit(t1_level)), - Type::Variable(Variable::Bound(t2_level)), - ) - | ( - Type::Variable(Variable::Bound(t1_level)), - Type::Variable(Variable::Implicit(t2_level)), - ) => t1_level == t2_level, - ( Type::Constrained(t1_constraint, t1_inner), Type::Constrained(t2_constraint, t2_inner), @@ -315,15 +293,14 @@ pub fn promote_type( } Type::Variable(ref variable) => { - let unification = state.unification.get(unification_id); // A bound variable escapes if its level >= the unification variable's domain. // This means the variable was bound at or after the unification was created. - if let Variable::Bound(level) = variable - && level.0 >= unification.domain.0 - { - return false; + if let Variable::Bound(level) = variable { + let unification = state.unification.get(unification_id); + if level.0 >= unification.domain.0 { + return false; + } } - true } diff --git a/compiler-core/checking/src/core.rs b/compiler-core/checking/src/core.rs index 21cb9708..0525d982 100644 --- a/compiler-core/checking/src/core.rs +++ b/compiler-core/checking/src/core.rs @@ -19,7 +19,6 @@ pub struct ForallBinder { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Variable { - Implicit(debruijn::Level), Skolem(debruijn::Level, TypeId), Bound(debruijn::Level), Free(SmolStr), @@ -125,7 +124,7 @@ pub struct Instance { pub constraints: Vec<(TypeId, TypeId)>, pub resolution: (FileId, TypeItemId), pub kind: InstanceKind, - pub kind_variables: debruijn::Size, + pub kind_variables: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/compiler-core/checking/src/core/debruijn.rs b/compiler-core/checking/src/core/debruijn.rs index 33bd124c..e68feede 100644 --- a/compiler-core/checking/src/core/debruijn.rs +++ b/compiler-core/checking/src/core/debruijn.rs @@ -151,6 +151,11 @@ impl Bound { self.inner.get(index as usize).copied() } + /// Returns `true` if the variable at `level` was implicitly bound. + pub fn is_implicit(&self, level: Level) -> bool { + matches!(self.get_level(level), Some(Variable::Implicit { .. })) + } + pub fn iter(&self) -> impl DoubleEndedIterator { self.inner.iter().enumerate().map(|(index, variable)| (Level(index as u32), *variable)) } diff --git a/compiler-core/checking/src/core/pretty.rs b/compiler-core/checking/src/core/pretty.rs index 03ce445c..c0dcec24 100644 --- a/compiler-core/checking/src/core/pretty.rs +++ b/compiler-core/checking/src/core/pretty.rs @@ -478,7 +478,6 @@ fn render_variable<'a>( context: &TraversalContext, ) -> Doc<'a> { match variable { - Variable::Implicit(level) => arena.text(format!("{}", level)), Variable::Skolem(level, _) => arena.text(format!("~{}", level)), Variable::Bound(level) => { let name = context.names.get(&level.0).cloned(); diff --git a/tests-integration/fixtures/checking/097_instance_chains/Main.snap b/tests-integration/fixtures/checking/097_instance_chains/Main.snap index b17561d0..1c9a92b0 100644 --- a/tests-integration/fixtures/checking/097_instance_chains/Main.snap +++ b/tests-integration/fixtures/checking/097_instance_chains/Main.snap @@ -22,7 +22,7 @@ Classes class TypeEq (&3 :: &0) (&4 :: &1) (&5 :: &2) Instances -instance TypeEq (&1 :: &0) (&1 :: &0) (True :: Boolean) +instance forall (&0 :: Type). TypeEq (&1 :: &0) (&1 :: &0) (True :: Boolean) chain: 0 -instance TypeEq (&2 :: &0) (&3 :: &1) (False :: Boolean) +instance forall (&0 :: Type) (&1 :: Type). TypeEq (&2 :: &0) (&3 :: &1) (False :: Boolean) chain: 1 diff --git a/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap b/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap index dcb2c56d..480170c4 100644 --- a/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap +++ b/tests-integration/fixtures/checking/119_instance_member_type_mismatch/Main.snap @@ -18,4 +18,4 @@ instance Show (Int :: Type) Errors CannotUnify { Int, String } at [TermDeclaration(Idx::(1))] CannotUnify { Int -> Int, Int -> String } at [TermDeclaration(Idx::(1))] -InstanceMemberTypeMismatch { expected: Id(32), actual: Id(34) } at [TermDeclaration(Idx::(1))] +InstanceMemberTypeMismatch { expected: Int -> String, actual: Int -> Int } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap b/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap index 963d1011..2c05e19c 100644 --- a/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap +++ b/tests-integration/fixtures/checking/125_instance_member_overly_general/Main.snap @@ -17,4 +17,4 @@ instance Show (Boolean :: Type) Errors CannotUnify { forall (a :: Type). a -> String, Boolean -> String } at [TermDeclaration(Idx::(1))] -InstanceMemberTypeMismatch { expected: Id(32), actual: Id(35) } at [TermDeclaration(Idx::(1))] +InstanceMemberTypeMismatch { expected: Boolean -> String, actual: forall (a :: Type). a -> String } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/126_instance_phantom/Main.snap b/tests-integration/fixtures/checking/126_instance_phantom/Main.snap index 3d67ef7a..88810445 100644 --- a/tests-integration/fixtures/checking/126_instance_phantom/Main.snap +++ b/tests-integration/fixtures/checking/126_instance_phantom/Main.snap @@ -21,5 +21,5 @@ Classes class Phantom (&0 :: Type) Instances -instance Phantom (Proxy @&0 &1 :: Type) +instance forall (&0 :: Type). Phantom (Proxy @&0 &1 :: Type) chain: 0 diff --git a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap index 94954e3c..4eeb2b60 100644 --- a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap +++ b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap @@ -27,7 +27,7 @@ ContainsNoEq Derived -derive Eq (Proxy @&0 &1 :: Type) +derive forall (&0 :: Type). Eq (Proxy @&0 &1 :: Type) derive Eq (ContainsNoEq :: Type) Errors diff --git a/tests-integration/src/generated/basic.rs b/tests-integration/src/generated/basic.rs index 6b6d79bd..7642a370 100644 --- a/tests-integration/src/generated/basic.rs +++ b/tests-integration/src/generated/basic.rs @@ -407,7 +407,23 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { head.push_str(&format!(" ({type_str} :: {kind_str})")); } - writeln!(snapshot, "instance {head}").unwrap(); + // Format kind variables as forall binders + let forall_prefix = if instance.kind_variables.is_empty() { + String::new() + } else { + let binders: Vec<_> = instance + .kind_variables + .iter() + .enumerate() + .map(|(i, kind)| { + let kind_str = pretty::print_global(engine, *kind); + format!("(&{i} :: {kind_str})") + }) + .collect(); + format!("forall {}. ", binders.join(" ")) + }; + + writeln!(snapshot, "instance {forall_prefix}{head}").unwrap(); if let checking::core::InstanceKind::Chain { position, .. } = instance.kind { writeln!(snapshot, " chain: {}", position).unwrap(); } @@ -461,7 +477,23 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { head.push_str(&format!(" ({type_str} :: {kind_str})")); } - writeln!(snapshot, "derive {head}").unwrap(); + // Format kind variables as forall binders + let forall_prefix = if instance.kind_variables.is_empty() { + String::new() + } else { + let binders: Vec<_> = instance + .kind_variables + .iter() + .enumerate() + .map(|(i, kind)| { + let kind_str = pretty::print_global(engine, *kind); + format!("(&{i} :: {kind_str})") + }) + .collect(); + format!("forall {}. ", binders.join(" ")) + }; + + writeln!(snapshot, "derive {forall_prefix}{head}").unwrap(); } if !checked.errors.is_empty() { @@ -497,6 +529,16 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { ) .unwrap(); } + checking::error::ErrorKind::InstanceMemberTypeMismatch { expected, actual } => { + let expected_pretty = pretty::print_global(engine, expected); + let actual_pretty = pretty::print_global(engine, actual); + writeln!( + snapshot, + "InstanceMemberTypeMismatch {{ expected: {expected_pretty}, actual: {actual_pretty} }} at {:?}", + &error.step + ) + .unwrap(); + } _ => { writeln!(snapshot, "{:?} at {:?}", error.kind, &error.step).unwrap(); } From aeae163f7450d40db9981e1cb161b5a5f47f63a8 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 14 Jan 2026 02:10:46 +0800 Subject: [PATCH 30/62] Remove ShiftImplicit --- .../checking/src/algorithm/quantify.rs | 10 +++--- .../checking/src/algorithm/substitute.rs | 33 ------------------- 2 files changed, 5 insertions(+), 38 deletions(-) diff --git a/compiler-core/checking/src/algorithm/quantify.rs b/compiler-core/checking/src/algorithm/quantify.rs index 7b7ad723..52e01cea 100644 --- a/compiler-core/checking/src/algorithm/quantify.rs +++ b/compiler-core/checking/src/algorithm/quantify.rs @@ -15,7 +15,7 @@ use crate::algorithm::constraint::{ }; use crate::algorithm::fold::Zonk; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::substitute::{ShiftBound, ShiftImplicit, SubstituteUnification, UniToLevel}; +use crate::algorithm::substitute::{ShiftBound, SubstituteUnification, UniToLevel}; use crate::core::{Class, ForallBinder, Instance, RowType, Type, TypeId, debruijn}; pub fn quantify(state: &mut CheckState, id: TypeId) -> Option<(TypeId, debruijn::Size)> { @@ -289,9 +289,9 @@ pub fn quantify_instance(state: &mut CheckState, instance: &mut Instance) -> Opt let kind_variables = kind_variables.map(|(_, kind)| kind).collect_vec(); let arguments = instance.arguments.iter().map(|&(t, k)| { - let t = ShiftImplicit::on(state, t, size.0); + let t = ShiftBound::on(state, t, size.0); let t = SubstituteUnification::on(&substitutions, state, t); - let k = ShiftImplicit::on(state, k, size.0); + let k = ShiftBound::on(state, k, size.0); let k = SubstituteUnification::on(&substitutions, state, k); (t, k) }); @@ -299,9 +299,9 @@ pub fn quantify_instance(state: &mut CheckState, instance: &mut Instance) -> Opt instance.arguments = arguments.collect(); let constraints = instance.constraints.iter().map(|&(t, k)| { - let t = ShiftImplicit::on(state, t, size.0); + let t = ShiftBound::on(state, t, size.0); let t = SubstituteUnification::on(&substitutions, state, t); - let k = ShiftImplicit::on(state, k, size.0); + let k = ShiftBound::on(state, k, size.0); let k = SubstituteUnification::on(&substitutions, state, k); (t, k) }); diff --git a/compiler-core/checking/src/algorithm/substitute.rs b/compiler-core/checking/src/algorithm/substitute.rs index eef90dc2..fac69b7a 100644 --- a/compiler-core/checking/src/algorithm/substitute.rs +++ b/compiler-core/checking/src/algorithm/substitute.rs @@ -68,39 +68,6 @@ impl TypeFold for ShiftBound { } } -pub struct ShiftImplicit { - offset: u32, -} - -impl ShiftImplicit { - /// Shifts all bound AND implicit variable levels in a type by a given offset. - /// - /// This is needed when adding new kind binders to instance heads, where - /// implicit variables also need their levels adjusted. - pub fn on(state: &mut CheckState, id: TypeId, offset: u32) -> TypeId { - if offset == 0 { - return id; - } - fold_type(state, id, &mut ShiftImplicit { offset }) - } -} - -impl TypeFold for ShiftImplicit { - fn transform(&mut self, state: &mut CheckState, _id: TypeId, t: &Type) -> FoldAction { - match t { - Type::Variable(Variable::Bound(level)) => { - let level = debruijn::Level(level.0 + self.offset); - FoldAction::Replace(state.storage.intern(Type::Variable(Variable::Bound(level)))) - } - _ => FoldAction::Continue, - } - } - - fn transform_binder(&mut self, binder: &mut ForallBinder) { - binder.level = debruijn::Level(binder.level.0 + self.offset); - } -} - pub type UniToLevel = FxHashMap; pub struct SubstituteUnification<'a> { From 99c6e4fefcd7249ce7c055292f5e56d0516eeffb Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 14 Jan 2026 04:24:59 +0800 Subject: [PATCH 31/62] Deduplicate test code --- tests-integration/src/generated/basic.rs | 383 +++++++++-------------- 1 file changed, 148 insertions(+), 235 deletions(-) diff --git a/tests-integration/src/generated/basic.rs b/tests-integration/src/generated/basic.rs index 7642a370..b084818a 100644 --- a/tests-integration/src/generated/basic.rs +++ b/tests-integration/src/generated/basic.rs @@ -3,7 +3,7 @@ use std::fmt::Write; use analyzer::{QueryEngine, locate}; use checking::core::pretty; use files::FileId; -use indexing::{ImportKind, TermItem, TypeItem, TypeItemKind}; +use indexing::{ImportKind, TermItem, TypeItem, TypeItemId, TypeItemKind}; use lowering::{ ExpressionKind, GraphNode, ImplicitTypeVariable, TermVariableResolution, TypeKind, TypeVariableResolution, @@ -91,14 +91,7 @@ pub fn report_resolved(engine: &QueryEngine, id: FileId, name: &str) -> String { let mut class_member_entries: Vec<_> = resolved.class.iter().collect(); class_member_entries.sort_by_key(|(class_id, name, _, _)| (class_id.into_raw(), name.as_str())); for (class_id, member_name, member_file, _) in class_member_entries { - let class_name: String = if member_file == id { - let name = indexed.items[class_id].name.as_ref(); - name.map_or_else(|| "".to_string(), |name| name.to_string()) - } else { - let indexed = engine.indexed(member_file).unwrap(); - let name = indexed.items[class_id].name.as_ref(); - name.map_or_else(|| "".to_string(), |name| name.to_string()) - }; + let class_name = resolve_class_name(engine, &indexed, id, (member_file, class_id)); let locality = if member_file == id { "" } else { " (imported)" }; writeln!(buffer, " - {class_name}.{member_name}{locality}").unwrap(); } @@ -164,53 +157,46 @@ pub fn report_lowered(engine: &QueryEngine, id: FileId, name: &str) -> String { writeln!(buffer, "\nTypes:\n").unwrap(); + macro_rules! pos { + ($id:expr) => {{ + let cst = stabilized.ast_ptr($id).unwrap(); + locate::offset_to_position(&content, cst.syntax_node_ptr().text_range().start()) + }}; + } + for (type_id, _) in info.iter_type() { let Some(TypeKind::Variable { resolution, .. }) = info.get_type_kind(type_id) else { continue; }; let cst = stabilized.ast_ptr(type_id).unwrap(); - let root = module.syntax(); - - let node = cst.syntax_node_ptr().to_node(root); + let node = cst.syntax_node_ptr().to_node(module.syntax()); let text = node.text().to_string(); - let range = cst.syntax_node_ptr().text_range(); - let position = locate::offset_to_position(&content, range.start()); - - writeln!(buffer, "{}@{:?}", text.trim(), position).unwrap(); - if let Some(resolution) = resolution { - match resolution { - TypeVariableResolution::Forall(id) => { - let cst = stabilized.ast_ptr(*id).unwrap(); - let range = cst.syntax_node_ptr().text_range(); - let position = locate::offset_to_position(&content, range.start()); - writeln!(buffer, " resolves to forall {position:?}").unwrap(); - } - TypeVariableResolution::Implicit(ImplicitTypeVariable { binding, node, id }) => { - if let GraphNode::Implicit { bindings, .. } = &graph[*node] { - let (name, type_ids) = - bindings.get_index(*id).expect("invariant violated: invalid index"); - if *binding { - writeln!(buffer, " introduces a constraint variable {name:?}") - .unwrap(); - } else { - writeln!(buffer, " resolves to a constraint variable {name:?}") - .unwrap(); - for &type_id in type_ids { - let cst = stabilized.ast_ptr(type_id).unwrap(); - let range = cst.syntax_node_ptr().text_range(); - let position = locate::offset_to_position(&content, range.start()); - writeln!(buffer, " {position:?}").unwrap(); - } - } - } else { - writeln!(buffer, " did not resolve to constraint variable!").unwrap(); + writeln!(buffer, "{}@{:?}", text.trim(), pos!(type_id)).unwrap(); + match resolution { + Some(TypeVariableResolution::Forall(id)) => { + writeln!(buffer, " resolves to forall {:?}", pos!(*id)).unwrap(); + } + Some(TypeVariableResolution::Implicit(ImplicitTypeVariable { binding, node, id })) => { + let GraphNode::Implicit { bindings, .. } = &graph[*node] else { + writeln!(buffer, " did not resolve to constraint variable!").unwrap(); + continue; + }; + let (name, type_ids) = + bindings.get_index(*id).expect("invariant violated: invalid index"); + if *binding { + writeln!(buffer, " introduces a constraint variable {name:?}").unwrap(); + } else { + writeln!(buffer, " resolves to a constraint variable {name:?}").unwrap(); + for &tid in type_ids { + writeln!(buffer, " {:?}", pos!(tid)).unwrap(); } } } - } else { - writeln!(buffer, " resolves to nothing").unwrap(); + None => { + writeln!(buffer, " resolves to nothing").unwrap(); + } } } @@ -227,50 +213,41 @@ fn report_on_term( resolution: &Option, ) { let cst = stabilized.ast_ptr(expression_id).unwrap(); - let root = module.syntax(); - - let node = cst.syntax_node_ptr().to_node(root); + let node = cst.syntax_node_ptr().to_node(module.syntax()); let text = node.text().to_string(); - - let range = node.text_range(); - let position = locate::offset_to_position(content, range.start()); + let position = locate::offset_to_position(content, node.text_range().start()); writeln!(buffer, "{}@{:?}", text.trim(), position).unwrap(); - if let Some(resolution) = resolution { - match resolution { - TermVariableResolution::Binder(binder) => { - let cst = stabilized.ast_ptr(*binder).unwrap(); - let range = cst.syntax_node_ptr().text_range(); - let position = locate::offset_to_position(content, range.start()); - writeln!(buffer, " resolves to binder {position:?}").unwrap(); - } - TermVariableResolution::Let(let_binding_id) => { - let let_binding = info.get_let_binding_group(*let_binding_id); - if let Some(signature) = let_binding.signature { - let cst = stabilized.ast_ptr(signature).unwrap(); - let range = cst.syntax_node_ptr().text_range(); - let position = locate::offset_to_position(content, range.start()); - writeln!(buffer, " resolves to signature {position:?}").unwrap(); - } - for equation in let_binding.equations.iter() { - let cst = stabilized.ast_ptr(*equation).unwrap(); - let range = cst.syntax_node_ptr().text_range(); - let position = locate::offset_to_position(content, range.start()); - writeln!(buffer, " resolves to equation {position:?}").unwrap(); - } - } - TermVariableResolution::RecordPun(record_pun) => { - let cst = stabilized.ast_ptr(*record_pun).unwrap(); - let range = cst.syntax_node_ptr().text_range(); - let position = locate::offset_to_position(content, range.start()); - writeln!(buffer, " resolves to record pun {position:?}").unwrap(); + + macro_rules! pos { + ($id:expr) => {{ + let cst = stabilized.ast_ptr($id).unwrap(); + locate::offset_to_position(content, cst.syntax_node_ptr().text_range().start()) + }}; + } + + match resolution { + Some(TermVariableResolution::Binder(id)) => { + writeln!(buffer, " resolves to binder {:?}", pos!(*id)).unwrap(); + } + Some(TermVariableResolution::Let(let_binding_id)) => { + let let_binding = info.get_let_binding_group(*let_binding_id); + if let Some(sig) = let_binding.signature { + writeln!(buffer, " resolves to signature {:?}", pos!(sig)).unwrap(); } - TermVariableResolution::Reference(_, _) => { - writeln!(buffer, " resolves to top-level name").unwrap(); + for eq in let_binding.equations.iter() { + writeln!(buffer, " resolves to equation {:?}", pos!(*eq)).unwrap(); } } - } else { - writeln!(buffer, " resolves to nothing").unwrap(); + Some(TermVariableResolution::RecordPun(id)) => { + writeln!(buffer, " resolves to record pun {:?}", pos!(*id)).unwrap(); + } + Some(TermVariableResolution::Reference(..)) => { + writeln!(buffer, " resolves to top-level name").unwrap(); + } + None => { + writeln!(buffer, " resolves to nothing").unwrap(); + } } } @@ -364,65 +341,9 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { let mut instance_entries: Vec<_> = checked.instances.iter().collect(); instance_entries.sort_by_key(|(id, _)| format!("{:?}", id)); for (_instance_id, instance) in instance_entries { - // Build instance head: constraints => ClassName (arg :: kind)... - let (class_file, class_type_id) = instance.resolution; - let class_name: String = if class_file == id { - indexed.items[class_type_id] - .name - .as_ref() - .map(|s| s.to_string()) - .unwrap_or_else(|| "".to_string()) - } else { - engine - .indexed(class_file) - .ok() - .and_then(|idx| idx.items[class_type_id].name.as_ref().map(|s| s.to_string())) - .unwrap_or_else(|| "".to_string()) - }; - - let mut head = String::new(); - - // Print constraints (without kinds) - if !instance.constraints.is_empty() { - let constraints: Vec<_> = instance - .constraints - .iter() - .map(|(t, _)| pretty::print_global(engine, *t)) - .collect(); - if constraints.len() == 1 { - head.push_str(&constraints[0]); - } else { - head.push('('); - head.push_str(&constraints.join(", ")); - head.push(')'); - } - head.push_str(" => "); - } - - // Print class name and arguments with kinds - head.push_str(&class_name); - for (arg_type, arg_kind) in &instance.arguments { - let type_str = pretty::print_global(engine, *arg_type); - let kind_str = pretty::print_global(engine, *arg_kind); - head.push_str(&format!(" ({type_str} :: {kind_str})")); - } - - // Format kind variables as forall binders - let forall_prefix = if instance.kind_variables.is_empty() { - String::new() - } else { - let binders: Vec<_> = instance - .kind_variables - .iter() - .enumerate() - .map(|(i, kind)| { - let kind_str = pretty::print_global(engine, *kind); - format!("(&{i} :: {kind_str})") - }) - .collect(); - format!("forall {}. ", binders.join(" ")) - }; - + let class_name = resolve_class_name(engine, &indexed, id, instance.resolution); + let head = format_instance_head(engine, &class_name, &instance.constraints, &instance.arguments); + let forall_prefix = format_forall_prefix(engine, &instance.kind_variables); writeln!(snapshot, "instance {forall_prefix}{head}").unwrap(); if let checking::core::InstanceKind::Chain { position, .. } = instance.kind { writeln!(snapshot, " chain: {}", position).unwrap(); @@ -435,64 +356,9 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { let mut derived_entries: Vec<_> = checked.derived.iter().collect(); derived_entries.sort_by_key(|(id, _)| format!("{:?}", id)); for (_derive_id, instance) in derived_entries { - let (class_file, class_type_id) = instance.resolution; - let class_name: String = if class_file == id { - indexed.items[class_type_id] - .name - .as_ref() - .map(|s| s.to_string()) - .unwrap_or_else(|| "".to_string()) - } else { - engine - .indexed(class_file) - .ok() - .and_then(|idx| idx.items[class_type_id].name.as_ref().map(|s| s.to_string())) - .unwrap_or_else(|| "".to_string()) - }; - - let mut head = String::new(); - - // Print constraints (without kinds) - if !instance.constraints.is_empty() { - let constraints: Vec<_> = instance - .constraints - .iter() - .map(|(t, _)| pretty::print_global(engine, *t)) - .collect(); - if constraints.len() == 1 { - head.push_str(&constraints[0]); - } else { - head.push('('); - head.push_str(&constraints.join(", ")); - head.push(')'); - } - head.push_str(" => "); - } - - // Print class name and arguments with kinds - head.push_str(&class_name); - for (arg_type, arg_kind) in &instance.arguments { - let type_str = pretty::print_global(engine, *arg_type); - let kind_str = pretty::print_global(engine, *arg_kind); - head.push_str(&format!(" ({type_str} :: {kind_str})")); - } - - // Format kind variables as forall binders - let forall_prefix = if instance.kind_variables.is_empty() { - String::new() - } else { - let binders: Vec<_> = instance - .kind_variables - .iter() - .enumerate() - .map(|(i, kind)| { - let kind_str = pretty::print_global(engine, *kind); - format!("(&{i} :: {kind_str})") - }) - .collect(); - format!("forall {}. ", binders.join(" ")) - }; - + let class_name = resolve_class_name(engine, &indexed, id, instance.resolution); + let head = format_instance_head(engine, &class_name, &instance.constraints, &instance.arguments); + let forall_prefix = format_forall_prefix(engine, &instance.kind_variables); writeln!(snapshot, "derive {forall_prefix}{head}").unwrap(); } @@ -500,50 +366,97 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { writeln!(snapshot, "\nErrors").unwrap(); } for error in &checked.errors { + use checking::error::ErrorKind::*; + let pp = |t| pretty::print_global(engine, t); + let step = &error.step; match error.kind { - checking::error::ErrorKind::CannotUnify { t1, t2 } => { - let t1_pretty = pretty::print_global(engine, t1); - let t2_pretty = pretty::print_global(engine, t2); - writeln!( - snapshot, - "CannotUnify {{ {t1_pretty}, {t2_pretty} }} at {:?}", - &error.step - ) - .unwrap(); + CannotUnify { t1, t2 } => { + writeln!(snapshot, "CannotUnify {{ {}, {} }} at {step:?}", pp(t1), pp(t2)).unwrap(); } - checking::error::ErrorKind::NoInstanceFound { constraint } => { - let constraint_pretty = pretty::print_global(engine, constraint); - writeln!( - snapshot, - "NoInstanceFound {{ {constraint_pretty} }} at {:?}", - &error.step - ) - .unwrap(); + NoInstanceFound { constraint } => { + writeln!(snapshot, "NoInstanceFound {{ {} }} at {step:?}", pp(constraint)).unwrap(); } - checking::error::ErrorKind::AmbiguousConstraint { constraint } => { - let constraint_pretty = pretty::print_global(engine, constraint); - writeln!( - snapshot, - "AmbiguousConstraint {{ {constraint_pretty} }} at {:?}", - &error.step - ) - .unwrap(); + AmbiguousConstraint { constraint } => { + writeln!(snapshot, "AmbiguousConstraint {{ {} }} at {step:?}", pp(constraint)).unwrap(); } - checking::error::ErrorKind::InstanceMemberTypeMismatch { expected, actual } => { - let expected_pretty = pretty::print_global(engine, expected); - let actual_pretty = pretty::print_global(engine, actual); - writeln!( - snapshot, - "InstanceMemberTypeMismatch {{ expected: {expected_pretty}, actual: {actual_pretty} }} at {:?}", - &error.step - ) - .unwrap(); + InstanceMemberTypeMismatch { expected, actual } => { + writeln!(snapshot, "InstanceMemberTypeMismatch {{ expected: {}, actual: {} }} at {step:?}", pp(expected), pp(actual)).unwrap(); } _ => { - writeln!(snapshot, "{:?} at {:?}", error.kind, &error.step).unwrap(); + writeln!(snapshot, "{:?} at {step:?}", error.kind).unwrap(); } } } snapshot } + +fn resolve_class_name( + engine: &QueryEngine, + indexed: &indexing::IndexedModule, + current_file: FileId, + resolution: (FileId, TypeItemId), +) -> String { + let (class_file, class_type_id) = resolution; + if class_file == current_file { + indexed.items[class_type_id] + .name + .as_deref() + .unwrap_or("") + .to_string() + } else { + engine + .indexed(class_file) + .ok() + .and_then(|idx| idx.items[class_type_id].name.as_deref().map(str::to_string)) + .unwrap_or_else(|| "".to_string()) + } +} + +fn format_instance_head( + engine: &QueryEngine, + class_name: &str, + constraints: &[(checking::core::TypeId, checking::core::TypeId)], + arguments: &[(checking::core::TypeId, checking::core::TypeId)], +) -> String { + let mut head = String::new(); + + if !constraints.is_empty() { + let formatted: Vec<_> = constraints + .iter() + .map(|(t, _)| pretty::print_global(engine, *t)) + .collect(); + if formatted.len() == 1 { + head.push_str(&formatted[0]); + } else { + head.push('('); + head.push_str(&formatted.join(", ")); + head.push(')'); + } + head.push_str(" => "); + } + + head.push_str(class_name); + for (arg_type, arg_kind) in arguments { + let type_str = pretty::print_global(engine, *arg_type); + let kind_str = pretty::print_global(engine, *arg_kind); + head.push_str(&format!(" ({type_str} :: {kind_str})")); + } + + head +} + +fn format_forall_prefix(engine: &QueryEngine, kind_variables: &[checking::core::TypeId]) -> String { + if kind_variables.is_empty() { + return String::new(); + } + let binders: Vec<_> = kind_variables + .iter() + .enumerate() + .map(|(i, kind)| { + let kind_str = pretty::print_global(engine, *kind); + format!("(&{i} :: {kind_str})") + }) + .collect(); + format!("forall {}. ", binders.join(" ")) +} From 9d860a89e71aaf9cb4a8826ab32c375af5fcb814 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 14 Jan 2026 05:33:28 +0800 Subject: [PATCH 32/62] Avoid recursing into constructor fields for Class1 --- .../src/algorithm/derive/higher_kinded.rs | 15 +++++++-------- .../136_derive_nested_higher_kinded/Main.snap | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/compiler-core/checking/src/algorithm/derive/higher_kinded.rs b/compiler-core/checking/src/algorithm/derive/higher_kinded.rs index 3af1ad15..7c09a2d4 100644 --- a/compiler-core/checking/src/algorithm/derive/higher_kinded.rs +++ b/compiler-core/checking/src/algorithm/derive/higher_kinded.rs @@ -18,17 +18,16 @@ use crate::core::{RowType, Type, TypeId, Variable}; /// Given `f :: Type -> Type` and deriving `Eq` /// /// For a field type like `f Int` -/// - Emits `Eq1 f` instead of `Eq (f Int)` +/// - Emits `Eq1 f` /// /// For a field type like `f (g Int)` -/// - Emits `Eq1 f` instead of `Eq (f (g Int))` -/// - Emits `Eq1 g` instead of `Eq (g Int)` +/// - Emits `Eq1 f`, `Eq (g Int)` /// -/// For nominal types like `Array (f Int)` -/// - Emits `Class (Array (f Int))` +/// For nominal types like `Array Int` +/// - Emits `Eq (Array Int)` /// -/// For records like `{ a :: f Int }` -/// - Each field is traversed, emits `Eq1 f` +/// For records like `{ a :: Int, b :: f Int }` +/// - Emits `Eq Int` and `Eq1 f` pub fn generate_constraint( state: &mut CheckState, context: &CheckContext, @@ -48,7 +47,7 @@ pub fn generate_constraint( if let Some(class1) = class1 { tools::emit_constraint(state, class1, function); } - generate_constraint(state, context, argument, class, class1); + tools::emit_constraint(state, class, argument); } else { tools::emit_constraint(state, class, type_id); } diff --git a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap index 593d55f2..3840e078 100644 --- a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap @@ -44,7 +44,7 @@ derive Eq1 &0 => Eq (U &0 :: Type) derive Ord1 &0 => Ord (U &0 :: Type) Errors -NoInstanceFound { Eq1 &1 } at [TermDeclaration(Idx::(1))] -NoInstanceFound { Ord1 &1 } at [TermDeclaration(Idx::(2))] +NoInstanceFound { Eq (&1 Int) } at [TermDeclaration(Idx::(1))] +NoInstanceFound { Ord (&1 Int) } at [TermDeclaration(Idx::(2))] NoInstanceFound { Eq (&1 42) } at [TermDeclaration(Idx::(11))] NoInstanceFound { Ord (&1 42) } at [TermDeclaration(Idx::(12))] From 6a8dfde88724dda544abccac999a0cb8918c75d0 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 14 Jan 2026 05:35:32 +0800 Subject: [PATCH 33/62] Implement derive for Eq1 and Ord1 --- .../checking/src/algorithm/derive.rs | 5 + .../checking/src/algorithm/derive/eq1.rs | 114 ++++++++++++++++++ .../checking/167_derive_eq_1/Main.purs | 45 +++++++ .../checking/167_derive_eq_1/Main.snap | 81 +++++++++++++ .../checking/168_derive_ord_1/Main.purs | 34 ++++++ .../checking/168_derive_ord_1/Main.snap | 57 +++++++++ tests-integration/tests/checking/generated.rs | 4 + 7 files changed, 340 insertions(+) create mode 100644 compiler-core/checking/src/algorithm/derive/eq1.rs create mode 100644 tests-integration/fixtures/checking/167_derive_eq_1/Main.purs create mode 100644 tests-integration/fixtures/checking/167_derive_eq_1/Main.snap create mode 100644 tests-integration/fixtures/checking/168_derive_ord_1/Main.purs create mode 100644 tests-integration/fixtures/checking/168_derive_ord_1/Main.snap diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index 229340e4..9533f031 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -1,6 +1,7 @@ //! Implements type class deriving for PureScript. mod contravariant; +mod eq1; mod foldable; mod functor; mod higher_kinded; @@ -111,6 +112,10 @@ where traversable::check_derive_traversable(state, context, elaborated)?; } else if class_is(known_types.bitraversable) { traversable::check_derive_bitraversable(state, context, elaborated)?; + } else if class_is(known_types.eq1) { + eq1::check_derive_eq1(state, context, elaborated)?; + } else if class_is(known_types.ord1) { + eq1::check_derive_ord1(state, context, elaborated)?; } else { state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); }; diff --git a/compiler-core/checking/src/algorithm/derive/eq1.rs b/compiler-core/checking/src/algorithm/derive/eq1.rs new file mode 100644 index 00000000..3a486721 --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/eq1.rs @@ -0,0 +1,114 @@ +//! Implements derive for Eq1 and Ord1. +//! +//! Eq1 and Ord1 derivation is simple: it delegates to the base class. +//! The derived implementations are `eq1 = eq` and `compare1 = compare`. +//! +//! For `derive instance Eq1 Identity` to work, there must be an instance +//! available for `Eq (Identity a)`. The derivation algorithm: +//! +//! 1. Creates a fresh skolem `a` and the given constraint `Eq a` +//! 2. Creates a wanted constraint for `Eq (Identity a)` +//! 3. Solves the constraints to determine if it's satisfiable + +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; + +use crate::ExternalQueries; +use crate::algorithm::derive::tools; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::transfer; +use crate::core::{Type, Variable, debruijn}; +use crate::error::ErrorKind; + +/// Checks a derive instance for Eq1. +pub fn check_derive_eq1( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some(eq) = context.known_types.eq else { + state.insert_error(ErrorKind::CannotDeriveClass { + class_file: input.class_file, + class_id: input.class_id, + }); + return Ok(()); + }; + + check_derive_class1(state, context, input, eq) +} + +/// Checks a derive instance for Ord1. +pub fn check_derive_ord1( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some(ord) = context.known_types.ord else { + state.insert_error(ErrorKind::CannotDeriveClass { + class_file: input.class_file, + class_id: input.class_id, + }); + return Ok(()); + }; + + check_derive_class1(state, context, input, ord) +} + +/// Shared implementation for Eq1 and Ord1 derivation. +fn check_derive_class1( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, + class: (FileId, TypeItemId), +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 1, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + if super::extract_type_constructor(state, derived_type).is_none() { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + tools::push_given_constraints(state, &input.constraints); + tools::emit_superclass_constraints(state, context, &input)?; + tools::register_derived_instance(state, context, input); + + // Create a fresh skolem for the last type parameter. + let skolem_level = state.type_scope.size().0; + let skolem_level = debruijn::Level(skolem_level); + + let skolem_type = Variable::Skolem(skolem_level, context.prim.t); + let skolem_type = state.storage.intern(Type::Variable(skolem_type)); + + // Build the fully-applied type e.g. `Identity` -> `Identity a` + let applied_type = state.storage.intern(Type::Application(derived_type, skolem_type)); + + // Insert the given constraint `Eq a` + let class_type = state.storage.intern(Type::Constructor(class.0, class.1)); + let given_constraint = state.storage.intern(Type::Application(class_type, skolem_type)); + state.constraints.push_given(given_constraint); + + // Emit the wanted constraint `Eq (Identity a)` + let wanted_constraint = state.storage.intern(Type::Application(class_type, applied_type)); + state.constraints.push_wanted(wanted_constraint); + + tools::solve_and_report_constraints(state, context) +} diff --git a/tests-integration/fixtures/checking/167_derive_eq_1/Main.purs b/tests-integration/fixtures/checking/167_derive_eq_1/Main.purs new file mode 100644 index 00000000..e2fc3041 --- /dev/null +++ b/tests-integration/fixtures/checking/167_derive_eq_1/Main.purs @@ -0,0 +1,45 @@ +module Main where + +import Data.Eq (class Eq, class Eq1) + +data Id a = Id a + +derive instance Eq a => Eq (Id a) +derive instance Eq1 Id + +data Pair a = Pair a a + +derive instance Eq a => Eq (Pair a) +derive instance Eq1 Pair + +data Mixed a = Mixed Int a Boolean + +derive instance Eq a => Eq (Mixed a) +derive instance Eq1 Mixed + +data Rec a = Rec { value :: a, count :: Int } + +derive instance Eq a => Eq (Rec a) +derive instance Eq1 Rec + +data Wrap f a = Wrap (f a) + +derive instance (Eq1 f, Eq a) => Eq (Wrap f a) +derive instance Eq1 f => Eq1 (Wrap f) + +data Compose f g a = Compose (f (g a)) + +derive instance (Eq1 f, Eq (g a)) => Eq (Compose f g a) + +-- Eq1 for Compose fails: needs Eq (g a) but only has Eq a from signature +derive instance Eq1 f => Eq1 (Compose f g) + +data Either' a = Left' a | Right' a + +derive instance Eq a => Eq (Either' a) +derive instance Eq1 Either' + +-- Should fail: missing Eq instance +data NoEq a = NoEq a + +derive instance Eq1 NoEq diff --git a/tests-integration/fixtures/checking/167_derive_eq_1/Main.snap b/tests-integration/fixtures/checking/167_derive_eq_1/Main.snap new file mode 100644 index 00000000..51ecc4e4 --- /dev/null +++ b/tests-integration/fixtures/checking/167_derive_eq_1/Main.snap @@ -0,0 +1,81 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Id :: forall (a :: Type). a -> Id a +Pair :: forall (a :: Type). a -> a -> Pair a +Mixed :: forall (a :: Type). Int -> a -> Boolean -> Mixed a +Rec :: forall (a :: Type). { count :: Int, value :: a } -> Rec a +Wrap :: forall (t9 :: Type) (f :: t9 -> Type) (a :: t9). f a -> Wrap @t9 f a +Compose :: + forall (t16 :: Type) (t20 :: Type) (f :: t16 -> Type) (g :: t20 -> t16) (a :: t20). + f (g a) -> Compose @t20 f g a +Left' :: forall (a :: Type). a -> Either' a +Right' :: forall (a :: Type). a -> Either' a +NoEq :: forall (a :: Type). a -> NoEq a + +Types +Id :: Type -> Type +Pair :: Type -> Type +Mixed :: Type -> Type +Rec :: Type -> Type +Wrap :: forall (t9 :: Type). (t9 -> Type) -> t9 -> Type +Compose :: forall (t16 :: Type) (t20 :: Type). (t16 -> Type) -> (t20 -> t16) -> t20 -> Type +Either' :: Type -> Type +NoEq :: Type -> Type + +Data +Id + Quantified = :0 + Kind = :0 + +Pair + Quantified = :0 + Kind = :0 + +Mixed + Quantified = :0 + Kind = :0 + +Rec + Quantified = :0 + Kind = :0 + +Wrap + Quantified = :1 + Kind = :0 + +Compose + Quantified = :2 + Kind = :0 + +Either' + Quantified = :0 + Kind = :0 + +NoEq + Quantified = :0 + Kind = :0 + + +Derived +derive Eq1 &0 => Eq1 (Wrap @Type &0 :: Type -> Type) +derive forall (&0 :: Type). (Eq1 &1, Eq (&2 &3)) => Eq (Compose @Type @&0 &1 &2 &3 :: Type) +derive Eq1 &0 => Eq1 (Compose @Type @Type &0 &1 :: Type -> Type) +derive Eq &0 => Eq (Id &0 :: Type) +derive Eq &0 => Eq (Either' &0 :: Type) +derive Eq1 (Either' :: Type -> Type) +derive Eq1 (NoEq :: Type -> Type) +derive Eq1 (Id :: Type -> Type) +derive Eq &0 => Eq (Pair &0 :: Type) +derive Eq1 (Pair :: Type -> Type) +derive Eq &0 => Eq (Mixed &0 :: Type) +derive Eq1 (Mixed :: Type -> Type) +derive Eq &0 => Eq (Rec &0 :: Type) +derive Eq1 (Rec :: Type -> Type) +derive (Eq1 &0, Eq &1) => Eq (Wrap @Type &0 &1 :: Type) + +Errors +NoInstanceFound { Eq (&1 ~&2) } at [TermDeclaration(Idx::(17))] +NoInstanceFound { Eq (NoEq ~&0) } at [TermDeclaration(Idx::(23))] diff --git a/tests-integration/fixtures/checking/168_derive_ord_1/Main.purs b/tests-integration/fixtures/checking/168_derive_ord_1/Main.purs new file mode 100644 index 00000000..56daa9e2 --- /dev/null +++ b/tests-integration/fixtures/checking/168_derive_ord_1/Main.purs @@ -0,0 +1,34 @@ +module Main where + +import Data.Eq (class Eq, class Eq1) +import Data.Ord (class Ord, class Ord1) + +data Id a = Id a + +derive instance Eq a => Eq (Id a) +derive instance Eq1 Id +derive instance Ord a => Ord (Id a) +derive instance Ord1 Id + +data Wrap f a = Wrap (f a) + +derive instance (Eq1 f, Eq a) => Eq (Wrap f a) +derive instance Eq1 f => Eq1 (Wrap f) +derive instance (Ord1 f, Ord a) => Ord (Wrap f a) +derive instance Ord1 f => Ord1 (Wrap f) + +data Compose f g a = Compose (f (g a)) + +derive instance (Eq1 f, Eq (g a)) => Eq (Compose f g a) +derive instance (Ord1 f, Ord (g a)) => Ord (Compose f g a) + +-- Eq1/Ord1 for Compose fails: needs Eq/Ord (g a) but only has Eq/Ord a +derive instance Eq1 f => Eq1 (Compose f g) +derive instance Ord1 f => Ord1 (Compose f g) + +-- Should fail: missing Ord instance +data NoOrd a = NoOrd a + +derive instance Eq a => Eq (NoOrd a) +derive instance Eq1 NoOrd +derive instance Ord1 NoOrd diff --git a/tests-integration/fixtures/checking/168_derive_ord_1/Main.snap b/tests-integration/fixtures/checking/168_derive_ord_1/Main.snap new file mode 100644 index 00000000..34cfb44f --- /dev/null +++ b/tests-integration/fixtures/checking/168_derive_ord_1/Main.snap @@ -0,0 +1,57 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Id :: forall (a :: Type). a -> Id a +Wrap :: forall (t5 :: Type) (f :: t5 -> Type) (a :: t5). f a -> Wrap @t5 f a +Compose :: + forall (t12 :: Type) (t16 :: Type) (f :: t12 -> Type) (g :: t16 -> t12) (a :: t16). + f (g a) -> Compose @t16 f g a +NoOrd :: forall (a :: Type). a -> NoOrd a + +Types +Id :: Type -> Type +Wrap :: forall (t5 :: Type). (t5 -> Type) -> t5 -> Type +Compose :: forall (t12 :: Type) (t16 :: Type). (t12 -> Type) -> (t16 -> t12) -> t16 -> Type +NoOrd :: Type -> Type + +Data +Id + Quantified = :0 + Kind = :0 + +Wrap + Quantified = :1 + Kind = :0 + +Compose + Quantified = :2 + Kind = :0 + +NoOrd + Quantified = :0 + Kind = :0 + + +Derived +derive forall (&0 :: Type). (Eq1 &1, Eq (&2 &3)) => Eq (Compose @Type @&0 &1 &2 &3 :: Type) +derive forall (&0 :: Type). (Ord1 &1, Ord (&2 &3)) => Ord (Compose @Type @&0 &1 &2 &3 :: Type) +derive Eq1 &0 => Eq1 (Compose @Type @Type &0 &1 :: Type -> Type) +derive Ord1 &0 => Ord1 (Compose @Type @Type &0 &1 :: Type -> Type) +derive Eq &0 => Eq (NoOrd &0 :: Type) +derive Eq1 (NoOrd :: Type -> Type) +derive Ord1 (NoOrd :: Type -> Type) +derive Eq &0 => Eq (Id &0 :: Type) +derive Eq1 (Id :: Type -> Type) +derive Ord &0 => Ord (Id &0 :: Type) +derive Ord1 (Id :: Type -> Type) +derive (Eq1 &0, Eq &1) => Eq (Wrap @Type &0 &1 :: Type) +derive Eq1 &0 => Eq1 (Wrap @Type &0 :: Type -> Type) +derive (Ord1 &0, Ord &1) => Ord (Wrap @Type &0 &1 :: Type) +derive Ord1 &0 => Ord1 (Wrap @Type &0 :: Type -> Type) + +Errors +NoInstanceFound { Eq (&1 ~&2) } at [TermDeclaration(Idx::(13))] +NoInstanceFound { Ord (&1 ~&2) } at [TermDeclaration(Idx::(14))] +NoInstanceFound { Ord (NoOrd ~&0) } at [TermDeclaration(Idx::(18))] diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 15ec96de..327f7e0b 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -343,3 +343,7 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_165_derive_bitraversable_higher_kinded_main() { run_test("165_derive_bitraversable_higher_kinded", "Main"); } #[rustfmt::skip] #[test] fn test_166_derive_traversable_missing_superclass_main() { run_test("166_derive_traversable_missing_superclass", "Main"); } + +#[rustfmt::skip] #[test] fn test_167_derive_eq_1_main() { run_test("167_derive_eq_1", "Main"); } + +#[rustfmt::skip] #[test] fn test_168_derive_ord_1_main() { run_test("168_derive_ord_1", "Main"); } From 61aaa38ae7441d856713500a588282cccd06dd01 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 14 Jan 2026 07:23:25 +0800 Subject: [PATCH 34/62] Generate unification variables for wildcard types --- compiler-core/checking/src/algorithm/kind.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler-core/checking/src/algorithm/kind.rs b/compiler-core/checking/src/algorithm/kind.rs index 6367a3d4..5a8287e1 100644 --- a/compiler-core/checking/src/algorithm/kind.rs +++ b/compiler-core/checking/src/algorithm/kind.rs @@ -224,7 +224,11 @@ where } }, - lowering::TypeKind::Wildcard => Ok(unknown), + lowering::TypeKind::Wildcard => { + let k = state.fresh_unification_type(context); + let t = state.fresh_unification_kinded(k); + Ok((t, k)) + } lowering::TypeKind::Record { items, tail } => { let expected_kind = From 6845bff1f05fc6b777109f6600267ee5a5ef74df Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 14 Jan 2026 07:24:11 +0800 Subject: [PATCH 35/62] Add subtyping rule for constrained types --- compiler-core/checking/src/algorithm/unification.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler-core/checking/src/algorithm/unification.rs b/compiler-core/checking/src/algorithm/unification.rs index c4f54208..77a65bd7 100644 --- a/compiler-core/checking/src/algorithm/unification.rs +++ b/compiler-core/checking/src/algorithm/unification.rs @@ -83,6 +83,11 @@ where subtype(state, context, inner, t2) } + (Type::Constrained(constraint, inner), _) => { + state.constraints.push_wanted(constraint); + subtype(state, context, inner, t2) + } + (_, _) => unify(state, context, t1, t2), } } From 7c974662ff4bda9580308504e7b5b5d1e0fdb297 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 14 Jan 2026 07:25:25 +0800 Subject: [PATCH 36/62] Add placeholder solver for Coercible --- .../checking/src/algorithm/constraint.rs | 6 ++++++ .../src/algorithm/constraint/compiler_solved.rs | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index fab8b694..1261c8b7 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -864,6 +864,12 @@ where } else { None } + } else if file_id == context.prim_coerce.file_id { + if item_id == context.prim_coerce.coercible { + prim_coercible(state, arguments) + } else { + None + } } else { None } diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs index ddc9f51b..b9748320 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs @@ -575,3 +575,20 @@ where Some(MatchInstance::Match { constraints: vec![], equalities: vec![(list, result)] }) } + +pub fn prim_coercible(state: &mut CheckState, arguments: &[TypeId]) -> Option { + let &[left, right] = arguments else { + return None; + }; + + let left = state.normalize_type(left); + let right = state.normalize_type(right); + + // Reflexivity + if left == right { + return Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }); + } + + // TODO: placeholder used for `derive instance Newtype` + Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) +} From 7fc60e9a89b416e9007f69aa919d34c57b055508 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 14 Jan 2026 07:27:51 +0800 Subject: [PATCH 37/62] Implement initial derive for Newtype and Generic --- .../checking/src/algorithm/derive.rs | 8 +- .../checking/src/algorithm/derive/generic.rs | 160 ++++++++++++++++++ .../checking/src/algorithm/derive/newtype.rs | 52 ++++++ compiler-core/checking/src/algorithm/state.rs | 94 ++++++++++ .../032_recursive_synonym_expansion/Main.snap | 18 +- .../123_incomplete_instance_head/Main.snap | 2 +- .../169_derive_newtype_class_simple/Main.purs | 7 + .../169_derive_newtype_class_simple/Main.snap | 18 ++ .../Main.purs | 7 + .../Main.snap | 18 ++ .../Main.purs | 7 + .../Main.snap | 18 ++ .../172_derive_generic_simple/Main.purs | 45 +++++ .../172_derive_generic_simple/Main.snap | 76 +++++++++ .../Main.purs | 23 +++ .../Main.snap | 29 ++++ .../checking/prelude/Data.Generic.Rep.purs | 17 ++ .../checking/prelude/Data.Newtype.purs | 12 ++ .../checking/prelude/Safe.Coerce.purs | 8 + tests-integration/tests/checking/generated.rs | 10 ++ 20 files changed, 618 insertions(+), 11 deletions(-) create mode 100644 compiler-core/checking/src/algorithm/derive/generic.rs create mode 100644 compiler-core/checking/src/algorithm/derive/newtype.rs create mode 100644 tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.snap create mode 100644 tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.purs create mode 100644 tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.snap create mode 100644 tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.purs create mode 100644 tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.snap create mode 100644 tests-integration/fixtures/checking/172_derive_generic_simple/Main.purs create mode 100644 tests-integration/fixtures/checking/172_derive_generic_simple/Main.snap create mode 100644 tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.purs create mode 100644 tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.snap create mode 100644 tests-integration/fixtures/checking/prelude/Data.Generic.Rep.purs create mode 100644 tests-integration/fixtures/checking/prelude/Data.Newtype.purs create mode 100644 tests-integration/fixtures/checking/prelude/Safe.Coerce.purs diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index 9533f031..eccbe674 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -4,7 +4,9 @@ mod contravariant; mod eq1; mod foldable; mod functor; +mod generic; mod higher_kinded; +mod newtype; mod tools; mod traversable; mod variance; @@ -116,6 +118,10 @@ where eq1::check_derive_eq1(state, context, elaborated)?; } else if class_is(known_types.ord1) { eq1::check_derive_ord1(state, context, elaborated)?; + } else if class_is(known_types.newtype) { + newtype::check_derive_newtype(state, context, elaborated)?; + } else if class_is(known_types.generic) { + generic::check_derive_generic(state, context, elaborated)?; } else { state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); }; @@ -217,7 +223,7 @@ where tools::solve_and_report_constraints(state, context) } -pub(crate) fn extract_type_constructor( +pub fn extract_type_constructor( state: &mut CheckState, mut type_id: TypeId, ) -> Option<(FileId, TypeItemId)> { diff --git a/compiler-core/checking/src/algorithm/derive/generic.rs b/compiler-core/checking/src/algorithm/derive/generic.rs new file mode 100644 index 00000000..fd1c967e --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/generic.rs @@ -0,0 +1,160 @@ +//! Implements derive for `Data.Generic.Rep.Generic`. +//! +//! Unlike other derivable classes that emit constraints, Generic generates +//! a type for the `rep` wildcard based on the provided type. +//! +//! ```purescript +//! data Either a b = Left a | Right b +//! -- Sum (Constructor "Left" (Argument a)) (Constructor "Right" (Argument b)) +//! ``` + +use building_types::QueryResult; +use files::FileId; +use indexing::{IndexedModule, TermItemId}; +use lowering::StringKind; +use smol_str::SmolStr; + +use crate::ExternalQueries; +use crate::algorithm::derive::{self, tools}; +use crate::algorithm::state::{CheckContext, CheckState, KnownGeneric}; +use crate::algorithm::{transfer, unification}; +use crate::core::{Type, TypeId}; +use crate::error::ErrorKind; + +pub fn check_derive_generic( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(derived_type, _), (wildcard_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 2, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((data_file, data_id)) = derive::extract_type_constructor(state, derived_type) else { + let global_type = transfer::globalize(state, context, derived_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + let Some(ref known_generic) = context.known_generic else { + state.insert_error(ErrorKind::CannotDeriveClass { + class_file: input.class_file, + class_id: input.class_id, + }); + return Ok(()); + }; + + let constructors = tools::lookup_data_constructors(context, data_file, data_id)?; + + let generic_rep = + build_generic_rep(state, context, known_generic, data_file, derived_type, &constructors)?; + + let _ = unification::unify(state, context, wildcard_type, generic_rep)?; + + tools::push_given_constraints(state, &input.constraints); + tools::emit_superclass_constraints(state, context, &input)?; + tools::register_derived_instance(state, context, input); + + Ok(()) +} + +fn build_generic_rep( + state: &mut CheckState, + context: &CheckContext, + known_generic: &KnownGeneric, + data_file: FileId, + derived_type: TypeId, + constructors: &[TermItemId], +) -> QueryResult +where + Q: ExternalQueries, +{ + let [ref rest @ .., last] = constructors[..] else { + return Ok(known_generic.no_constructors); + }; + + let last = + build_generic_constructor(state, context, known_generic, data_file, derived_type, last)?; + + rest.iter().rev().try_fold(last, |accumulator, &constructor_id| { + let constructor = build_generic_constructor( + state, + context, + known_generic, + data_file, + derived_type, + constructor_id, + )?; + let applied = state.storage.intern(Type::Application(known_generic.sum, constructor)); + Ok(state.storage.intern(Type::Application(applied, accumulator))) + }) +} + +/// Builds a Constructor rep: `Constructor "Name" fields_rep` +fn build_generic_constructor( + state: &mut CheckState, + context: &CheckContext, + known_generic: &KnownGeneric, + data_file: FileId, + derived_type: TypeId, + constructor_id: TermItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let constructor_name = |indexed: &IndexedModule| { + const UNKNOWN_NAME: SmolStr = SmolStr::new_static(""); + let constructor_name = indexed.items[constructor_id].name.clone(); + constructor_name.unwrap_or(UNKNOWN_NAME) + }; + + let constructor_name = if data_file == context.id { + let indexed = &context.indexed; + constructor_name(&indexed) + } else { + let indexed = context.queries.indexed(data_file)?; + constructor_name(&indexed) + }; + + let constructor_type = + derive::lookup_local_term_type(state, context, data_file, constructor_id)?; + + let field_types = if let Some(constructor_type) = constructor_type { + derive::extract_constructor_fields(state, constructor_type, derived_type) + } else { + vec![] + }; + + let fields_rep = build_fields_rep(state, known_generic, &field_types); + + let name = state.storage.intern(Type::String(StringKind::String, constructor_name)); + let constructor = state.storage.intern(Type::Application(known_generic.constructor, name)); + Ok(state.storage.intern(Type::Application(constructor, fields_rep))) +} + +/// Builds field rep: Product of Arguments, or NoArguments if empty. +fn build_fields_rep( + state: &mut CheckState, + known_generic: &KnownGeneric, + field_types: &[TypeId], +) -> TypeId { + let [ref rest @ .., last] = field_types[..] else { + return known_generic.no_arguments; + }; + + let last = state.storage.intern(Type::Application(known_generic.argument, last)); + rest.iter().rev().fold(last, |accumulator, &field| { + let argument = state.storage.intern(Type::Application(known_generic.argument, field)); + let product = state.storage.intern(Type::Application(known_generic.product, argument)); + state.storage.intern(Type::Application(product, accumulator)) + }) +} diff --git a/compiler-core/checking/src/algorithm/derive/newtype.rs b/compiler-core/checking/src/algorithm/derive/newtype.rs new file mode 100644 index 00000000..634fa403 --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/newtype.rs @@ -0,0 +1,52 @@ +//! Implements derive for `Data.Newtype.Newtype`. + +use building_types::QueryResult; + +use crate::ExternalQueries; +use crate::algorithm::derive::{self, tools}; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::{transfer, unification}; +use crate::error::ErrorKind; + +pub fn check_derive_newtype( + state: &mut CheckState, + context: &CheckContext, + input: tools::ElaboratedDerive, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let [(newtype_type, _), (wildcard_type, _)] = input.arguments[..] else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file: input.class_file, + class_id: input.class_id, + expected: 2, + actual: input.arguments.len(), + }); + return Ok(()); + }; + + let Some((newtype_file, newtype_id)) = super::extract_type_constructor(state, newtype_type) + else { + let global_type = transfer::globalize(state, context, newtype_type); + state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); + return Ok(()); + }; + + if !derive::is_newtype(context, newtype_file, newtype_id)? { + let global_type = transfer::globalize(state, context, newtype_type); + state.insert_error(ErrorKind::ExpectedNewtype { type_id: global_type }); + return Ok(()); + } + + let inner_type = + derive::get_newtype_inner(state, context, newtype_file, newtype_id, newtype_type)?; + + let _ = unification::unify(state, context, wildcard_type, inner_type); + + tools::push_given_constraints(state, &input.constraints); + tools::emit_superclass_constraints(state, context, &input)?; + tools::register_derived_instance(state, context, input); + + tools::solve_and_report_constraints(state, context) +} diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index b8eec030..431368ca 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -335,7 +335,9 @@ where pub prim_symbol: PrimSymbolCore, pub prim_row: PrimRowCore, pub prim_row_list: PrimRowListCore, + pub prim_coerce: PrimCoerceCore, pub known_types: KnownTypesCore, + pub known_generic: Option, pub id: FileId, pub indexed: Arc, @@ -365,7 +367,9 @@ where let prim_symbol = PrimSymbolCore::collect(queries, state)?; let prim_row = PrimRowCore::collect(queries, state)?; let prim_row_list = PrimRowListCore::collect(queries, state)?; + let prim_coerce = PrimCoerceCore::collect(queries)?; let known_types = KnownTypesCore::collect(queries)?; + let known_generic = KnownGeneric::collect(queries, &mut state.storage)?; let prim_id = queries.prim_id(); let prim_indexed = queries.indexed(prim_id)?; Ok(CheckContext { @@ -376,7 +380,9 @@ where prim_symbol, prim_row, prim_row_list, + prim_coerce, known_types, + known_generic, id, indexed, lowered, @@ -605,6 +611,26 @@ impl PrimRowListCore { } } +pub struct PrimCoerceCore { + pub file_id: FileId, + pub coercible: TypeItemId, +} + +impl PrimCoerceCore { + fn collect(queries: &impl ExternalQueries) -> QueryResult { + let file_id = queries + .module_file("Prim.Coerce") + .unwrap_or_else(|| unreachable!("invariant violated: Prim.Coerce not found")); + + let resolved = queries.resolved(file_id)?; + let (_, coercible) = resolved.exports.lookup_type("Coercible").unwrap_or_else(|| { + unreachable!("invariant violated: Coercible not in Prim.Coerce") + }); + + Ok(PrimCoerceCore { file_id, coercible }) + } +} + fn fetch_known_type( queries: &impl ExternalQueries, m: &str, @@ -620,6 +646,22 @@ fn fetch_known_type( Ok(Some((file_id, type_id))) } +fn fetch_known_constructor( + queries: &impl ExternalQueries, + storage: &mut TypeInterner, + m: &str, + n: &str, +) -> QueryResult> { + let Some(file_id) = queries.module_file(m) else { + return Ok(None); + }; + let resolved = queries.resolved(file_id)?; + let Some((file_id, type_id)) = resolved.exports.lookup_type(n) else { + return Ok(None); + }; + Ok(Some(storage.intern(Type::Constructor(file_id, type_id)))) +} + pub struct KnownTypesCore { pub eq: Option<(FileId, TypeItemId)>, pub eq1: Option<(FileId, TypeItemId)>, @@ -633,6 +675,8 @@ pub struct KnownTypesCore { pub bifoldable: Option<(FileId, TypeItemId)>, pub traversable: Option<(FileId, TypeItemId)>, pub bitraversable: Option<(FileId, TypeItemId)>, + pub newtype: Option<(FileId, TypeItemId)>, + pub generic: Option<(FileId, TypeItemId)>, } impl KnownTypesCore { @@ -650,6 +694,8 @@ impl KnownTypesCore { let bifoldable = fetch_known_type(queries, "Data.Bifoldable", "Bifoldable")?; let traversable = fetch_known_type(queries, "Data.Traversable", "Traversable")?; let bitraversable = fetch_known_type(queries, "Data.Bitraversable", "Bitraversable")?; + let newtype = fetch_known_type(queries, "Data.Newtype", "Newtype")?; + let generic = fetch_known_type(queries, "Data.Generic.Rep", "Generic")?; Ok(KnownTypesCore { eq, eq1, @@ -663,10 +709,58 @@ impl KnownTypesCore { bifoldable, traversable, bitraversable, + newtype, + generic, }) } } +pub struct KnownGeneric { + pub no_constructors: TypeId, + pub constructor: TypeId, + pub sum: TypeId, + pub product: TypeId, + pub no_arguments: TypeId, + pub argument: TypeId, +} + +impl KnownGeneric { + fn collect( + queries: &impl ExternalQueries, + storage: &mut TypeInterner, + ) -> QueryResult> { + let Some(no_constructors) = + fetch_known_constructor(queries, storage, "Data.Generic.Rep", "NoConstructors")? + else { + return Ok(None); + }; + let Some(constructor) = + fetch_known_constructor(queries, storage, "Data.Generic.Rep", "Constructor")? + else { + return Ok(None); + }; + let Some(sum) = fetch_known_constructor(queries, storage, "Data.Generic.Rep", "Sum")? else { + return Ok(None); + }; + let Some(product) = + fetch_known_constructor(queries, storage, "Data.Generic.Rep", "Product")? + else { + return Ok(None); + }; + let Some(no_arguments) = + fetch_known_constructor(queries, storage, "Data.Generic.Rep", "NoArguments")? + else { + return Ok(None); + }; + let Some(argument) = + fetch_known_constructor(queries, storage, "Data.Generic.Rep", "Argument")? + else { + return Ok(None); + }; + Ok(Some(KnownGeneric { no_constructors, constructor, sum, product, no_arguments, argument })) + } +} + impl CheckState { /// Executes the given closure with a term binding group in scope. /// diff --git a/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap b/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap index 29001447..55ec31f7 100644 --- a/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap +++ b/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap @@ -37,12 +37,12 @@ Valid = Int Errors -RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] -RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] -RecursiveSynonymExpansion { file_id: Idx::(22), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap b/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap index e4d009ff..316f0572 100644 --- a/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap +++ b/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap @@ -18,4 +18,4 @@ instance Pair (Int :: Type) chain: 0 Errors -InstanceHeadMismatch { class_file: Idx::(22), class_item: Idx::(0), expected: 2, actual: 1 } at [TermDeclaration(Idx::(2))] +InstanceHeadMismatch { class_file: Idx::(25), class_item: Idx::(0), expected: 2, actual: 1 } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.purs b/tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.purs new file mode 100644 index 00000000..b48f3b16 --- /dev/null +++ b/tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Newtype (class Newtype) + +newtype UserId = UserId Int + +derive instance Newtype UserId _ diff --git a/tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.snap b/tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.snap new file mode 100644 index 00000000..dcdc175c --- /dev/null +++ b/tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +UserId :: Int -> UserId + +Types +UserId :: Type + +Data +UserId + Quantified = :0 + Kind = :0 + + +Derived +derive Newtype (UserId :: Type) (Int :: Type) diff --git a/tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.purs b/tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.purs new file mode 100644 index 00000000..c6284bd3 --- /dev/null +++ b/tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Newtype (class Newtype) + +newtype Wrapper a = Wrapper a + +derive instance Newtype (Wrapper a) _ diff --git a/tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.snap b/tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.snap new file mode 100644 index 00000000..18a91c09 --- /dev/null +++ b/tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Wrapper :: forall (a :: Type). a -> Wrapper a + +Types +Wrapper :: Type -> Type + +Data +Wrapper + Quantified = :0 + Kind = :0 + + +Derived +derive Newtype (Wrapper &0 :: Type) (&0 :: Type) diff --git a/tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.purs b/tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.purs new file mode 100644 index 00000000..18a518ba --- /dev/null +++ b/tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Newtype (class Newtype) + +data NotANewtype = NotANewtype Int + +derive instance Newtype NotANewtype _ diff --git a/tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.snap b/tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.snap new file mode 100644 index 00000000..6dd3d089 --- /dev/null +++ b/tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +NotANewtype :: Int -> NotANewtype + +Types +NotANewtype :: Type + +Data +NotANewtype + Quantified = :0 + Kind = :0 + + +Errors +ExpectedNewtype { type_id: Id(9) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/172_derive_generic_simple/Main.purs b/tests-integration/fixtures/checking/172_derive_generic_simple/Main.purs new file mode 100644 index 00000000..547bd9e2 --- /dev/null +++ b/tests-integration/fixtures/checking/172_derive_generic_simple/Main.purs @@ -0,0 +1,45 @@ +module Main where + +import Data.Generic.Rep (class Generic) + +data Void + +data MyUnit = MyUnit + +data Identity a = Identity a + +data Either a b = Left a | Right b + +data Tuple a b = Tuple a b + +newtype Wrapper a = Wrapper a + +derive instance Generic Void _ +derive instance Generic MyUnit _ +derive instance Generic (Identity a) _ +derive instance Generic (Either a b) _ +derive instance Generic (Tuple a b) _ +derive instance Generic (Wrapper a) _ + +-- Use forceSolve to emit the Rep types in the snapshot +data Proxy a = Proxy + +getVoid :: forall rep. Generic Void rep => Proxy rep +getVoid = Proxy + +getMyUnit :: forall rep. Generic MyUnit rep => Proxy rep +getMyUnit = Proxy + +getIdentity :: forall a rep. Generic (Identity a) rep => Proxy rep +getIdentity = Proxy + +getEither :: forall a b rep. Generic (Either a b) rep => Proxy rep +getEither = Proxy + +getTuple :: forall a b rep. Generic (Tuple a b) rep => Proxy rep +getTuple = Proxy + +getWrapper :: forall a rep. Generic (Wrapper a) rep => Proxy rep +getWrapper = Proxy + +forceSolve = { getVoid, getMyUnit, getIdentity, getEither, getTuple, getWrapper } diff --git a/tests-integration/fixtures/checking/172_derive_generic_simple/Main.snap b/tests-integration/fixtures/checking/172_derive_generic_simple/Main.snap new file mode 100644 index 00000000..74517fbe --- /dev/null +++ b/tests-integration/fixtures/checking/172_derive_generic_simple/Main.snap @@ -0,0 +1,76 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +MyUnit :: MyUnit +Identity :: forall (a :: Type). a -> Identity a +Left :: forall (a :: Type) (b :: Type). a -> Either a b +Right :: forall (a :: Type) (b :: Type). b -> Either a b +Tuple :: forall (a :: Type) (b :: Type). a -> b -> Tuple a b +Wrapper :: forall (a :: Type). a -> Wrapper a +Proxy :: forall (t6 :: Type) (a :: t6). Proxy @t6 a +getVoid :: forall (rep :: Type). Generic Void rep => Proxy @Type rep +getMyUnit :: forall (rep :: Type). Generic MyUnit rep => Proxy @Type rep +getIdentity :: forall (a :: Type) (rep :: Type). Generic (Identity a) rep => Proxy @Type rep +getEither :: + forall (a :: Type) (b :: Type) (rep :: Type). Generic (Either a b) rep => Proxy @Type rep +getTuple :: forall (a :: Type) (b :: Type) (rep :: Type). Generic (Tuple a b) rep => Proxy @Type rep +getWrapper :: forall (a :: Type) (rep :: Type). Generic (Wrapper a) rep => Proxy @Type rep +forceSolve :: + forall (t65 :: Type) (t67 :: Type) (t68 :: Type) (t70 :: Type) (t71 :: Type) (t73 :: Type). + { getEither :: + Proxy @Type (Sum (Constructor "Left" (Argument t67)) (Constructor "Right" (Argument t68))) + , getIdentity :: Proxy @Type (Constructor "Identity" (Argument t65)) + , getMyUnit :: Proxy @Type (Constructor "MyUnit" NoArguments) + , getTuple :: Proxy @Type (Constructor "Tuple" (Product (Argument t70) (Argument t71))) + , getVoid :: Proxy @Type NoConstructors + , getWrapper :: Proxy @Type (Constructor "Wrapper" (Argument t73)) + } + +Types +Void :: Type +MyUnit :: Type +Identity :: Type -> Type +Either :: Type -> Type -> Type +Tuple :: Type -> Type -> Type +Wrapper :: Type -> Type +Proxy :: forall (t6 :: Type). t6 -> Type + +Data +Void + Quantified = :0 + Kind = :0 + +MyUnit + Quantified = :0 + Kind = :0 + +Identity + Quantified = :0 + Kind = :0 + +Either + Quantified = :0 + Kind = :0 + +Tuple + Quantified = :0 + Kind = :0 + +Wrapper + Quantified = :0 + Kind = :0 + +Proxy + Quantified = :1 + Kind = :0 + + +Derived +derive Generic (Void :: Type) (NoConstructors :: Type) +derive Generic (MyUnit :: Type) (Constructor "MyUnit" NoArguments :: Type) +derive Generic (Identity &0 :: Type) (Constructor "Identity" (Argument &0) :: Type) +derive Generic (Either &0 &1 :: Type) (Sum (Constructor "Left" (Argument &0)) (Constructor "Right" (Argument &1)) :: Type) +derive Generic (Tuple &0 &1 :: Type) (Constructor "Tuple" (Product (Argument &0) (Argument &1)) :: Type) +derive Generic (Wrapper &0 :: Type) (Constructor "Wrapper" (Argument &0) :: Type) diff --git a/tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.purs b/tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.purs new file mode 100644 index 00000000..64588234 --- /dev/null +++ b/tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.purs @@ -0,0 +1,23 @@ +module Main where + +import Data.Newtype (class Newtype, wrap, unwrap) + +newtype UserId = UserId Int + +derive instance Newtype UserId _ + +wrapUserId :: Int -> UserId +wrapUserId = wrap + +unwrapUserId :: UserId -> Int +unwrapUserId = unwrap + +newtype Wrapper a = Wrapper a + +derive instance Newtype (Wrapper a) _ + +wrapWrapper :: forall a. a -> Wrapper a +wrapWrapper = wrap + +unwrapWrapper :: forall a. Wrapper a -> a +unwrapWrapper = unwrap diff --git a/tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.snap b/tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.snap new file mode 100644 index 00000000..88bb1763 --- /dev/null +++ b/tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.snap @@ -0,0 +1,29 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +UserId :: Int -> UserId +wrapUserId :: Int -> UserId +unwrapUserId :: UserId -> Int +Wrapper :: forall (a :: Type). a -> Wrapper a +wrapWrapper :: forall (a :: Type). a -> Wrapper a +unwrapWrapper :: forall (a :: Type). Wrapper a -> a + +Types +UserId :: Type +Wrapper :: Type -> Type + +Data +UserId + Quantified = :0 + Kind = :0 + +Wrapper + Quantified = :0 + Kind = :0 + + +Derived +derive Newtype (UserId :: Type) (Int :: Type) +derive Newtype (Wrapper &0 :: Type) (&0 :: Type) diff --git a/tests-integration/fixtures/checking/prelude/Data.Generic.Rep.purs b/tests-integration/fixtures/checking/prelude/Data.Generic.Rep.purs new file mode 100644 index 00000000..4ff22dca --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Data.Generic.Rep.purs @@ -0,0 +1,17 @@ +module Data.Generic.Rep where + +class Generic a rep | a -> rep where + to :: rep -> a + from :: a -> rep + +data NoConstructors + +data Constructor (name :: Symbol) a = Constructor a + +data Sum a b = Inl a | Inr b + +data Product a b = Product a b + +data NoArguments = NoArguments + +newtype Argument a = Argument a diff --git a/tests-integration/fixtures/checking/prelude/Data.Newtype.purs b/tests-integration/fixtures/checking/prelude/Data.Newtype.purs new file mode 100644 index 00000000..2235b4f5 --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Data.Newtype.purs @@ -0,0 +1,12 @@ +module Data.Newtype where + +import Prim.Coerce (class Coercible) +import Safe.Coerce (coerce) + +class Coercible t a <= Newtype t a | t -> a + +wrap :: forall t a. Newtype t a => a -> t +wrap = coerce + +unwrap :: forall t a. Newtype t a => t -> a +unwrap = coerce diff --git a/tests-integration/fixtures/checking/prelude/Safe.Coerce.purs b/tests-integration/fixtures/checking/prelude/Safe.Coerce.purs new file mode 100644 index 00000000..d701d143 --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Safe.Coerce.purs @@ -0,0 +1,8 @@ +module Safe.Coerce where + +import Prim.Coerce (class Coercible) + +coerce :: forall a b. Coercible a b => a -> b +coerce = unsafeCoerce + +foreign import unsafeCoerce :: forall a b. a -> b diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 327f7e0b..60febb2e 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -347,3 +347,13 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_167_derive_eq_1_main() { run_test("167_derive_eq_1", "Main"); } #[rustfmt::skip] #[test] fn test_168_derive_ord_1_main() { run_test("168_derive_ord_1", "Main"); } + +#[rustfmt::skip] #[test] fn test_169_derive_newtype_class_simple_main() { run_test("169_derive_newtype_class_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_170_derive_newtype_class_parameterized_main() { run_test("170_derive_newtype_class_parameterized", "Main"); } + +#[rustfmt::skip] #[test] fn test_171_derive_newtype_class_not_newtype_main() { run_test("171_derive_newtype_class_not_newtype", "Main"); } + +#[rustfmt::skip] #[test] fn test_172_derive_generic_simple_main() { run_test("172_derive_generic_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_173_derive_newtype_class_coercible_main() { run_test("173_derive_newtype_class_coercible", "Main"); } From 6241d17d45188dc8641ae6a98cf52c75dc0e90e5 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 14 Jan 2026 16:54:10 +0800 Subject: [PATCH 38/62] Extract binding group computation to a query --- compiler-core/building-types/src/lib.rs | 4 + compiler-core/building/src/engine.rs | 72 +++++++++- compiler-core/checking/src/algorithm.rs | 14 +- .../checking/src/algorithm/derive/generic.rs | 2 +- .../checking/src/algorithm/kind/synonym.rs | 10 +- compiler-core/checking/src/algorithm/state.rs | 7 +- compiler-core/checking/src/lib.rs | 3 +- compiler-core/lowering/src/algorithm.rs | 25 ++-- compiler-core/lowering/src/lib.rs | 124 ++++++++++++------ docs/src/wasm/src/engine.rs | 18 ++- tests-integration/tests/lowering.rs | 34 +++-- .../lowering__non_recursive_kinds.snap | 15 +-- tests-package-set/tests/parsing.rs | 11 +- 13 files changed, 234 insertions(+), 105 deletions(-) diff --git a/compiler-core/building-types/src/lib.rs b/compiler-core/building-types/src/lib.rs index 7a701638..4332f1f4 100644 --- a/compiler-core/building-types/src/lib.rs +++ b/compiler-core/building-types/src/lib.rs @@ -14,6 +14,7 @@ pub enum QueryKey { Stabilized(FileId), Indexed(FileId), Lowered(FileId), + Grouped(FileId), Resolved(FileId), Bracketed(FileId), Sectioned(FileId), @@ -35,6 +36,7 @@ pub trait QueryProxy { type Stabilized; type Indexed; type Lowered; + type Grouped; type Resolved; type Bracketed; type Sectioned; @@ -48,6 +50,8 @@ pub trait QueryProxy { fn lowered(&self, id: FileId) -> QueryResult; + fn grouped(&self, id: FileId) -> QueryResult; + fn resolved(&self, id: FileId) -> QueryResult; fn bracketed(&self, id: FileId) -> QueryResult; diff --git a/compiler-core/building/src/engine.rs b/compiler-core/building/src/engine.rs index 3a395c1a..a26d0436 100644 --- a/compiler-core/building/src/engine.rs +++ b/compiler-core/building/src/engine.rs @@ -38,7 +38,7 @@ use files::FileId; use graph::SnapshotGraph; use indexing::IndexedModule; use lock_api::{RawRwLock, RawRwLockRecursive}; -use lowering::LoweredModule; +use lowering::{GroupedModule, LoweredModule}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use parsing::FullParsedModule; use promise::{Future, Promise}; @@ -94,6 +94,7 @@ struct DerivedStorage { stabilized: FxHashMap>>, indexed: FxHashMap>>, lowered: FxHashMap>>, + grouped: FxHashMap>>, resolved: FxHashMap>>, bracketed: FxHashMap>>, sectioned: FxHashMap>>, @@ -422,6 +423,7 @@ impl QueryEngine { QueryKey::Stabilized(k) => derived_changed!(stabilized, k), QueryKey::Indexed(k) => derived_changed!(indexed, k), QueryKey::Lowered(k) => derived_changed!(lowered, k), + QueryKey::Grouped(k) => derived_changed!(grouped, k), QueryKey::Resolved(k) => derived_changed!(resolved, k), QueryKey::Bracketed(k) => derived_changed!(bracketed, k), QueryKey::Sectioned(k) => derived_changed!(sectioned, k), @@ -692,6 +694,20 @@ impl QueryEngine { ) } + pub fn grouped(&self, id: FileId) -> QueryResult> { + self.query( + QueryKey::Grouped(id), + |storage| storage.derived.grouped.get(&id), + |storage| storage.derived.grouped.entry(id), + |this| { + let lowered = this.lowered(id)?; + let indexed = this.indexed(id)?; + let groups = lowering::group_module(&indexed, &lowered); + Ok(Arc::new(groups)) + }, + ) + } + pub fn resolved(&self, id: FileId) -> QueryResult> { self.query( QueryKey::Resolved(id), @@ -758,6 +774,8 @@ impl QueryProxy for QueryEngine { type Lowered = Arc; + type Grouped = Arc; + type Resolved = Arc; type Bracketed = Arc; @@ -782,6 +800,10 @@ impl QueryProxy for QueryEngine { QueryEngine::lowered(self, id) } + fn grouped(&self, id: FileId) -> QueryResult { + QueryEngine::grouped(self, id) + } + fn resolved(&self, id: FileId) -> QueryResult { QueryEngine::resolved(self, id) } @@ -1266,4 +1288,52 @@ mod tests { }) ); } + + #[test] + fn test_grouped_identity() { + let mut engine = QueryEngine::default(); + let mut files = Files::default(); + prim::configure(&mut engine, &mut files); + + let id = files.insert("./src/Main.purs", "module Main where\n\nx = y\ny = 1"); + let content = files.content(id); + engine.set_content(id, content); + + let groups_a = engine.grouped(id).unwrap(); + let groups_b = engine.grouped(id).unwrap(); + assert!(Arc::ptr_eq(&groups_a, &groups_b)); + } + + #[test] + fn test_lowered_identity() { + let mut engine = QueryEngine::default(); + let mut files = Files::default(); + prim::configure(&mut engine, &mut files); + + let id = files.insert("./src/Main.purs", "module Main where\n\nx = 1"); + let content = files.content(id); + engine.set_content(id, content); + + let lowered_a = engine.lowered(id).unwrap(); + let lowered_b = engine.lowered(id).unwrap(); + assert!(Arc::ptr_eq(&lowered_a, &lowered_b)); + } + + #[test] + fn test_grouped_stable() { + let mut engine = QueryEngine::default(); + let mut files = Files::default(); + prim::configure(&mut engine, &mut files); + + let id = files.insert("./src/Main.purs", "module Main where\n\nx = 1"); + engine.set_content(id, files.content(id)); + let groups_a = engine.grouped(id).unwrap(); + + let id = files.insert("./src/Main.purs", "module Main where\n\n\n\nx = 1"); + engine.set_content(id, files.content(id)); + let groups_b = engine.grouped(id).unwrap(); + + assert_eq!(groups_a.term_scc, groups_b.term_scc); + assert_eq!(groups_a.type_scc, groups_b.type_scc); + } } diff --git a/compiler-core/checking/src/algorithm.rs b/compiler-core/checking/src/algorithm.rs index 22257422..25703b23 100644 --- a/compiler-core/checking/src/algorithm.rs +++ b/compiler-core/checking/src/algorithm.rs @@ -109,7 +109,7 @@ fn check_type_signatures( where Q: ExternalQueries, { - for scc in &context.lowered.type_scc { + for scc in &context.grouped.type_scc { let items = match scc { Scc::Base(id) | Scc::Recursive(id) => slice::from_ref(id), Scc::Mutual(items) => items, @@ -134,7 +134,7 @@ fn check_type_definitions( where Q: ExternalQueries, { - for scc in &context.lowered.type_scc { + for scc in &context.grouped.type_scc { match scc { Scc::Base(id) => { if let Some(item) = type_item::check_type_item(state, context, *id)? { @@ -175,7 +175,7 @@ fn check_term_signatures( where Q: ExternalQueries, { - for scc in &context.lowered.term_scc { + for scc in &context.grouped.term_scc { match scc { Scc::Base(item) | Scc::Recursive(item) => { term_item::check_term_signature(state, context, *item)?; @@ -198,7 +198,7 @@ fn check_instance_heads( where Q: ExternalQueries, { - let items = context.lowered.term_scc.iter().flat_map(|scc| match scc { + let items = context.grouped.term_scc.iter().flat_map(|scc| match scc { Scc::Base(id) | Scc::Recursive(id) => slice::from_ref(id), Scc::Mutual(id) => id, }); @@ -226,7 +226,7 @@ fn check_instance_members( where Q: ExternalQueries, { - let items = context.lowered.term_scc.iter().flat_map(|scc| match scc { + let items = context.grouped.term_scc.iter().flat_map(|scc| match scc { Scc::Base(id) | Scc::Recursive(id) => slice::from_ref(id), Scc::Mutual(id) => id, }); @@ -277,7 +277,7 @@ fn check_derive_heads( where Q: ExternalQueries, { - let items = context.lowered.term_scc.iter().flat_map(|scc| match scc { + let items = context.grouped.term_scc.iter().flat_map(|scc| match scc { Scc::Base(item) | Scc::Recursive(item) => slice::from_ref(item), Scc::Mutual(items) => items.as_slice(), }); @@ -329,7 +329,7 @@ where Some(term_item::CheckValueGroup { item_id, signature, equations }) }; - for scc in &context.lowered.term_scc { + for scc in &context.grouped.term_scc { match scc { Scc::Base(id) | Scc::Recursive(id) => { let Some(value_group) = extract_value_group(*id) else { diff --git a/compiler-core/checking/src/algorithm/derive/generic.rs b/compiler-core/checking/src/algorithm/derive/generic.rs index fd1c967e..68dc6cd3 100644 --- a/compiler-core/checking/src/algorithm/derive/generic.rs +++ b/compiler-core/checking/src/algorithm/derive/generic.rs @@ -119,7 +119,7 @@ where let constructor_name = if data_file == context.id { let indexed = &context.indexed; - constructor_name(&indexed) + constructor_name(indexed) } else { let indexed = context.queries.indexed(data_file)?; constructor_name(&indexed) diff --git a/compiler-core/checking/src/algorithm/kind/synonym.rs b/compiler-core/checking/src/algorithm/kind/synonym.rs index 0f95d555..9de92bbb 100644 --- a/compiler-core/checking/src/algorithm/kind/synonym.rs +++ b/compiler-core/checking/src/algorithm/kind/synonym.rs @@ -12,7 +12,7 @@ use building_types::QueryResult; use files::FileId; use indexing::TypeItemId; use itertools::Itertools; -use lowering::LoweredModule; +use lowering::GroupedModule; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::{kind, substitute, transfer, unification}; @@ -28,8 +28,8 @@ fn is_recursive_synonym( where Q: ExternalQueries, { - let is_recursive = |lowered: &LoweredModule| { - lowered.errors.iter().any(|error| { + let is_recursive = |groups: &GroupedModule| { + groups.cycle_errors.iter().any(|error| { if let lowering::LoweringError::RecursiveSynonym(recursive) = error { recursive.group.contains(&item_id) } else { @@ -39,9 +39,9 @@ where }; let is_recursive = if file_id == context.id { - is_recursive(context.lowered.as_ref()) + is_recursive(context.grouped.as_ref()) } else { - is_recursive(context.queries.lowered(file_id)?.as_ref()) + is_recursive(context.queries.grouped(file_id)?.as_ref()) }; Ok(is_recursive) diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index 431368ca..bd41b43c 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -10,8 +10,8 @@ use building_types::QueryResult; use files::FileId; use indexing::{IndexedModule, TermItemId, TypeItemId}; use lowering::{ - BinderId, GraphNodeId, ImplicitBindingId, LetBindingNameGroupId, LoweredModule, RecordPunId, - TypeItemIr, TypeVariableBindingId, + BinderId, GroupedModule, GraphNodeId, ImplicitBindingId, LetBindingNameGroupId, LoweredModule, + RecordPunId, TypeItemIr, TypeVariableBindingId, }; use resolving::ResolvedModule; use rustc_hash::FxHashMap; @@ -342,6 +342,7 @@ where pub id: FileId, pub indexed: Arc, pub lowered: Arc, + pub grouped: Arc, pub bracketed: Arc, pub sectioned: Arc, @@ -359,6 +360,7 @@ where ) -> QueryResult> { let indexed = queries.indexed(id)?; let lowered = queries.lowered(id)?; + let grouped = queries.grouped(id)?; let bracketed = queries.bracketed(id)?; let sectioned = queries.sectioned(id)?; let prim = PrimCore::collect(queries, state)?; @@ -386,6 +388,7 @@ where id, indexed, lowered, + grouped, bracketed, sectioned, prim_indexed, diff --git a/compiler-core/checking/src/lib.rs b/compiler-core/checking/src/lib.rs index cbdadbf1..cde17d8f 100644 --- a/compiler-core/checking/src/lib.rs +++ b/compiler-core/checking/src/lib.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use building_types::{QueryProxy, QueryResult}; use files::FileId; use indexing::{DeriveId, IndexedModule, InstanceId, TermItemId, TypeItemId}; -use lowering::LoweredModule; +use lowering::{GroupedModule, LoweredModule}; use resolving::ResolvedModule; use rustc_hash::FxHashMap; @@ -19,6 +19,7 @@ pub trait ExternalQueries: QueryProxy< Indexed = Arc, Lowered = Arc, + Grouped = Arc, Resolved = Arc, Bracketed = Arc, Sectioned = Arc, diff --git a/compiler-core/lowering/src/algorithm.rs b/compiler-core/lowering/src/algorithm.rs index b639ce89..2b1e36a4 100644 --- a/compiler-core/lowering/src/algorithm.rs +++ b/compiler-core/lowering/src/algorithm.rs @@ -12,7 +12,7 @@ use itertools::Itertools; use petgraph::prelude::DiGraphMap; use resolving::ResolvedModule; use rowan::ast::AstNode; -use rustc_hash::{FxBuildHasher, FxHashMap}; +use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use smol_str::SmolStr; use stabilizing::{ExpectId, StabilizedModule}; use syntax::cst; @@ -37,11 +37,10 @@ pub(crate) struct State { pub(crate) current_let_binding: Option, pub(crate) current_let_scope: Option, - pub(crate) term_graph: ItemGraph, - pub(crate) type_graph: ItemGraph, - - pub(crate) kind_graph: ItemGraph, - pub(crate) synonym_graph: ItemGraph, + pub(crate) term_edges: FxHashSet<(TermItemId, TermItemId)>, + pub(crate) type_edges: FxHashSet<(TypeItemId, TypeItemId)>, + pub(crate) kind_edges: FxHashSet<(TypeItemId, TypeItemId)>, + pub(crate) synonym_edges: FxHashSet<(TypeItemId, TypeItemId)>, pub(crate) let_binding_graph: ItemGraph, pub(crate) errors: Vec, @@ -69,19 +68,16 @@ impl State { fn begin_term(&mut self, id: TermItemId) { self.current_term = Some(id); self.current_type = None; - self.term_graph.add_node(id); } fn begin_type(&mut self, id: TypeItemId) { self.current_term = None; self.current_type = Some(id); self.current_synonym = None; - self.type_graph.add_node(id); } fn begin_synonym(&mut self, id: TypeItemId) { self.current_synonym = Some(id); - self.synonym_graph.add_node(id); } fn end_synonym(&mut self) { @@ -90,7 +86,6 @@ impl State { fn begin_kind(&mut self, id: TypeItemId) { self.current_kind = Some(id); - self.kind_graph.add_node(id); } fn end_kind(&mut self) { @@ -206,7 +201,7 @@ impl State { if context.file_id == file_id && let Some(current_id) = self.current_term { - self.term_graph.add_edge(current_id, term_id, ()); + self.term_edges.insert((current_id, term_id)); } Some((file_id, term_id)) @@ -251,16 +246,16 @@ impl State { if context.file_id == file_id && let Some(current_id) = self.current_type { - self.type_graph.add_edge(current_id, type_id, ()); + self.type_edges.insert((current_id, type_id)); if let Some(synonym_id) = self.current_synonym && let TypeItemKind::Synonym { .. } = context.indexed.items[type_id].kind { - self.synonym_graph.add_edge(synonym_id, type_id, ()); + self.synonym_edges.insert((synonym_id, type_id)); } if let Some(kind_id) = self.current_kind { - self.kind_graph.add_edge(kind_id, type_id, ()); + self.kind_edges.insert((kind_id, type_id)); } } @@ -478,8 +473,6 @@ fn lower_term_item(state: &mut State, context: &Context, item_id: TermItemId, it } TermItemKind::Value { signature, equations } => { - state.term_graph.add_node(item_id); - let signature = signature.and_then(|id| { let cst = context.stabilized.ast_ptr(id).and_then(|cst| cst.try_to_node(context.root))?; diff --git a/compiler-core/lowering/src/lib.rs b/compiler-core/lowering/src/lib.rs index 30d3e0b8..190ab229 100644 --- a/compiler-core/lowering/src/lib.rs +++ b/compiler-core/lowering/src/lib.rs @@ -8,6 +8,7 @@ pub mod intermediate; pub mod scope; pub mod source; +use std::hash::Hash; use std::sync::Arc; pub use error::*; @@ -18,10 +19,9 @@ pub use source::*; use files::FileId; use indexing::{IndexedModule, TermItemId, TypeItemId}; use petgraph::algo::tarjan_scc; -use petgraph::graphmap::NodeTrait; use petgraph::prelude::DiGraphMap; use resolving::ResolvedModule; -use rustc_hash::FxBuildHasher; +use rustc_hash::{FxBuildHasher, FxHashSet}; use stabilizing::StabilizedModule; use syntax::cst; @@ -30,12 +30,21 @@ pub struct LoweredModule { pub info: LoweringInfo, pub graph: LoweringGraph, pub nodes: LoweringGraphNodes, - pub term_scc: Vec>, - pub type_scc: Vec>, + pub term_edges: FxHashSet<(TermItemId, TermItemId)>, + pub type_edges: FxHashSet<(TypeItemId, TypeItemId)>, + pub kind_edges: FxHashSet<(TypeItemId, TypeItemId)>, + pub synonym_edges: FxHashSet<(TypeItemId, TypeItemId)>, pub errors: Vec, } #[derive(Debug, PartialEq, Eq)] +pub struct GroupedModule { + pub term_scc: Vec>, + pub type_scc: Vec>, + pub cycle_errors: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Scc { /// Non-recursive Base(T), @@ -57,54 +66,83 @@ pub fn lower_module( info, graph, nodes, - term_graph, - type_graph, - kind_graph, - synonym_graph, - mut errors, + term_edges, + type_edges, + kind_edges, + synonym_edges, + errors, .. } = algorithm::lower_module(file_id, module, prim, stabilized, indexed, resolved); - let term_scc = tarjan_scc(&term_graph); - let term_scc = term_scc.into_iter().map(into_scc(&term_graph)).collect(); - - let type_scc = tarjan_scc(&type_graph); - let type_scc = type_scc.into_iter().map(into_scc(&type_graph)).collect(); - - let kind_scc = tarjan_scc(&kind_graph); - for scc in kind_scc { - match scc.as_slice() { - [single] if !kind_graph.contains_edge(*single, *single) => { - continue; - } - group => { - let group = Arc::from(group); - errors.push(LoweringError::RecursiveKinds(RecursiveGroup { group })); - } - } - } + LoweredModule { info, graph, nodes, term_edges, type_edges, kind_edges, synonym_edges, errors } +} - let synonym_scc = tarjan_scc(&synonym_graph); - for scc in synonym_scc { - match scc.as_slice() { - [single] if !synonym_graph.contains_edge(*single, *single) => { - continue; - } - group => { - let group = Arc::from(group); - errors.push(LoweringError::RecursiveSynonym(RecursiveGroup { group })); - } - } - } +pub fn group_module(indexed: &IndexedModule, lowered: &LoweredModule) -> GroupedModule { + let term_nodes = || indexed.items.iter_terms().map(|(id, _)| id); + let type_nodes = || indexed.items.iter_types().map(|(id, _)| id); + + let term_scc = compute_scc(term_nodes(), &lowered.term_edges); + let type_scc = compute_scc(type_nodes(), &lowered.type_edges); + + let kind_cycles = find_cycles(type_nodes(), &lowered.kind_edges); + let synonym_cycles = find_cycles(type_nodes(), &lowered.synonym_edges); + + let kind_cycles = kind_cycles + .into_iter() + .map(|group| LoweringError::RecursiveKinds(RecursiveGroup { group })); + + let synonym_cycles = synonym_cycles + .into_iter() + .map(|group| LoweringError::RecursiveSynonym(RecursiveGroup { group })); + + let cycle_errors = kind_cycles.chain(synonym_cycles).collect(); + GroupedModule { term_scc, type_scc, cycle_errors } +} - LoweredModule { info, graph, nodes, term_scc, type_scc, errors } +fn compute_scc(nodes: impl Iterator, edges: &FxHashSet<(N, N)>) -> Vec> +where + N: Copy + Ord + Hash, +{ + let graph = build_graph(nodes, edges); + tarjan_scc(&graph).into_iter().map(|scc| into_scc(&graph, scc)).collect() +} + +fn find_cycles(nodes: impl Iterator, edges: &FxHashSet<(N, N)>) -> Vec> +where + N: Copy + Ord + Hash, +{ + let graph = build_graph(nodes, edges); + + let components = tarjan_scc(&graph).into_iter().filter_map(|scc| match scc.as_slice() { + [single] if !graph.contains_edge(*single, *single) => None, + _ => Some(Arc::from(scc)), + }); + + components.collect() +} + +fn build_graph( + nodes: impl Iterator, + edges: &FxHashSet<(N, N)>, +) -> DiGraphMap +where + N: Copy + Ord + Hash, +{ + let mut graph = DiGraphMap::default(); + for node in nodes { + graph.add_node(node); + } + for &(from, to) in edges { + graph.add_edge(from, to, ()); + } + graph } -fn into_scc(graph: &DiGraphMap) -> impl FnMut(Vec) -> Scc +fn into_scc(graph: &DiGraphMap, scc: Vec) -> Scc where - N: NodeTrait, + N: Copy + Ord + Hash, { - |scc| match scc[..] { + match scc[..] { [single] if !graph.contains_edge(single, single) => Scc::Base(single), [single] => Scc::Recursive(single), _ => Scc::Mutual(scc), diff --git a/docs/src/wasm/src/engine.rs b/docs/src/wasm/src/engine.rs index 01c5edd5..0c1ca698 100644 --- a/docs/src/wasm/src/engine.rs +++ b/docs/src/wasm/src/engine.rs @@ -7,7 +7,7 @@ use building_types::{ModuleNameId, ModuleNameInterner, QueryProxy, QueryResult}; use checking::{CheckedModule, Type, TypeId, TypeInterner}; use files::{FileId, Files}; use indexing::IndexedModule; -use lowering::LoweredModule; +use lowering::{GroupedModule, LoweredModule}; use parsing::FullParsedModule; use prim_constants::MODULE_MAP; use resolving::ResolvedModule; @@ -26,6 +26,7 @@ struct DerivedStorage { stabilized: FxHashMap>, indexed: FxHashMap>, lowered: FxHashMap>, + grouped: FxHashMap>, resolved: FxHashMap>, bracketed: FxHashMap>, sectioned: FxHashMap>, @@ -90,6 +91,7 @@ impl WasmQueryEngine { derived.stabilized.remove(&existing_id); derived.indexed.remove(&existing_id); derived.lowered.remove(&existing_id); + derived.grouped.remove(&existing_id); derived.resolved.remove(&existing_id); derived.bracketed.remove(&existing_id); derived.sectioned.remove(&existing_id); @@ -125,6 +127,7 @@ impl QueryProxy for WasmQueryEngine { type Stabilized = Arc; type Indexed = Arc; type Lowered = Arc; + type Grouped = Arc; type Resolved = Arc; type Bracketed = Arc; type Sectioned = Arc; @@ -191,6 +194,19 @@ impl QueryProxy for WasmQueryEngine { Ok(lowered) } + fn grouped(&self, id: FileId) -> QueryResult { + if let Some(cached) = self.derived.borrow().grouped.get(&id) { + return Ok(cached.clone()); + } + + let lowered = self.lowered(id)?; + let indexed = self.indexed(id)?; + let grouped = Arc::new(lowering::group_module(&indexed, &lowered)); + + self.derived.borrow_mut().grouped.insert(id, grouped.clone()); + Ok(grouped) + } + fn resolved(&self, id: FileId) -> QueryResult { if let Some(cached) = self.derived.borrow().resolved.get(&id) { return Ok(cached.clone()); diff --git a/tests-integration/tests/lowering.rs b/tests-integration/tests/lowering.rs index 97174050..720e5e5d 100644 --- a/tests-integration/tests/lowering.rs +++ b/tests-integration/tests/lowering.rs @@ -1,6 +1,8 @@ #[path = "lowering/generated.rs"] mod generated; +use std::iter; + use analyzer::{QueryEngine, prim}; use files::Files; @@ -41,10 +43,10 @@ type Triple3 a = Triple1 a engine.set_content(id, content); - let lowered = engine.lowered(id).unwrap(); + let groups = engine.grouped(id).unwrap(); - let terms = &lowered.term_scc; - let types = &lowered.type_scc; + let terms = &groups.term_scc; + let types = &groups.type_scc; insta::assert_debug_snapshot!((terms, types)); } @@ -73,10 +75,10 @@ infix 5 type Add as + engine.set_content(id, content); - let lowered = engine.lowered(id).unwrap(); + let groups = engine.grouped(id).unwrap(); - let terms = &lowered.term_scc; - let types = &lowered.type_scc; + let terms = &groups.term_scc; + let types = &groups.type_scc; insta::assert_debug_snapshot!((terms, types)); } @@ -101,10 +103,10 @@ c _ = 0 engine.set_content(id, content); - let lowered = engine.lowered(id).unwrap(); + let groups = engine.grouped(id).unwrap(); - let terms = &lowered.term_scc; - let types = &lowered.type_scc; + let terms = &groups.term_scc; + let types = &groups.type_scc; insta::assert_debug_snapshot!((terms, types)); } @@ -130,9 +132,9 @@ type H = H engine.set_content(id, content); - let lowered = engine.lowered(id).unwrap(); + let groups = engine.grouped(id).unwrap(); - insta::assert_debug_snapshot!(lowered.errors); + insta::assert_debug_snapshot!(groups.cycle_errors); } #[test] @@ -231,9 +233,9 @@ foreign import data Proxy :: forall k. k -> Type engine.set_content(id, content); - let lowered = engine.lowered(id).unwrap(); + let groups = engine.grouped(id).unwrap(); - insta::assert_debug_snapshot!(lowered.errors); + insta::assert_debug_snapshot!(groups.cycle_errors); } #[test] @@ -252,6 +254,8 @@ data B = B A data C (a :: Proxy D) data D (a :: Proxy C) + +foreign import data Proxy :: forall k. k -> Type "#, ); let content = files.content(id); @@ -259,6 +263,8 @@ data D (a :: Proxy C) engine.set_content(id, content); let lowered = engine.lowered(id).unwrap(); + let groups = engine.grouped(id).unwrap(); - insta::assert_debug_snapshot!(lowered.errors); + let errors: Vec<_> = iter::chain(&lowered.errors, &groups.cycle_errors).collect(); + insta::assert_debug_snapshot!(errors); } diff --git a/tests-integration/tests/snapshots/lowering__non_recursive_kinds.snap b/tests-integration/tests/snapshots/lowering__non_recursive_kinds.snap index 3f61d2ff..d4ed9020 100644 --- a/tests-integration/tests/snapshots/lowering__non_recursive_kinds.snap +++ b/tests-integration/tests/snapshots/lowering__non_recursive_kinds.snap @@ -1,16 +1,5 @@ --- source: tests-integration/tests/lowering.rs -expression: lowered.errors +expression: errors --- -[ - NotInScope( - TypeConstructor { - id: AstId(14), - }, - ), - NotInScope( - TypeConstructor { - id: AstId(19), - }, - ), -] +[] diff --git a/tests-package-set/tests/parsing.rs b/tests-package-set/tests/parsing.rs index fbbd7f07..9ea7dbb6 100644 --- a/tests-package-set/tests/parsing.rs +++ b/tests-package-set/tests/parsing.rs @@ -156,6 +156,15 @@ fn test_parallel_parse_package_set() { let lowering = start.elapsed(); println!("Lowering {lowering:?}"); + let start = Instant::now(); + source.par_iter().for_each(|&id| { + let engine = engine.snapshot(); + let grouped = engine.grouped(id); + assert!(grouped.is_ok()); + }); + let grouped = start.elapsed(); + println!("Grouped {grouped:?}"); + let start = Instant::now(); source.par_iter().for_each(|&id| { let engine = engine.snapshot(); @@ -185,6 +194,6 @@ fn test_parallel_parse_package_set() { println!( "Total {:?}", - parsing + cst_id + indexing + resolving + lowering + bracketing + sectioning + checking + parsing + cst_id + indexing + resolving + lowering + grouped + bracketing + sectioning + checking ); } From fd2e2e664b13b3bb68e8aa097064ed5abf0dd3da Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Wed, 14 Jan 2026 21:45:35 +0800 Subject: [PATCH 39/62] Fix formatting --- compiler-core/checking/src/algorithm/state.rs | 21 ++++++++++---- tests-integration/src/generated/basic.rs | 29 ++++++++++--------- tests-package-set/tests/parsing.rs | 10 ++++++- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index bd41b43c..acdd9e2c 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -10,7 +10,7 @@ use building_types::QueryResult; use files::FileId; use indexing::{IndexedModule, TermItemId, TypeItemId}; use lowering::{ - BinderId, GroupedModule, GraphNodeId, ImplicitBindingId, LetBindingNameGroupId, LoweredModule, + BinderId, GraphNodeId, GroupedModule, ImplicitBindingId, LetBindingNameGroupId, LoweredModule, RecordPunId, TypeItemIr, TypeVariableBindingId, }; use resolving::ResolvedModule; @@ -626,9 +626,10 @@ impl PrimCoerceCore { .unwrap_or_else(|| unreachable!("invariant violated: Prim.Coerce not found")); let resolved = queries.resolved(file_id)?; - let (_, coercible) = resolved.exports.lookup_type("Coercible").unwrap_or_else(|| { - unreachable!("invariant violated: Coercible not in Prim.Coerce") - }); + let (_, coercible) = resolved + .exports + .lookup_type("Coercible") + .unwrap_or_else(|| unreachable!("invariant violated: Coercible not in Prim.Coerce")); Ok(PrimCoerceCore { file_id, coercible }) } @@ -742,7 +743,8 @@ impl KnownGeneric { else { return Ok(None); }; - let Some(sum) = fetch_known_constructor(queries, storage, "Data.Generic.Rep", "Sum")? else { + let Some(sum) = fetch_known_constructor(queries, storage, "Data.Generic.Rep", "Sum")? + else { return Ok(None); }; let Some(product) = @@ -760,7 +762,14 @@ impl KnownGeneric { else { return Ok(None); }; - Ok(Some(KnownGeneric { no_constructors, constructor, sum, product, no_arguments, argument })) + Ok(Some(KnownGeneric { + no_constructors, + constructor, + sum, + product, + no_arguments, + argument, + })) } } diff --git a/tests-integration/src/generated/basic.rs b/tests-integration/src/generated/basic.rs index b084818a..c9511aa4 100644 --- a/tests-integration/src/generated/basic.rs +++ b/tests-integration/src/generated/basic.rs @@ -342,7 +342,8 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { instance_entries.sort_by_key(|(id, _)| format!("{:?}", id)); for (_instance_id, instance) in instance_entries { let class_name = resolve_class_name(engine, &indexed, id, instance.resolution); - let head = format_instance_head(engine, &class_name, &instance.constraints, &instance.arguments); + let head = + format_instance_head(engine, &class_name, &instance.constraints, &instance.arguments); let forall_prefix = format_forall_prefix(engine, &instance.kind_variables); writeln!(snapshot, "instance {forall_prefix}{head}").unwrap(); if let checking::core::InstanceKind::Chain { position, .. } = instance.kind { @@ -357,7 +358,8 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { derived_entries.sort_by_key(|(id, _)| format!("{:?}", id)); for (_derive_id, instance) in derived_entries { let class_name = resolve_class_name(engine, &indexed, id, instance.resolution); - let head = format_instance_head(engine, &class_name, &instance.constraints, &instance.arguments); + let head = + format_instance_head(engine, &class_name, &instance.constraints, &instance.arguments); let forall_prefix = format_forall_prefix(engine, &instance.kind_variables); writeln!(snapshot, "derive {forall_prefix}{head}").unwrap(); } @@ -377,10 +379,17 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { writeln!(snapshot, "NoInstanceFound {{ {} }} at {step:?}", pp(constraint)).unwrap(); } AmbiguousConstraint { constraint } => { - writeln!(snapshot, "AmbiguousConstraint {{ {} }} at {step:?}", pp(constraint)).unwrap(); + writeln!(snapshot, "AmbiguousConstraint {{ {} }} at {step:?}", pp(constraint)) + .unwrap(); } InstanceMemberTypeMismatch { expected, actual } => { - writeln!(snapshot, "InstanceMemberTypeMismatch {{ expected: {}, actual: {} }} at {step:?}", pp(expected), pp(actual)).unwrap(); + writeln!( + snapshot, + "InstanceMemberTypeMismatch {{ expected: {}, actual: {} }} at {step:?}", + pp(expected), + pp(actual) + ) + .unwrap(); } _ => { writeln!(snapshot, "{:?} at {step:?}", error.kind).unwrap(); @@ -399,11 +408,7 @@ fn resolve_class_name( ) -> String { let (class_file, class_type_id) = resolution; if class_file == current_file { - indexed.items[class_type_id] - .name - .as_deref() - .unwrap_or("") - .to_string() + indexed.items[class_type_id].name.as_deref().unwrap_or("").to_string() } else { engine .indexed(class_file) @@ -422,10 +427,8 @@ fn format_instance_head( let mut head = String::new(); if !constraints.is_empty() { - let formatted: Vec<_> = constraints - .iter() - .map(|(t, _)| pretty::print_global(engine, *t)) - .collect(); + let formatted: Vec<_> = + constraints.iter().map(|(t, _)| pretty::print_global(engine, *t)).collect(); if formatted.len() == 1 { head.push_str(&formatted[0]); } else { diff --git a/tests-package-set/tests/parsing.rs b/tests-package-set/tests/parsing.rs index 9ea7dbb6..dd100f5e 100644 --- a/tests-package-set/tests/parsing.rs +++ b/tests-package-set/tests/parsing.rs @@ -194,6 +194,14 @@ fn test_parallel_parse_package_set() { println!( "Total {:?}", - parsing + cst_id + indexing + resolving + lowering + grouped + bracketing + sectioning + checking + parsing + + cst_id + + indexing + + resolving + + lowering + + grouped + + bracketing + + sectioning + + checking ); } From 7b5f50905ea5eabe73a404ae39b46ca4248c9380 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 01:11:05 +0800 Subject: [PATCH 40/62] Add core::Role type --- compiler-core/checking/src/core.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/compiler-core/checking/src/core.rs b/compiler-core/checking/src/core.rs index 0525d982..1253d783 100644 --- a/compiler-core/checking/src/core.rs +++ b/compiler-core/checking/src/core.rs @@ -135,6 +135,24 @@ pub struct Class { pub kind_variables: debruijn::Size, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Role { + Phantom, + Representational, + Nominal, +} + +impl From for Role { + fn from(role: lowering::Role) -> Self { + match role { + lowering::Role::Phantom => Role::Phantom, + lowering::Role::Representational => Role::Representational, + lowering::Role::Nominal => Role::Nominal, + lowering::Role::Unknown => Role::Phantom, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct DataLike { pub quantified_variables: debruijn::Size, From 3fd53a7c6bc8228217c11a9c9567e0b5b8cd4f8b Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 01:11:19 +0800 Subject: [PATCH 41/62] Add roles map to CheckedModule --- compiler-core/checking/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler-core/checking/src/lib.rs b/compiler-core/checking/src/lib.rs index cde17d8f..b672f958 100644 --- a/compiler-core/checking/src/lib.rs +++ b/compiler-core/checking/src/lib.rs @@ -42,6 +42,7 @@ pub struct CheckedModule { pub derived: FxHashMap, pub classes: FxHashMap, pub data: FxHashMap, + pub roles: FxHashMap>, pub errors: Vec, } @@ -66,6 +67,10 @@ impl CheckedModule { pub fn lookup_data(&self, id: TypeItemId) -> Option { self.data.get(&id).copied() } + + pub fn lookup_roles(&self, id: TypeItemId) -> Option> { + self.roles.get(&id).cloned() + } } pub fn check_module(queries: &impl ExternalQueries, file_id: FileId) -> QueryResult { From 66725c1032eae148f7efcba1e7210d68193ee234 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 01:45:43 +0800 Subject: [PATCH 42/62] Implement role inference and checking --- .../checking/src/algorithm/type_item.rs | 220 +++++++++++++++++- compiler-core/checking/src/error.rs | 6 + .../checking/001_proxy_checking/Main.snap | 4 + .../checking/002_proxy_inference/Main.snap | 4 + .../checking/003_data_recursive/Main.snap | 4 + .../checking/004_data_mutual/Main.snap | 5 + .../checking/005_newtype_recursive/Main.snap | 4 + .../checking/006_type_synonym/Main.snap | 4 + .../checking/007_foreign_poly/Main.snap | 4 + .../009_expand_identity_synonym/Main.snap | 4 + .../checking/016_type_integer/Main.snap | 4 + .../checking/017_type_string/Main.snap | 4 + .../Main.snap | 8 +- .../031_partial_synonym_polykind/Main.snap | 5 +- .../checking/044_binder_constructor/Main.snap | 6 + .../checking/045_do_discard/Main.snap | 5 + .../fixtures/checking/046_do_bind/Main.snap | 5 + .../checking/047_do_non_monadic/Main.snap | 4 + .../checking/049_ado_discard/Main.snap | 5 + .../fixtures/checking/050_ado_bind/Main.snap | 5 + .../051_ado_non_applicative/Main.snap | 4 + .../checking/053_do_polymorphic/Main.snap | 4 + .../checking/054_ado_polymorphic/Main.snap | 4 + .../058_binder_operator_chain/Main.snap | 4 + .../fixtures/checking/062_case_of/Main.snap | 6 + .../checking/065_do_collector/Main.snap | 4 + .../checking/066_ado_collector/Main.snap | 4 + .../fixtures/checking/067_ado_let/Main.snap | 3 + .../068_expression_sections/Main.snap | 4 + .../checking/079_let_recursive/Main.snap | 3 + .../checking/089_no_instance_found/Main.snap | 3 + .../checking/090_instance_improve/Main.snap | 4 + .../091_superclass_elaboration/Main.snap | 3 + .../Main.snap | 3 + .../checking/097_instance_chains/Main.snap | 3 + .../checking/098_fundep_propagation/Main.snap | 5 + .../checking/099_builtin_int/Main.snap | 5 + .../checking/100_builtin_given/Main.snap | 6 + .../checking/101_builtin_symbol/Main.snap | 4 + .../checking/102_builtin_row/Main.snap | 4 + .../checking/103_do_row_collector/Main.snap | 3 + .../105_incomplete_type_signature/Main.snap | 4 + .../Main.snap | 4 + .../Main.snap | 4 + .../Main.snap | 4 + .../109_row_cons_invalid_discharged/Main.snap | 4 + .../Main.snap | 3 + .../111_int_add_invalid_no_instance/Main.snap | 3 + .../112_int_mul_invalid_no_instance/Main.snap | 3 + .../Main.snap | 3 + .../Main.snap | 3 + .../checking/115_empty_do_block/Main.snap | 3 + .../checking/116_empty_ado_block/Main.snap | 3 + .../checking/117_do_ado_constrained/Main.snap | 3 + .../Main.snap | 3 + .../Main.snap | 4 + .../Main.snap | 3 + .../checking/126_instance_phantom/Main.snap | 3 + .../checking/127_derive_eq_simple/Main.snap | 5 + .../128_type_operator_mutual/Main.snap | 4 + .../129_derive_eq_with_fields/Main.snap | 4 + .../130_derive_eq_parameterized/Main.snap | 3 + .../131_derive_eq_missing_instance/Main.snap | 4 + .../132_derive_eq_1_higher_kinded/Main.snap | 4 + .../checking/133_derive_eq_partial/Main.snap | 3 + .../checking/134_derive_ord_simple/Main.snap | 6 + .../135_derive_ord_1_higher_kinded/Main.snap | 4 + .../136_derive_nested_higher_kinded/Main.snap | 6 + .../137_derive_newtype_simple/Main.snap | 3 + .../Main.snap | 3 + .../139_derive_newtype_with_given/Main.snap | 3 + .../140_derive_newtype_recursive/Main.snap | 3 + .../141_derive_newtype_phantom/Main.snap | 3 + .../142_derive_newtype_not_newtype/Main.snap | 3 + .../Main.snap | 3 + .../Main.snap | 3 + .../145_derive_newtype_multi_param/Main.snap | 3 + .../146_derive_functor_simple/Main.snap | 5 + .../Main.snap | 4 + .../Main.snap | 5 + .../149_derive_bifunctor_simple/Main.snap | 5 + .../Main.snap | 4 + .../Main.snap | 3 + .../152_derive_contravariant_simple/Main.snap | 5 + .../153_derive_contravariant_error/Main.snap | 4 + .../154_derive_profunctor_simple/Main.snap | 5 + .../155_derive_profunctor_error/Main.snap | 4 + .../Main.snap | 3 + .../Main.snap | 4 + .../158_derive_foldable_simple/Main.snap | 5 + .../Main.snap | 4 + .../160_derive_bifoldable_simple/Main.snap | 5 + .../Main.snap | 4 + .../162_derive_traversable_simple/Main.snap | 5 + .../Main.snap | 3 + .../164_derive_bitraversable_simple/Main.snap | 4 + .../Main.snap | 3 + .../Main.snap | 3 + .../checking/167_derive_eq_1/Main.snap | 10 + .../checking/168_derive_ord_1/Main.snap | 6 + .../169_derive_newtype_class_simple/Main.snap | 3 + .../Main.snap | 3 + .../Main.snap | 3 + .../172_derive_generic_simple/Main.snap | 9 + .../Main.snap | 4 + .../174_role_inference_phantom/Main.purs | 3 + .../174_role_inference_phantom/Main.snap | 18 ++ .../Main.purs | 3 + .../Main.snap | 19 ++ .../Main.purs | 6 + .../Main.snap | 23 ++ .../Main.purs | 3 + .../Main.snap | 18 ++ .../178_role_inference_nested/Main.purs | 5 + .../178_role_inference_nested/Main.snap | 26 +++ .../179_role_inference_recursive/Main.purs | 3 + .../179_role_inference_recursive/Main.snap | 19 ++ .../180_role_declaration_strengthen/Main.purs | 5 + .../180_role_declaration_strengthen/Main.snap | 19 ++ .../Main.purs | 5 + .../Main.snap | 21 ++ .../182_role_declaration_foreign/Main.purs | 5 + .../182_role_declaration_foreign/Main.snap | 11 + tests-integration/src/generated/basic.rs | 19 +- tests-integration/tests/checking/generated.rs | 18 ++ 125 files changed, 883 insertions(+), 11 deletions(-) create mode 100644 tests-integration/fixtures/checking/174_role_inference_phantom/Main.purs create mode 100644 tests-integration/fixtures/checking/174_role_inference_phantom/Main.snap create mode 100644 tests-integration/fixtures/checking/175_role_inference_representational/Main.purs create mode 100644 tests-integration/fixtures/checking/175_role_inference_representational/Main.snap create mode 100644 tests-integration/fixtures/checking/176_role_inference_nominal_constraint/Main.purs create mode 100644 tests-integration/fixtures/checking/176_role_inference_nominal_constraint/Main.snap create mode 100644 tests-integration/fixtures/checking/177_role_inference_nominal_parametric/Main.purs create mode 100644 tests-integration/fixtures/checking/177_role_inference_nominal_parametric/Main.snap create mode 100644 tests-integration/fixtures/checking/178_role_inference_nested/Main.purs create mode 100644 tests-integration/fixtures/checking/178_role_inference_nested/Main.snap create mode 100644 tests-integration/fixtures/checking/179_role_inference_recursive/Main.purs create mode 100644 tests-integration/fixtures/checking/179_role_inference_recursive/Main.snap create mode 100644 tests-integration/fixtures/checking/180_role_declaration_strengthen/Main.purs create mode 100644 tests-integration/fixtures/checking/180_role_declaration_strengthen/Main.snap create mode 100644 tests-integration/fixtures/checking/181_role_declaration_loosen_error/Main.purs create mode 100644 tests-integration/fixtures/checking/181_role_declaration_loosen_error/Main.snap create mode 100644 tests-integration/fixtures/checking/182_role_declaration_foreign/Main.purs create mode 100644 tests-integration/fixtures/checking/182_role_declaration_foreign/Main.snap diff --git a/compiler-core/checking/src/algorithm/type_item.rs b/compiler-core/checking/src/algorithm/type_item.rs index 6593e830..633a718b 100644 --- a/compiler-core/checking/src/algorithm/type_item.rs +++ b/compiler-core/checking/src/algorithm/type_item.rs @@ -2,17 +2,18 @@ use std::sync::Arc; use building_types::QueryResult; use indexing::{TermItemId, TypeItemId}; -use itertools::Itertools; +use itertools::{Itertools, izip}; use lowering::{ ClassIr, DataIr, NewtypeIr, SynonymIr, TermItemIr, TypeItemIr, TypeVariableBinding, }; use smol_str::SmolStr; use crate::ExternalQueries; +use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState, CheckedConstructor}; use crate::algorithm::{inspect, kind, quantify, transfer, unification}; use crate::core::{ - Class, DataLike, ForallBinder, Operator, Synonym, Type, TypeId, Variable, debruijn, + Class, DataLike, ForallBinder, Operator, Role, Synonym, Type, TypeId, Variable, debruijn, }; use crate::error::{ErrorKind, ErrorStep}; @@ -32,6 +33,7 @@ pub struct CheckedData { pub kind_variables: Vec, pub type_variables: Vec, pub constructors: Vec, + pub declared_roles: Arc<[lowering::Role]>, } #[derive(Clone)] @@ -76,18 +78,18 @@ where }; match item { - TypeItemIr::DataGroup { signature, data, .. } => { + TypeItemIr::DataGroup { signature, data, roles } => { let Some(DataIr { variables }) = data else { return Ok(None); }; - check_data_definition(state, context, item_id, *signature, variables) + check_data_definition(state, context, item_id, *signature, variables, roles) } - TypeItemIr::NewtypeGroup { signature, newtype, .. } => { + TypeItemIr::NewtypeGroup { signature, newtype, roles } => { let Some(NewtypeIr { variables }) = newtype else { return Ok(None); }; - check_data_definition(state, context, item_id, *signature, variables) + check_data_definition(state, context, item_id, *signature, variables, roles) } TypeItemIr::SynonymGroup { signature, synonym } => { @@ -104,7 +106,9 @@ where check_class_definition(state, context, item_id, *signature, class) } - TypeItemIr::Foreign { .. } => Ok(None), + TypeItemIr::Foreign { roles, .. } => { + check_foreign_definition(state, context, item_id, roles) + } TypeItemIr::Operator { associativity, precedence, resolution } => { let Some(associativity) = *associativity else { return Ok(None) }; @@ -130,6 +134,7 @@ fn check_data_definition( item_id: TypeItemId, signature: Option, variables: &[TypeVariableBinding], + declared_roles: &Arc<[lowering::Role]>, ) -> QueryResult> where Q: ExternalQueries, @@ -168,6 +173,7 @@ where kind_variables, type_variables, constructors, + declared_roles: declared_roles.clone(), }))) } @@ -415,9 +421,14 @@ fn commit_data( where Q: ExternalQueries, { - let CheckedData { inferred_kind, kind_variables, type_variables, constructors } = checked; + let CheckedData { inferred_kind, kind_variables, type_variables, constructors, declared_roles } = + checked; + let kind_variable_count = kind_variables.len() as u32; + let inferred_roles = infer_roles(state, &type_variables, &constructors); + check_roles(state, item_id, &inferred_roles, &declared_roles, false); + if let Some(inferred_kind) = inferred_kind { let Some((quantified_type, quantified_variables)) = quantify::quantify(state, inferred_kind) @@ -846,3 +857,196 @@ where Ok(constructors) } + +/// Infers roles for type parameters based on their usage in constructors. +fn infer_roles( + state: &mut CheckState, + type_variables: &[ForallBinder], + constructors: &[CheckedConstructor], +) -> Vec { + fn aux( + state: &mut CheckState, + roles: &mut [Role], + variables: &[ForallBinder], + type_id: TypeId, + under_constraint: bool, + is_variable_argument: bool, + ) { + let type_id = state.normalize_type(type_id); + match state.storage[type_id].clone() { + Type::Variable(Variable::Bound(level)) => { + if let Some(index) = variables.iter().position(|v| v.level == level) { + // The following cases infer to nominal roles: + // + // ``` + // -- `a` appears under a constraint + // data Shown a = Shown ((Show a => a -> String) -> String) + // + // -- `a` appears under another variable + // data Parametric f a = Parametric (f a) + // ``` + let role = if under_constraint || is_variable_argument { + Role::Nominal + } else { + Role::Representational + }; + roles[index] = roles[index].max(role); + } + } + + Type::Application(function, argument) => { + let function_id = state.normalize_type(function); + let is_type_variable = + matches!(state.storage[function_id], Type::Variable(Variable::Bound(_))); + + aux(state, roles, variables, function, under_constraint, false); + aux(state, roles, variables, argument, under_constraint, is_type_variable); + } + + Type::Constrained(constraint, inner) => { + aux(state, roles, variables, constraint, true, false); + aux(state, roles, variables, inner, true, false); + } + + Type::Forall(binder, inner) => { + aux(state, roles, variables, binder.kind, under_constraint, false); + aux(state, roles, variables, inner, under_constraint, false); + } + + Type::Function(arg, result) => { + aux(state, roles, variables, arg, under_constraint, false); + aux(state, roles, variables, result, under_constraint, false); + } + + Type::KindApplication(function, argument) => { + aux(state, roles, variables, function, under_constraint, false); + aux(state, roles, variables, argument, under_constraint, false); + } + + Type::Kinded(inner, kind) => { + aux(state, roles, variables, inner, under_constraint, false); + aux(state, roles, variables, kind, under_constraint, false); + } + + Type::OperatorApplication(_, _, left, right) => { + aux(state, roles, variables, left, under_constraint, false); + aux(state, roles, variables, right, under_constraint, false); + } + + Type::Row(row) => { + for field in row.fields.iter() { + aux(state, roles, variables, field.id, under_constraint, false); + } + if let Some(tail) = row.tail { + aux(state, roles, variables, tail, under_constraint, false); + } + } + + Type::SynonymApplication(_, _, _, arguments) => { + for &arg in arguments.iter() { + aux(state, roles, variables, arg, under_constraint, false); + } + } + + Type::Constructor(_, _) + | Type::Integer(_) + | Type::Operator(_, _) + | Type::String(_, _) + | Type::Unification(_) + | Type::Variable(_) + | Type::Unknown => (), + } + } + + let mut roles = vec![Role::Phantom; type_variables.len()]; + + for constructor in constructors { + for &field_type in &constructor.arguments { + aux(state, &mut roles, type_variables, field_type, false, false); + } + } + + roles +} + +/// Check declared roles against inferred roles, inserting the final roles. +/// +/// `data` and `newtype` can only strengthen roles, Phantom -> Nominal; `foreign` +/// can loosen roles, Nominal -> Phantom, since there's no usage to infer from. +fn check_roles( + state: &mut CheckState, + type_id: TypeItemId, + inferred: &[Role], + declared: &[lowering::Role], + is_foreign: bool, +) { + let mut validated = inferred.to_vec(); + + for (index, (validated, &inferred, declared)) in + izip!(validated.iter_mut(), inferred, declared).enumerate() + { + let declared = match declared { + lowering::Role::Phantom => Role::Phantom, + lowering::Role::Representational => Role::Representational, + lowering::Role::Nominal => Role::Nominal, + lowering::Role::Unknown => continue, + }; + + if is_foreign { + *validated = declared; + } else if declared >= inferred { + *validated = declared; + } else { + state.insert_error(ErrorKind::InvalidRoleDeclaration { + type_id, + parameter_index: index, + declared, + inferred, + }); + } + } + + let validated = Arc::from(validated); + state.checked.roles.insert(type_id, validated); +} + +/// Counts the number of type parameters in a kind by counting function arrows. +fn count_kind_arguments(state: &mut CheckState, type_id: TypeId) -> usize { + let mut count = 0; + let mut current_id = type_id; + + safe_loop! { + current_id = state.normalize_type(current_id); + match &state.storage[current_id] { + Type::Function(_, result) => { + count += 1; + current_id = *result; + } + Type::Forall(_, inner) => { + current_id = *inner; + } + _ => break, + } + } + + count +} + +/// Checks a foreign type declaration. +fn check_foreign_definition( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, + declared_roles: &Arc<[lowering::Role]>, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let stored_kind = kind::lookup_file_type(state, context, context.id, item_id)?; + let parameter_count = count_kind_arguments(state, stored_kind); + + let inferred_roles = vec![Role::Nominal; parameter_count]; + check_roles(state, item_id, &inferred_roles, declared_roles, true); + + Ok(None) +} diff --git a/compiler-core/checking/src/error.rs b/compiler-core/checking/src/error.rs index 75ab397f..4f914bbd 100644 --- a/compiler-core/checking/src/error.rs +++ b/compiler-core/checking/src/error.rs @@ -87,6 +87,12 @@ pub enum ErrorKind { expected: u32, actual: u32, }, + InvalidRoleDeclaration { + type_id: indexing::TypeItemId, + parameter_index: usize, + declared: crate::core::Role, + inferred: crate::core::Role, + }, } #[derive(Debug, PartialEq, Eq)] diff --git a/tests-integration/fixtures/checking/001_proxy_checking/Main.snap b/tests-integration/fixtures/checking/001_proxy_checking/Main.snap index fd9bdca9..3b8f7595 100644 --- a/tests-integration/fixtures/checking/001_proxy_checking/Main.snap +++ b/tests-integration/fixtures/checking/001_proxy_checking/Main.snap @@ -12,3 +12,7 @@ Data Proxy Quantified = :0 Kind = :1 + + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking/002_proxy_inference/Main.snap b/tests-integration/fixtures/checking/002_proxy_inference/Main.snap index 5021cb1b..22188897 100644 --- a/tests-integration/fixtures/checking/002_proxy_inference/Main.snap +++ b/tests-integration/fixtures/checking/002_proxy_inference/Main.snap @@ -12,3 +12,7 @@ Data Proxy Quantified = :1 Kind = :0 + + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking/003_data_recursive/Main.snap b/tests-integration/fixtures/checking/003_data_recursive/Main.snap index 0ac744a5..4016bb24 100644 --- a/tests-integration/fixtures/checking/003_data_recursive/Main.snap +++ b/tests-integration/fixtures/checking/003_data_recursive/Main.snap @@ -13,3 +13,7 @@ Data List Quantified = :0 Kind = :0 + + +Roles +List = [Representational] diff --git a/tests-integration/fixtures/checking/004_data_mutual/Main.snap b/tests-integration/fixtures/checking/004_data_mutual/Main.snap index 7392175b..0b0411fe 100644 --- a/tests-integration/fixtures/checking/004_data_mutual/Main.snap +++ b/tests-integration/fixtures/checking/004_data_mutual/Main.snap @@ -19,3 +19,8 @@ Tree Forest Quantified = :0 Kind = :0 + + +Roles +Tree = [Representational] +Forest = [Representational] diff --git a/tests-integration/fixtures/checking/005_newtype_recursive/Main.snap b/tests-integration/fixtures/checking/005_newtype_recursive/Main.snap index 94e57fd6..518d0b78 100644 --- a/tests-integration/fixtures/checking/005_newtype_recursive/Main.snap +++ b/tests-integration/fixtures/checking/005_newtype_recursive/Main.snap @@ -12,3 +12,7 @@ Data Mu Quantified = :0 Kind = :0 + + +Roles +Mu = [Representational] diff --git a/tests-integration/fixtures/checking/006_type_synonym/Main.snap b/tests-integration/fixtures/checking/006_type_synonym/Main.snap index f649b0f1..3fa86715 100644 --- a/tests-integration/fixtures/checking/006_type_synonym/Main.snap +++ b/tests-integration/fixtures/checking/006_type_synonym/Main.snap @@ -43,3 +43,7 @@ CheckApplyElab = forall (x :: Type) (y :: Type) (f :: x -> y) (a :: x). f a Quantified = :0 Kind = :2 Type = :2 + + +Roles +Tuple = [Nominal, Nominal] diff --git a/tests-integration/fixtures/checking/007_foreign_poly/Main.snap b/tests-integration/fixtures/checking/007_foreign_poly/Main.snap index fd5a2567..f8833b48 100644 --- a/tests-integration/fixtures/checking/007_foreign_poly/Main.snap +++ b/tests-integration/fixtures/checking/007_foreign_poly/Main.snap @@ -13,3 +13,7 @@ InferTuplePoly = forall (t7 :: Type) (t8 :: Type) (x :: t7) (y :: t8). TuplePoly Quantified = :2 Kind = :0 Type = :2 + + +Roles +TuplePoly = [Nominal, Nominal] diff --git a/tests-integration/fixtures/checking/009_expand_identity_synonym/Main.snap b/tests-integration/fixtures/checking/009_expand_identity_synonym/Main.snap index 0f911fa4..6117a786 100644 --- a/tests-integration/fixtures/checking/009_expand_identity_synonym/Main.snap +++ b/tests-integration/fixtures/checking/009_expand_identity_synonym/Main.snap @@ -25,3 +25,7 @@ Test2 = Identity Digit Quantified = :0 Kind = :0 Type = :0 + + +Roles +Digit = [] diff --git a/tests-integration/fixtures/checking/016_type_integer/Main.snap b/tests-integration/fixtures/checking/016_type_integer/Main.snap index 4470dc1e..62506109 100644 --- a/tests-integration/fixtures/checking/016_type_integer/Main.snap +++ b/tests-integration/fixtures/checking/016_type_integer/Main.snap @@ -37,3 +37,7 @@ Hex = Proxy @Int 16777215 Quantified = :0 Kind = :0 Type = :0 + + +Roles +Proxy = [Nominal] diff --git a/tests-integration/fixtures/checking/017_type_string/Main.snap b/tests-integration/fixtures/checking/017_type_string/Main.snap index 94fced78..bb54d20d 100644 --- a/tests-integration/fixtures/checking/017_type_string/Main.snap +++ b/tests-integration/fixtures/checking/017_type_string/Main.snap @@ -49,3 +49,7 @@ RawWithQuote = Proxy @Symbol """"hello"""" Quantified = :0 Kind = :0 Type = :0 + + +Roles +Proxy = [Nominal] diff --git a/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap b/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap index d7dd7ff9..6d3df352 100644 --- a/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap +++ b/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap @@ -1,6 +1,5 @@ --- source: tests-integration/tests/checking/generated.rs -assertion_line: 12 expression: report --- Terms @@ -69,3 +68,10 @@ StateT Tuple Quantified = :0 Kind = :0 + + +Roles +Identity = [Representational] +ReaderT = [Representational, Representational, Nominal] +StateT = [Representational, Representational, Representational] +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking/031_partial_synonym_polykind/Main.snap b/tests-integration/fixtures/checking/031_partial_synonym_polykind/Main.snap index 1f6286c9..12517656 100644 --- a/tests-integration/fixtures/checking/031_partial_synonym_polykind/Main.snap +++ b/tests-integration/fixtures/checking/031_partial_synonym_polykind/Main.snap @@ -1,6 +1,5 @@ --- source: tests-integration/tests/checking/generated.rs -assertion_line: 12 expression: report --- Terms @@ -80,3 +79,7 @@ Data Function Quantified = :0 Kind = :0 + + +Roles +Function = [Phantom, Phantom] diff --git a/tests-integration/fixtures/checking/044_binder_constructor/Main.snap b/tests-integration/fixtures/checking/044_binder_constructor/Main.snap index 244cc65b..8a1f7a69 100644 --- a/tests-integration/fixtures/checking/044_binder_constructor/Main.snap +++ b/tests-integration/fixtures/checking/044_binder_constructor/Main.snap @@ -34,3 +34,9 @@ Maybe List Quantified = :0 Kind = :0 + + +Roles +Pair = [Representational, Representational] +Maybe = [Representational] +List = [Representational] diff --git a/tests-integration/fixtures/checking/045_do_discard/Main.snap b/tests-integration/fixtures/checking/045_do_discard/Main.snap index e7a7de0b..17da3dd7 100644 --- a/tests-integration/fixtures/checking/045_do_discard/Main.snap +++ b/tests-integration/fixtures/checking/045_do_discard/Main.snap @@ -19,3 +19,8 @@ Data Unit Quantified = :0 Kind = :0 + + +Roles +Effect = [Nominal] +Unit = [] diff --git a/tests-integration/fixtures/checking/046_do_bind/Main.snap b/tests-integration/fixtures/checking/046_do_bind/Main.snap index ad77c33a..ba585cab 100644 --- a/tests-integration/fixtures/checking/046_do_bind/Main.snap +++ b/tests-integration/fixtures/checking/046_do_bind/Main.snap @@ -18,3 +18,8 @@ Data Tuple Quantified = :0 Kind = :0 + + +Roles +Effect = [Nominal] +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking/047_do_non_monadic/Main.snap b/tests-integration/fixtures/checking/047_do_non_monadic/Main.snap index 5c9c9c2e..e995bc82 100644 --- a/tests-integration/fixtures/checking/047_do_non_monadic/Main.snap +++ b/tests-integration/fixtures/checking/047_do_non_monadic/Main.snap @@ -16,3 +16,7 @@ Data Tuple Quantified = :0 Kind = :0 + + +Roles +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking/049_ado_discard/Main.snap b/tests-integration/fixtures/checking/049_ado_discard/Main.snap index e0e045ad..4a47a052 100644 --- a/tests-integration/fixtures/checking/049_ado_discard/Main.snap +++ b/tests-integration/fixtures/checking/049_ado_discard/Main.snap @@ -19,3 +19,8 @@ Data Unit Quantified = :0 Kind = :0 + + +Roles +Effect = [Nominal] +Unit = [] diff --git a/tests-integration/fixtures/checking/050_ado_bind/Main.snap b/tests-integration/fixtures/checking/050_ado_bind/Main.snap index 2d16e859..fa650034 100644 --- a/tests-integration/fixtures/checking/050_ado_bind/Main.snap +++ b/tests-integration/fixtures/checking/050_ado_bind/Main.snap @@ -18,3 +18,8 @@ Data Tuple Quantified = :0 Kind = :0 + + +Roles +Effect = [Nominal] +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking/051_ado_non_applicative/Main.snap b/tests-integration/fixtures/checking/051_ado_non_applicative/Main.snap index a7eed7b8..6427d4d4 100644 --- a/tests-integration/fixtures/checking/051_ado_non_applicative/Main.snap +++ b/tests-integration/fixtures/checking/051_ado_non_applicative/Main.snap @@ -17,3 +17,7 @@ Data Tuple Quantified = :0 Kind = :0 + + +Roles +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap b/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap index c281f778..74fc7804 100644 --- a/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap +++ b/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap @@ -17,3 +17,7 @@ Data Tuple Quantified = :0 Kind = :0 + + +Roles +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap b/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap index 783a485d..6fc9f5f5 100644 --- a/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap +++ b/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap @@ -17,3 +17,7 @@ Data Tuple Quantified = :0 Kind = :0 + + +Roles +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking/058_binder_operator_chain/Main.snap b/tests-integration/fixtures/checking/058_binder_operator_chain/Main.snap index 2580e1b4..4779edfd 100644 --- a/tests-integration/fixtures/checking/058_binder_operator_chain/Main.snap +++ b/tests-integration/fixtures/checking/058_binder_operator_chain/Main.snap @@ -17,3 +17,7 @@ Data List Quantified = :0 Kind = :0 + + +Roles +List = [Representational] diff --git a/tests-integration/fixtures/checking/062_case_of/Main.snap b/tests-integration/fixtures/checking/062_case_of/Main.snap index ef1c3f52..41dd6995 100644 --- a/tests-integration/fixtures/checking/062_case_of/Main.snap +++ b/tests-integration/fixtures/checking/062_case_of/Main.snap @@ -34,3 +34,9 @@ Either Tuple Quantified = :0 Kind = :0 + + +Roles +Maybe = [Representational] +Either = [Representational, Representational] +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking/065_do_collector/Main.snap b/tests-integration/fixtures/checking/065_do_collector/Main.snap index a3223f87..371c8f3c 100644 --- a/tests-integration/fixtures/checking/065_do_collector/Main.snap +++ b/tests-integration/fixtures/checking/065_do_collector/Main.snap @@ -21,3 +21,7 @@ test3 :: Collector (Tuple Int (Tuple String { y :: Int, z :: String })) { y :: I Types Collector :: Type -> Type -> Type Tuple :: Type -> Type -> Type + +Roles +Collector = [Nominal, Nominal] +Tuple = [Nominal, Nominal] diff --git a/tests-integration/fixtures/checking/066_ado_collector/Main.snap b/tests-integration/fixtures/checking/066_ado_collector/Main.snap index cf77fa1e..5670b4cb 100644 --- a/tests-integration/fixtures/checking/066_ado_collector/Main.snap +++ b/tests-integration/fixtures/checking/066_ado_collector/Main.snap @@ -20,3 +20,7 @@ test3 :: forall (t51 :: Type). Collector (Tuple (Tuple Int t51) String) { x :: I Types Collector :: Type -> Type -> Type Tuple :: Type -> Type -> Type + +Roles +Collector = [Nominal, Nominal] +Tuple = [Nominal, Nominal] diff --git a/tests-integration/fixtures/checking/067_ado_let/Main.snap b/tests-integration/fixtures/checking/067_ado_let/Main.snap index d50f8adb..15e2ea79 100644 --- a/tests-integration/fixtures/checking/067_ado_let/Main.snap +++ b/tests-integration/fixtures/checking/067_ado_let/Main.snap @@ -10,3 +10,6 @@ test :: Effect { x :: Int, y :: { x :: Int }, z :: String } Types Effect :: Type -> Type + +Roles +Effect = [Nominal] diff --git a/tests-integration/fixtures/checking/068_expression_sections/Main.snap b/tests-integration/fixtures/checking/068_expression_sections/Main.snap index 25603427..90d55e2f 100644 --- a/tests-integration/fixtures/checking/068_expression_sections/Main.snap +++ b/tests-integration/fixtures/checking/068_expression_sections/Main.snap @@ -33,3 +33,7 @@ Data Tuple Quantified = :0 Kind = :0 + + +Roles +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking/079_let_recursive/Main.snap b/tests-integration/fixtures/checking/079_let_recursive/Main.snap index d4290fe3..9ee03edb 100644 --- a/tests-integration/fixtures/checking/079_let_recursive/Main.snap +++ b/tests-integration/fixtures/checking/079_let_recursive/Main.snap @@ -13,3 +13,6 @@ mixedRecursion :: Int -> Int Types Void :: Type + +Roles +Void = [] diff --git a/tests-integration/fixtures/checking/089_no_instance_found/Main.snap b/tests-integration/fixtures/checking/089_no_instance_found/Main.snap index 0bcc7879..c3a35638 100644 --- a/tests-integration/fixtures/checking/089_no_instance_found/Main.snap +++ b/tests-integration/fixtures/checking/089_no_instance_found/Main.snap @@ -17,6 +17,9 @@ Foo Kind = :0 +Roles +Foo = [] + Classes class Eq (&0 :: Type) diff --git a/tests-integration/fixtures/checking/090_instance_improve/Main.snap b/tests-integration/fixtures/checking/090_instance_improve/Main.snap index 26f88cdc..843c4db4 100644 --- a/tests-integration/fixtures/checking/090_instance_improve/Main.snap +++ b/tests-integration/fixtures/checking/090_instance_improve/Main.snap @@ -21,6 +21,10 @@ False Kind = :0 +Roles +True = [] +False = [] + Classes class TypeEq (&1 :: Type) (&2 :: Type) (&3 :: &0) diff --git a/tests-integration/fixtures/checking/091_superclass_elaboration/Main.snap b/tests-integration/fixtures/checking/091_superclass_elaboration/Main.snap index 0b476249..b5104d5a 100644 --- a/tests-integration/fixtures/checking/091_superclass_elaboration/Main.snap +++ b/tests-integration/fixtures/checking/091_superclass_elaboration/Main.snap @@ -22,6 +22,9 @@ Ordering Kind = :0 +Roles +Ordering = [] + Classes class Eq (&0 :: Type) class Eq &0 <= Ord (&0 :: Type) diff --git a/tests-integration/fixtures/checking/094_let_binding_constraint_error/Main.snap b/tests-integration/fixtures/checking/094_let_binding_constraint_error/Main.snap index 954a6784..a9bd2503 100644 --- a/tests-integration/fixtures/checking/094_let_binding_constraint_error/Main.snap +++ b/tests-integration/fixtures/checking/094_let_binding_constraint_error/Main.snap @@ -17,6 +17,9 @@ Foo Kind = :0 +Roles +Foo = [] + Classes class Eq (&0 :: Type) diff --git a/tests-integration/fixtures/checking/097_instance_chains/Main.snap b/tests-integration/fixtures/checking/097_instance_chains/Main.snap index 1c9a92b0..58fcb5a0 100644 --- a/tests-integration/fixtures/checking/097_instance_chains/Main.snap +++ b/tests-integration/fixtures/checking/097_instance_chains/Main.snap @@ -18,6 +18,9 @@ Proxy Kind = :0 +Roles +Proxy = [Phantom] + Classes class TypeEq (&3 :: &0) (&4 :: &1) (&5 :: &2) diff --git a/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap b/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap index a9e9a73b..d66b0514 100644 --- a/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap +++ b/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap @@ -22,6 +22,11 @@ Proxy Kind = :0 +Roles +Proxy = [Phantom] +Z = [] +S = [Nominal] + Classes class IsZero (&2 :: &0) (&3 :: &1) class And (&3 :: &0) (&4 :: &1) (&5 :: &2) diff --git a/tests-integration/fixtures/checking/099_builtin_int/Main.snap b/tests-integration/fixtures/checking/099_builtin_int/Main.snap index f421134a..09185331 100644 --- a/tests-integration/fixtures/checking/099_builtin_int/Main.snap +++ b/tests-integration/fixtures/checking/099_builtin_int/Main.snap @@ -36,3 +36,8 @@ Proxy Unit Quantified = :0 Kind = :0 + + +Roles +Proxy = [Phantom] +Unit = [] diff --git a/tests-integration/fixtures/checking/100_builtin_given/Main.snap b/tests-integration/fixtures/checking/100_builtin_given/Main.snap index 07dd85d4..30feedcf 100644 --- a/tests-integration/fixtures/checking/100_builtin_given/Main.snap +++ b/tests-integration/fixtures/checking/100_builtin_given/Main.snap @@ -53,3 +53,9 @@ N Something Quantified = :0 Kind = :0 + + +Roles +Proxy = [Phantom] +N = [] +Something = [] diff --git a/tests-integration/fixtures/checking/101_builtin_symbol/Main.snap b/tests-integration/fixtures/checking/101_builtin_symbol/Main.snap index bfefe760..f7cd5b11 100644 --- a/tests-integration/fixtures/checking/101_builtin_symbol/Main.snap +++ b/tests-integration/fixtures/checking/101_builtin_symbol/Main.snap @@ -42,3 +42,7 @@ Data Proxy Quantified = :0 Kind = :1 + + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking/102_builtin_row/Main.snap b/tests-integration/fixtures/checking/102_builtin_row/Main.snap index e9ba29b3..23687cf1 100644 --- a/tests-integration/fixtures/checking/102_builtin_row/Main.snap +++ b/tests-integration/fixtures/checking/102_builtin_row/Main.snap @@ -87,3 +87,7 @@ Data Proxy Quantified = :0 Kind = :1 + + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking/103_do_row_collector/Main.snap b/tests-integration/fixtures/checking/103_do_row_collector/Main.snap index b9cb04ba..1f142c7d 100644 --- a/tests-integration/fixtures/checking/103_do_row_collector/Main.snap +++ b/tests-integration/fixtures/checking/103_do_row_collector/Main.snap @@ -39,3 +39,6 @@ test4 :: Types Collector :: Int -> Row Type -> Type -> Type + +Roles +Collector = [Nominal, Nominal, Nominal] diff --git a/tests-integration/fixtures/checking/105_incomplete_type_signature/Main.snap b/tests-integration/fixtures/checking/105_incomplete_type_signature/Main.snap index fb3b6177..48d84aa2 100644 --- a/tests-integration/fixtures/checking/105_incomplete_type_signature/Main.snap +++ b/tests-integration/fixtures/checking/105_incomplete_type_signature/Main.snap @@ -12,3 +12,7 @@ Data Foo Quantified = :0 Kind = :0 + + +Roles +Foo = [] diff --git a/tests-integration/fixtures/checking/106_row_union_invalid_discharged/Main.snap b/tests-integration/fixtures/checking/106_row_union_invalid_discharged/Main.snap index af87489e..628c72b7 100644 --- a/tests-integration/fixtures/checking/106_row_union_invalid_discharged/Main.snap +++ b/tests-integration/fixtures/checking/106_row_union_invalid_discharged/Main.snap @@ -18,3 +18,7 @@ Data Proxy Quantified = :0 Kind = :1 + + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking/107_symbol_append_invalid_discharged/Main.snap b/tests-integration/fixtures/checking/107_symbol_append_invalid_discharged/Main.snap index 3ec57bc9..25a45996 100644 --- a/tests-integration/fixtures/checking/107_symbol_append_invalid_discharged/Main.snap +++ b/tests-integration/fixtures/checking/107_symbol_append_invalid_discharged/Main.snap @@ -14,3 +14,7 @@ Data Proxy Quantified = :0 Kind = :1 + + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking/108_symbol_cons_invalid_discharged/Main.snap b/tests-integration/fixtures/checking/108_symbol_cons_invalid_discharged/Main.snap index be875079..46cf0cce 100644 --- a/tests-integration/fixtures/checking/108_symbol_cons_invalid_discharged/Main.snap +++ b/tests-integration/fixtures/checking/108_symbol_cons_invalid_discharged/Main.snap @@ -14,3 +14,7 @@ Data Proxy Quantified = :0 Kind = :1 + + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking/109_row_cons_invalid_discharged/Main.snap b/tests-integration/fixtures/checking/109_row_cons_invalid_discharged/Main.snap index 496dad83..97513cdd 100644 --- a/tests-integration/fixtures/checking/109_row_cons_invalid_discharged/Main.snap +++ b/tests-integration/fixtures/checking/109_row_cons_invalid_discharged/Main.snap @@ -18,3 +18,7 @@ Data Proxy Quantified = :0 Kind = :1 + + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking/110_row_lacks_invalid_no_instance/Main.snap b/tests-integration/fixtures/checking/110_row_lacks_invalid_no_instance/Main.snap index 9910d0a2..535aa9c4 100644 --- a/tests-integration/fixtures/checking/110_row_lacks_invalid_no_instance/Main.snap +++ b/tests-integration/fixtures/checking/110_row_lacks_invalid_no_instance/Main.snap @@ -16,5 +16,8 @@ Proxy Kind = :1 +Roles +Proxy = [Phantom] + Errors NoInstanceFound { Lacks @Type "b" ( a :: Int, b :: String ) } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/111_int_add_invalid_no_instance/Main.snap b/tests-integration/fixtures/checking/111_int_add_invalid_no_instance/Main.snap index 0a4876b9..87a06f58 100644 --- a/tests-integration/fixtures/checking/111_int_add_invalid_no_instance/Main.snap +++ b/tests-integration/fixtures/checking/111_int_add_invalid_no_instance/Main.snap @@ -16,5 +16,8 @@ Proxy Kind = :1 +Roles +Proxy = [Phantom] + Errors NoInstanceFound { Add 2 3 10 } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/112_int_mul_invalid_no_instance/Main.snap b/tests-integration/fixtures/checking/112_int_mul_invalid_no_instance/Main.snap index d2eab345..266be6b4 100644 --- a/tests-integration/fixtures/checking/112_int_mul_invalid_no_instance/Main.snap +++ b/tests-integration/fixtures/checking/112_int_mul_invalid_no_instance/Main.snap @@ -16,5 +16,8 @@ Proxy Kind = :1 +Roles +Proxy = [Phantom] + Errors NoInstanceFound { Mul 2 3 10 } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/113_int_compare_invalid_no_instance/Main.snap b/tests-integration/fixtures/checking/113_int_compare_invalid_no_instance/Main.snap index 020827bc..54602c05 100644 --- a/tests-integration/fixtures/checking/113_int_compare_invalid_no_instance/Main.snap +++ b/tests-integration/fixtures/checking/113_int_compare_invalid_no_instance/Main.snap @@ -16,5 +16,8 @@ Proxy Kind = :1 +Roles +Proxy = [Phantom] + Errors NoInstanceFound { Compare 5 1 LT } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/114_int_tostring_invalid_no_instance/Main.snap b/tests-integration/fixtures/checking/114_int_tostring_invalid_no_instance/Main.snap index 74ca812d..4ecd5fd5 100644 --- a/tests-integration/fixtures/checking/114_int_tostring_invalid_no_instance/Main.snap +++ b/tests-integration/fixtures/checking/114_int_tostring_invalid_no_instance/Main.snap @@ -16,5 +16,8 @@ Proxy Kind = :1 +Roles +Proxy = [Phantom] + Errors NoInstanceFound { ToString 42 "999" } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/115_empty_do_block/Main.snap b/tests-integration/fixtures/checking/115_empty_do_block/Main.snap index ad90143f..5054f09d 100644 --- a/tests-integration/fixtures/checking/115_empty_do_block/Main.snap +++ b/tests-integration/fixtures/checking/115_empty_do_block/Main.snap @@ -11,6 +11,9 @@ test :: ??? Types Effect :: Type -> Type +Roles +Effect = [Nominal] + Errors EmptyDoBlock at [TermDeclaration(Idx::(3)), InferringExpression(AstId(56))] CannotUnify { Type, ??? } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/116_empty_ado_block/Main.snap b/tests-integration/fixtures/checking/116_empty_ado_block/Main.snap index 22abaf4a..cc52924a 100644 --- a/tests-integration/fixtures/checking/116_empty_ado_block/Main.snap +++ b/tests-integration/fixtures/checking/116_empty_ado_block/Main.snap @@ -12,6 +12,9 @@ test2 :: Effect Int Types Effect :: Type -> Type +Roles +Effect = [Nominal] + Errors EmptyAdoBlock at [TermDeclaration(Idx::(3)), InferringExpression(AstId(54))] CannotUnify { Type, ??? } at [TermDeclaration(Idx::(3))] diff --git a/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap b/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap index 91c98fc7..f3297232 100644 --- a/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap +++ b/tests-integration/fixtures/checking/117_do_ado_constrained/Main.snap @@ -31,6 +31,9 @@ Tuple Kind = :0 +Roles +Tuple = [Representational, Representational] + Classes class Functor (&0 :: Type -> Type) class Functor &0 <= Apply (&0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/121_instance_member_inner_forall/Main.snap b/tests-integration/fixtures/checking/121_instance_member_inner_forall/Main.snap index 7a59baa3..418554e0 100644 --- a/tests-integration/fixtures/checking/121_instance_member_inner_forall/Main.snap +++ b/tests-integration/fixtures/checking/121_instance_member_inner_forall/Main.snap @@ -16,6 +16,9 @@ Box Kind = :0 +Roles +Box = [Representational] + Classes class Functor (&0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/122_instance_member_inner_forall_constraint/Main.snap b/tests-integration/fixtures/checking/122_instance_member_inner_forall_constraint/Main.snap index bbeb151e..13cd0b89 100644 --- a/tests-integration/fixtures/checking/122_instance_member_inner_forall_constraint/Main.snap +++ b/tests-integration/fixtures/checking/122_instance_member_inner_forall_constraint/Main.snap @@ -26,6 +26,10 @@ Maybe Kind = :0 +Roles +Box = [Representational] +Maybe = [Representational] + Classes class Show (&0 :: Type) class Functor (&0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/124_instance_member_missing_constraint/Main.snap b/tests-integration/fixtures/checking/124_instance_member_missing_constraint/Main.snap index a4e79833..124224bd 100644 --- a/tests-integration/fixtures/checking/124_instance_member_missing_constraint/Main.snap +++ b/tests-integration/fixtures/checking/124_instance_member_missing_constraint/Main.snap @@ -18,6 +18,9 @@ Box Kind = :0 +Roles +Box = [Representational] + Classes class Show (&0 :: Type) class Functor (&0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/126_instance_phantom/Main.snap b/tests-integration/fixtures/checking/126_instance_phantom/Main.snap index 88810445..30689b33 100644 --- a/tests-integration/fixtures/checking/126_instance_phantom/Main.snap +++ b/tests-integration/fixtures/checking/126_instance_phantom/Main.snap @@ -17,6 +17,9 @@ Proxy Kind = :0 +Roles +Proxy = [Phantom] + Classes class Phantom (&0 :: Type) diff --git a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap index 4eeb2b60..01ef762c 100644 --- a/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap +++ b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap @@ -26,6 +26,11 @@ ContainsNoEq Kind = :0 +Roles +Proxy = [Phantom] +NoEq = [] +ContainsNoEq = [] + Derived derive forall (&0 :: Type). Eq (Proxy @&0 &1 :: Type) derive Eq (ContainsNoEq :: Type) diff --git a/tests-integration/fixtures/checking/128_type_operator_mutual/Main.snap b/tests-integration/fixtures/checking/128_type_operator_mutual/Main.snap index 9e0fd0c4..e69231b5 100644 --- a/tests-integration/fixtures/checking/128_type_operator_mutual/Main.snap +++ b/tests-integration/fixtures/checking/128_type_operator_mutual/Main.snap @@ -13,3 +13,7 @@ Data Add Quantified = :2 Kind = :0 + + +Roles +Add = [Representational, Representational] diff --git a/tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.snap b/tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.snap index b1f41303..f1148a15 100644 --- a/tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.snap +++ b/tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.snap @@ -20,6 +20,10 @@ Pair Kind = :0 +Roles +Box = [] +Pair = [] + Derived derive Eq (Box :: Type) derive Eq (Pair :: Type) diff --git a/tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.snap b/tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.snap index 0c0d4ef1..000c34be 100644 --- a/tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.snap +++ b/tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.snap @@ -15,5 +15,8 @@ Maybe Kind = :0 +Roles +Maybe = [Representational] + Derived derive Eq &0 => Eq (Maybe &0 :: Type) diff --git a/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap index a3861cb3..72d81bda 100644 --- a/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap +++ b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap @@ -20,6 +20,10 @@ Box Kind = :0 +Roles +NoEq = [] +Box = [] + Derived derive Eq (Box :: Type) diff --git a/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.snap b/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.snap index 9a45920c..fe7b919e 100644 --- a/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.snap @@ -20,6 +20,10 @@ WrapNoEq1 Kind = :0 +Roles +Wrap = [Representational, Nominal] +WrapNoEq1 = [Representational, Nominal] + Derived derive (Eq1 &0, Eq &1) => Eq (Wrap @Type &0 &1 :: Type) derive Eq &1 => Eq (WrapNoEq1 @Type &0 &1 :: Type) diff --git a/tests-integration/fixtures/checking/133_derive_eq_partial/Main.snap b/tests-integration/fixtures/checking/133_derive_eq_partial/Main.snap index aba4244e..65d7fda5 100644 --- a/tests-integration/fixtures/checking/133_derive_eq_partial/Main.snap +++ b/tests-integration/fixtures/checking/133_derive_eq_partial/Main.snap @@ -15,6 +15,9 @@ Either Kind = :0 +Roles +Either = [Representational, Representational] + Derived derive Eq &0 => Eq (Either Int &0 :: Type) derive Eq (Either Int &0 :: Type) diff --git a/tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap b/tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap index 2d58992c..ffce0587 100644 --- a/tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap +++ b/tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap @@ -32,6 +32,12 @@ ContainsNoOrd Kind = :0 +Roles +Box = [] +Pair = [] +NoOrd = [] +ContainsNoOrd = [] + Derived derive Eq (Box :: Type) derive Ord (Box :: Type) diff --git a/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap index 78625c0b..069f554b 100644 --- a/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap @@ -20,6 +20,10 @@ WrapNoOrd1 Kind = :0 +Roles +Wrap = [Representational, Nominal] +WrapNoOrd1 = [Representational, Nominal] + Derived derive (Eq1 &0, Eq &1) => Eq (Wrap @Type &0 &1 :: Type) derive (Ord1 &0, Ord &1) => Ord (Wrap @Type &0 &1 :: Type) diff --git a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap index 3840e078..3bffb8dd 100644 --- a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap @@ -33,6 +33,12 @@ V Kind = :0 +Roles +T = [Representational, Representational] +Maybe = [Representational] +U = [Representational] +V = [Representational, Representational] + Derived derive Eq1 &0 => Eq (V @Type &0 &1 :: Type) derive Ord1 &0 => Ord (V @Type &0 &1 :: Type) diff --git a/tests-integration/fixtures/checking/137_derive_newtype_simple/Main.snap b/tests-integration/fixtures/checking/137_derive_newtype_simple/Main.snap index e7946aa6..73271ea5 100644 --- a/tests-integration/fixtures/checking/137_derive_newtype_simple/Main.snap +++ b/tests-integration/fixtures/checking/137_derive_newtype_simple/Main.snap @@ -14,5 +14,8 @@ Wrapper Kind = :0 +Roles +Wrapper = [] + Derived derive Show (Wrapper :: Type) diff --git a/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Main.snap b/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Main.snap index 51cf2d11..0bf73312 100644 --- a/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Main.snap +++ b/tests-integration/fixtures/checking/138_derive_newtype_parameterized/Main.snap @@ -14,5 +14,8 @@ Identity Kind = :0 +Roles +Identity = [Representational] + Derived derive Show (Identity Int :: Type) diff --git a/tests-integration/fixtures/checking/139_derive_newtype_with_given/Main.snap b/tests-integration/fixtures/checking/139_derive_newtype_with_given/Main.snap index 9d9f2328..1af357a5 100644 --- a/tests-integration/fixtures/checking/139_derive_newtype_with_given/Main.snap +++ b/tests-integration/fixtures/checking/139_derive_newtype_with_given/Main.snap @@ -14,5 +14,8 @@ Identity Kind = :0 +Roles +Identity = [Representational] + Derived derive Show &0 => Show (Identity &0 :: Type) diff --git a/tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.snap b/tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.snap index 6db61fac..a43f2a32 100644 --- a/tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.snap +++ b/tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.snap @@ -14,5 +14,8 @@ Mu Kind = :0 +Roles +Mu = [Representational] + Derived derive Show (&0 (Mu &0)) => Show (Mu &0 :: Type) diff --git a/tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.snap b/tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.snap index 5cd575ae..71582a9d 100644 --- a/tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.snap +++ b/tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.snap @@ -14,5 +14,8 @@ Vector Kind = :0 +Roles +Vector = [Phantom, Representational] + Derived derive Show &1 => Show (Vector &0 &1 :: Type) diff --git a/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.snap b/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.snap index 5df74bcd..b57fee0c 100644 --- a/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.snap +++ b/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.snap @@ -14,5 +14,8 @@ Foo Kind = :0 +Roles +Foo = [] + Errors ExpectedNewtype { type_id: Id(9) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.snap b/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.snap index 7ebfb058..6fdb0231 100644 --- a/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.snap +++ b/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.snap @@ -14,6 +14,9 @@ Identity Kind = :0 +Roles +Identity = [Representational] + Derived derive Show (Identity String :: Type) diff --git a/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.snap b/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.snap index f39d47f2..1ce47204 100644 --- a/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.snap +++ b/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.snap @@ -14,6 +14,9 @@ Identity Kind = :0 +Roles +Identity = [Representational] + Derived derive Show (Identity &0 :: Type) diff --git a/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.snap b/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.snap index fddd6c2f..d0998052 100644 --- a/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.snap +++ b/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.snap @@ -14,5 +14,8 @@ Pair Kind = :0 +Roles +Pair = [Representational, Phantom] + Derived derive Show (Pair @Type Int String :: Type) diff --git a/tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap b/tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap index 16403041..4b99ee66 100644 --- a/tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap +++ b/tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap @@ -27,6 +27,11 @@ Maybe Kind = :0 +Roles +Identity = [Representational] +Const = [Representational, Phantom] +Maybe = [Representational] + Derived derive Functor (Identity :: Type -> Type) derive Functor (Const @Type &0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap b/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap index 9d6c4842..c511ba0a 100644 --- a/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap @@ -20,6 +20,10 @@ WrapNoFunctor Kind = :0 +Roles +Wrap = [Representational, Nominal] +WrapNoFunctor = [Representational, Nominal] + Derived derive Functor &0 => Functor (Wrap @Type &0 :: Type -> Type) derive Functor (WrapNoFunctor @Type &0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap b/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap index f8dad8ed..bda806e5 100644 --- a/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap +++ b/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap @@ -26,6 +26,11 @@ Cont Kind = :0 +Roles +Predicate = [Representational] +Reader = [Representational, Representational] +Cont = [Representational, Representational] + Derived derive Functor (Predicate :: Type -> Type) derive Functor (Reader &0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap b/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap index 2a082a21..b659adfc 100644 --- a/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap +++ b/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap @@ -28,6 +28,11 @@ Const2 Kind = :0 +Roles +Either = [Representational, Representational] +Pair = [Representational, Representational] +Const2 = [Representational, Phantom, Phantom] + Derived derive Bifunctor (Either :: Type -> Type -> Type) derive Bifunctor (Pair :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap index a89e53e8..2af3fdac 100644 --- a/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap @@ -25,6 +25,10 @@ WrapBothNoConstraint Kind = :0 +Roles +WrapBoth = [Representational, Representational, Nominal, Nominal] +WrapBothNoConstraint = [Representational, Representational, Nominal, Nominal] + Derived derive (Functor &0, Functor &1) => Bifunctor (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) derive Bifunctor (WrapBothNoConstraint @Type @Type &0 &1 :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap index cbaef58f..3c19e666 100644 --- a/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap +++ b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap @@ -16,6 +16,9 @@ WrapBoth Kind = :0 +Roles +WrapBoth = [Representational, Representational, Nominal, Nominal] + Derived derive Bifunctor (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.snap b/tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.snap index e0480070..dff8c108 100644 --- a/tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.snap +++ b/tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.snap @@ -26,6 +26,11 @@ Op Kind = :0 +Roles +Predicate = [Representational] +Comparison = [Representational] +Op = [Representational, Representational] + Derived derive Contravariant (Predicate :: Type -> Type) derive Contravariant (Comparison :: Type -> Type) diff --git a/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap b/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap index bf9a55ad..261f65b8 100644 --- a/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap +++ b/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap @@ -20,6 +20,10 @@ Producer Kind = :0 +Roles +Identity = [Representational] +Producer = [Representational] + Derived derive Contravariant (Identity :: Type -> Type) derive Contravariant (Producer :: Type -> Type) diff --git a/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap b/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap index 59aa56f9..bdf61386 100644 --- a/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap +++ b/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap @@ -27,6 +27,11 @@ Choice Kind = :0 +Roles +Fn = [Representational, Representational] +ConstR = [Representational, Representational, Phantom] +Choice = [Representational, Representational] + Derived derive Profunctor (Fn :: Type -> Type -> Type) derive Profunctor (ConstR @Type &0 :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap b/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap index 3b4ea232..f123fd92 100644 --- a/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap +++ b/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap @@ -20,6 +20,10 @@ WrongSecond Kind = :0 +Roles +WrongFirst = [Representational, Representational] +WrongSecond = [Representational, Representational] + Derived derive Profunctor (WrongFirst :: Type -> Type -> Type) derive Profunctor (WrongSecond :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap b/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap index 9d229420..65f6d595 100644 --- a/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap +++ b/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap @@ -14,6 +14,9 @@ Triple Kind = :0 +Roles +Triple = [Representational, Representational, Representational] + Derived derive Bifunctor (Triple Int String :: Type -> Type) diff --git a/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap b/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap index 28391ae8..eb192a78 100644 --- a/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap +++ b/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap @@ -20,6 +20,10 @@ Unit Kind = :0 +Roles +Pair = [Representational, Representational] +Unit = [] + Derived derive Functor (Pair Int String :: Type) derive Functor (Unit :: Type) diff --git a/tests-integration/fixtures/checking/158_derive_foldable_simple/Main.snap b/tests-integration/fixtures/checking/158_derive_foldable_simple/Main.snap index 0686cbb7..cdbb704d 100644 --- a/tests-integration/fixtures/checking/158_derive_foldable_simple/Main.snap +++ b/tests-integration/fixtures/checking/158_derive_foldable_simple/Main.snap @@ -27,6 +27,11 @@ Const Kind = :0 +Roles +Identity = [Representational] +Maybe = [Representational] +Const = [Representational, Phantom] + Derived derive Foldable (Identity :: Type -> Type) derive Foldable (Maybe :: Type -> Type) diff --git a/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.snap b/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.snap index 453fce55..282a1b5b 100644 --- a/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.snap @@ -20,6 +20,10 @@ WrapNoFoldable Kind = :0 +Roles +Wrap = [Representational, Nominal] +WrapNoFoldable = [Representational, Nominal] + Derived derive Foldable &0 => Foldable (Wrap @Type &0 :: Type -> Type) derive Foldable (WrapNoFoldable @Type &0 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.snap b/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.snap index fd2799de..ef2d2efb 100644 --- a/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.snap +++ b/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.snap @@ -28,6 +28,11 @@ Const2 Kind = :0 +Roles +Either = [Representational, Representational] +Pair = [Representational, Representational] +Const2 = [Representational, Phantom, Phantom] + Derived derive Bifoldable (Either :: Type -> Type -> Type) derive Bifoldable (Pair :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.snap b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.snap index 110ade98..d9b8fc33 100644 --- a/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.snap @@ -25,6 +25,10 @@ WrapBothNoConstraint Kind = :0 +Roles +WrapBoth = [Representational, Representational, Nominal, Nominal] +WrapBothNoConstraint = [Representational, Representational, Nominal, Nominal] + Derived derive (Foldable &0, Foldable &1) => Bifoldable (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) derive Bifoldable (WrapBothNoConstraint @Type @Type &0 &1 :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap b/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap index bfae14f5..eb4c896c 100644 --- a/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap +++ b/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap @@ -27,6 +27,11 @@ Const Kind = :0 +Roles +Identity = [Representational] +Maybe = [Representational] +Const = [Representational, Phantom] + Derived derive Functor (Identity :: Type -> Type) derive Foldable (Identity :: Type -> Type) diff --git a/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap b/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap index 7fc586bb..8493bfee 100644 --- a/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap @@ -16,6 +16,9 @@ Compose Kind = :0 +Roles +Compose = [Representational, Representational, Nominal] + Derived derive (Functor &0, Functor &1) => Functor (Compose @Type @Type &0 &1 :: Type -> Type) derive (Foldable &0, Foldable &1) => Foldable (Compose @Type @Type &0 &1 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.snap b/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.snap index e8f14c9e..922cf920 100644 --- a/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.snap +++ b/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.snap @@ -21,6 +21,10 @@ Pair Kind = :0 +Roles +Either = [Representational, Representational] +Pair = [Representational, Representational] + Derived derive Bifunctor (Either :: Type -> Type -> Type) derive Bifoldable (Either :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap b/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap index d01f925e..e5d4e8d8 100644 --- a/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap @@ -16,6 +16,9 @@ WrapBoth Kind = :0 +Roles +WrapBoth = [Representational, Representational, Nominal, Nominal] + Derived derive (Functor &0, Functor &1) => Bifunctor (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) derive (Foldable &0, Foldable &1) => Bifoldable (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) diff --git a/tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.snap b/tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.snap index 11ae2bb4..b734c3c8 100644 --- a/tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.snap +++ b/tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.snap @@ -16,6 +16,9 @@ Compose Kind = :0 +Roles +Compose = [Representational, Representational, Nominal] + Derived derive (Traversable &0, Traversable &1) => Traversable (Compose @Type @Type &0 &1 :: Type -> Type) diff --git a/tests-integration/fixtures/checking/167_derive_eq_1/Main.snap b/tests-integration/fixtures/checking/167_derive_eq_1/Main.snap index 51ecc4e4..4b36ecd6 100644 --- a/tests-integration/fixtures/checking/167_derive_eq_1/Main.snap +++ b/tests-integration/fixtures/checking/167_derive_eq_1/Main.snap @@ -59,6 +59,16 @@ NoEq Kind = :0 +Roles +Id = [Representational] +Pair = [Representational] +Mixed = [Representational] +Rec = [Representational] +Wrap = [Representational, Nominal] +Compose = [Representational, Representational, Nominal] +Either' = [Representational] +NoEq = [Representational] + Derived derive Eq1 &0 => Eq1 (Wrap @Type &0 :: Type -> Type) derive forall (&0 :: Type). (Eq1 &1, Eq (&2 &3)) => Eq (Compose @Type @&0 &1 &2 &3 :: Type) diff --git a/tests-integration/fixtures/checking/168_derive_ord_1/Main.snap b/tests-integration/fixtures/checking/168_derive_ord_1/Main.snap index 34cfb44f..5e0d4172 100644 --- a/tests-integration/fixtures/checking/168_derive_ord_1/Main.snap +++ b/tests-integration/fixtures/checking/168_derive_ord_1/Main.snap @@ -34,6 +34,12 @@ NoOrd Kind = :0 +Roles +Id = [Representational] +Wrap = [Representational, Nominal] +Compose = [Representational, Representational, Nominal] +NoOrd = [Representational] + Derived derive forall (&0 :: Type). (Eq1 &1, Eq (&2 &3)) => Eq (Compose @Type @&0 &1 &2 &3 :: Type) derive forall (&0 :: Type). (Ord1 &1, Ord (&2 &3)) => Ord (Compose @Type @&0 &1 &2 &3 :: Type) diff --git a/tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.snap b/tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.snap index dcdc175c..1ee274d7 100644 --- a/tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.snap +++ b/tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.snap @@ -14,5 +14,8 @@ UserId Kind = :0 +Roles +UserId = [] + Derived derive Newtype (UserId :: Type) (Int :: Type) diff --git a/tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.snap b/tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.snap index 18a91c09..d292637b 100644 --- a/tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.snap +++ b/tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.snap @@ -14,5 +14,8 @@ Wrapper Kind = :0 +Roles +Wrapper = [Representational] + Derived derive Newtype (Wrapper &0 :: Type) (&0 :: Type) diff --git a/tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.snap b/tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.snap index 6dd3d089..80d37ac7 100644 --- a/tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.snap +++ b/tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.snap @@ -14,5 +14,8 @@ NotANewtype Kind = :0 +Roles +NotANewtype = [] + Errors ExpectedNewtype { type_id: Id(9) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/172_derive_generic_simple/Main.snap b/tests-integration/fixtures/checking/172_derive_generic_simple/Main.snap index 74517fbe..57b576fa 100644 --- a/tests-integration/fixtures/checking/172_derive_generic_simple/Main.snap +++ b/tests-integration/fixtures/checking/172_derive_generic_simple/Main.snap @@ -67,6 +67,15 @@ Proxy Kind = :0 +Roles +Void = [] +MyUnit = [] +Identity = [Representational] +Either = [Representational, Representational] +Tuple = [Representational, Representational] +Wrapper = [Representational] +Proxy = [Phantom] + Derived derive Generic (Void :: Type) (NoConstructors :: Type) derive Generic (MyUnit :: Type) (Constructor "MyUnit" NoArguments :: Type) diff --git a/tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.snap b/tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.snap index 88bb1763..780692ee 100644 --- a/tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.snap +++ b/tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.snap @@ -24,6 +24,10 @@ Wrapper Kind = :0 +Roles +UserId = [] +Wrapper = [Representational] + Derived derive Newtype (UserId :: Type) (Int :: Type) derive Newtype (Wrapper &0 :: Type) (&0 :: Type) diff --git a/tests-integration/fixtures/checking/174_role_inference_phantom/Main.purs b/tests-integration/fixtures/checking/174_role_inference_phantom/Main.purs new file mode 100644 index 00000000..24cc27bb --- /dev/null +++ b/tests-integration/fixtures/checking/174_role_inference_phantom/Main.purs @@ -0,0 +1,3 @@ +module Main where + +data Proxy a = Proxy diff --git a/tests-integration/fixtures/checking/174_role_inference_phantom/Main.snap b/tests-integration/fixtures/checking/174_role_inference_phantom/Main.snap new file mode 100644 index 00000000..22188897 --- /dev/null +++ b/tests-integration/fixtures/checking/174_role_inference_phantom/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Proxy :: forall (t0 :: Type) (a :: t0). Proxy @t0 a + +Types +Proxy :: forall (t0 :: Type). t0 -> Type + +Data +Proxy + Quantified = :1 + Kind = :0 + + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking/175_role_inference_representational/Main.purs b/tests-integration/fixtures/checking/175_role_inference_representational/Main.purs new file mode 100644 index 00000000..e9dace92 --- /dev/null +++ b/tests-integration/fixtures/checking/175_role_inference_representational/Main.purs @@ -0,0 +1,3 @@ +module Main where + +data Maybe a = Nothing | Just a diff --git a/tests-integration/fixtures/checking/175_role_inference_representational/Main.snap b/tests-integration/fixtures/checking/175_role_inference_representational/Main.snap new file mode 100644 index 00000000..c5235b8c --- /dev/null +++ b/tests-integration/fixtures/checking/175_role_inference_representational/Main.snap @@ -0,0 +1,19 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe a +Just :: forall (a :: Type). a -> Maybe a + +Types +Maybe :: Type -> Type + +Data +Maybe + Quantified = :0 + Kind = :0 + + +Roles +Maybe = [Representational] diff --git a/tests-integration/fixtures/checking/176_role_inference_nominal_constraint/Main.purs b/tests-integration/fixtures/checking/176_role_inference_nominal_constraint/Main.purs new file mode 100644 index 00000000..d909d35b --- /dev/null +++ b/tests-integration/fixtures/checking/176_role_inference_nominal_constraint/Main.purs @@ -0,0 +1,6 @@ +module Main where + +class Show a where + show :: a -> String + +newtype Shown a = Shown ((Show a => a -> String) -> String) diff --git a/tests-integration/fixtures/checking/176_role_inference_nominal_constraint/Main.snap b/tests-integration/fixtures/checking/176_role_inference_nominal_constraint/Main.snap new file mode 100644 index 00000000..4589da61 --- /dev/null +++ b/tests-integration/fixtures/checking/176_role_inference_nominal_constraint/Main.snap @@ -0,0 +1,23 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +show :: forall (a :: Type). Show a => a -> String +Shown :: forall (a :: Type). ((Show a => a -> String) -> String) -> Shown a + +Types +Show :: Type -> Constraint +Shown :: Type -> Type + +Data +Shown + Quantified = :0 + Kind = :0 + + +Roles +Shown = [Nominal] + +Classes +class Show (&0 :: Type) diff --git a/tests-integration/fixtures/checking/177_role_inference_nominal_parametric/Main.purs b/tests-integration/fixtures/checking/177_role_inference_nominal_parametric/Main.purs new file mode 100644 index 00000000..2e491825 --- /dev/null +++ b/tests-integration/fixtures/checking/177_role_inference_nominal_parametric/Main.purs @@ -0,0 +1,3 @@ +module Main where + +data F f a = F (f a) diff --git a/tests-integration/fixtures/checking/177_role_inference_nominal_parametric/Main.snap b/tests-integration/fixtures/checking/177_role_inference_nominal_parametric/Main.snap new file mode 100644 index 00000000..85741f64 --- /dev/null +++ b/tests-integration/fixtures/checking/177_role_inference_nominal_parametric/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +F :: forall (t4 :: Type) (f :: t4 -> Type) (a :: t4). f a -> F @t4 f a + +Types +F :: forall (t4 :: Type). (t4 -> Type) -> t4 -> Type + +Data +F + Quantified = :1 + Kind = :0 + + +Roles +F = [Representational, Nominal] diff --git a/tests-integration/fixtures/checking/178_role_inference_nested/Main.purs b/tests-integration/fixtures/checking/178_role_inference_nested/Main.purs new file mode 100644 index 00000000..cc9ae276 --- /dev/null +++ b/tests-integration/fixtures/checking/178_role_inference_nested/Main.purs @@ -0,0 +1,5 @@ +module Main where + +data Maybe a = Nothing | Just a + +newtype First a = First (Maybe a) diff --git a/tests-integration/fixtures/checking/178_role_inference_nested/Main.snap b/tests-integration/fixtures/checking/178_role_inference_nested/Main.snap new file mode 100644 index 00000000..605c0358 --- /dev/null +++ b/tests-integration/fixtures/checking/178_role_inference_nested/Main.snap @@ -0,0 +1,26 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe a +Just :: forall (a :: Type). a -> Maybe a +First :: forall (a :: Type). Maybe a -> First a + +Types +Maybe :: Type -> Type +First :: Type -> Type + +Data +Maybe + Quantified = :0 + Kind = :0 + +First + Quantified = :0 + Kind = :0 + + +Roles +Maybe = [Representational] +First = [Representational] diff --git a/tests-integration/fixtures/checking/179_role_inference_recursive/Main.purs b/tests-integration/fixtures/checking/179_role_inference_recursive/Main.purs new file mode 100644 index 00000000..41ab2965 --- /dev/null +++ b/tests-integration/fixtures/checking/179_role_inference_recursive/Main.purs @@ -0,0 +1,3 @@ +module Main where + +data List a = Nil | Cons a (List a) diff --git a/tests-integration/fixtures/checking/179_role_inference_recursive/Main.snap b/tests-integration/fixtures/checking/179_role_inference_recursive/Main.snap new file mode 100644 index 00000000..4016bb24 --- /dev/null +++ b/tests-integration/fixtures/checking/179_role_inference_recursive/Main.snap @@ -0,0 +1,19 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Nil :: forall (a :: Type). List a +Cons :: forall (a :: Type). a -> List a -> List a + +Types +List :: Type -> Type + +Data +List + Quantified = :0 + Kind = :0 + + +Roles +List = [Representational] diff --git a/tests-integration/fixtures/checking/180_role_declaration_strengthen/Main.purs b/tests-integration/fixtures/checking/180_role_declaration_strengthen/Main.purs new file mode 100644 index 00000000..500516d1 --- /dev/null +++ b/tests-integration/fixtures/checking/180_role_declaration_strengthen/Main.purs @@ -0,0 +1,5 @@ +module Main where + +data Maybe a = Nothing | Just a + +type role Maybe nominal diff --git a/tests-integration/fixtures/checking/180_role_declaration_strengthen/Main.snap b/tests-integration/fixtures/checking/180_role_declaration_strengthen/Main.snap new file mode 100644 index 00000000..d9555a9e --- /dev/null +++ b/tests-integration/fixtures/checking/180_role_declaration_strengthen/Main.snap @@ -0,0 +1,19 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe a +Just :: forall (a :: Type). a -> Maybe a + +Types +Maybe :: Type -> Type + +Data +Maybe + Quantified = :0 + Kind = :0 + + +Roles +Maybe = [Nominal] diff --git a/tests-integration/fixtures/checking/181_role_declaration_loosen_error/Main.purs b/tests-integration/fixtures/checking/181_role_declaration_loosen_error/Main.purs new file mode 100644 index 00000000..ad53bad9 --- /dev/null +++ b/tests-integration/fixtures/checking/181_role_declaration_loosen_error/Main.purs @@ -0,0 +1,5 @@ +module Main where + +data F f a = F (f a) + +type role F representational phantom diff --git a/tests-integration/fixtures/checking/181_role_declaration_loosen_error/Main.snap b/tests-integration/fixtures/checking/181_role_declaration_loosen_error/Main.snap new file mode 100644 index 00000000..6c7f82d0 --- /dev/null +++ b/tests-integration/fixtures/checking/181_role_declaration_loosen_error/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +F :: forall (t4 :: Type) (f :: t4 -> Type) (a :: t4). f a -> F @t4 f a + +Types +F :: forall (t4 :: Type). (t4 -> Type) -> t4 -> Type + +Data +F + Quantified = :1 + Kind = :0 + + +Roles +F = [Representational, Nominal] + +Errors +InvalidRoleDeclaration { type_id: Idx::(0), parameter_index: 1, declared: Phantom, inferred: Nominal } at [] diff --git a/tests-integration/fixtures/checking/182_role_declaration_foreign/Main.purs b/tests-integration/fixtures/checking/182_role_declaration_foreign/Main.purs new file mode 100644 index 00000000..6c59361e --- /dev/null +++ b/tests-integration/fixtures/checking/182_role_declaration_foreign/Main.purs @@ -0,0 +1,5 @@ +module Main where + +foreign import data Effect :: Type -> Type + +type role Effect representational diff --git a/tests-integration/fixtures/checking/182_role_declaration_foreign/Main.snap b/tests-integration/fixtures/checking/182_role_declaration_foreign/Main.snap new file mode 100644 index 00000000..e01fb98f --- /dev/null +++ b/tests-integration/fixtures/checking/182_role_declaration_foreign/Main.snap @@ -0,0 +1,11 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms + +Types +Effect :: Type -> Type + +Roles +Effect = [Representational] diff --git a/tests-integration/src/generated/basic.rs b/tests-integration/src/generated/basic.rs index c9511aa4..b95c5028 100644 --- a/tests-integration/src/generated/basic.rs +++ b/tests-integration/src/generated/basic.rs @@ -291,7 +291,9 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { writeln!(snapshot, "\nData").unwrap(); } for (id, TypeItem { name, kind, .. }) in indexed.items.iter_types() { - let (TypeItemKind::Data { .. } | TypeItemKind::Newtype { .. }) = kind else { continue }; + let (TypeItemKind::Data { .. } | TypeItemKind::Newtype { .. }) = kind else { + continue; + }; let Some(name) = name else { continue }; let Some(data) = checked.lookup_data(id) else { continue }; writeln!(snapshot, "{name}").unwrap(); @@ -300,6 +302,21 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { writeln!(snapshot).unwrap(); } + if !checked.roles.is_empty() { + writeln!(snapshot, "\nRoles").unwrap(); + } + for (id, TypeItem { name, kind, .. }) in indexed.items.iter_types() { + let (TypeItemKind::Data { .. } | TypeItemKind::Newtype { .. } | TypeItemKind::Foreign { .. }) = + kind + else { + continue; + }; + let Some(name) = name else { continue }; + let Some(roles) = checked.lookup_roles(id) else { continue }; + let roles_str: Vec<_> = roles.iter().map(|r| format!("{r:?}")).collect(); + writeln!(snapshot, "{name} = [{}]", roles_str.join(", ")).unwrap(); + } + if !checked.classes.is_empty() { writeln!(snapshot, "\nClasses").unwrap(); } diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 60febb2e..a7de8463 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -357,3 +357,21 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_172_derive_generic_simple_main() { run_test("172_derive_generic_simple", "Main"); } #[rustfmt::skip] #[test] fn test_173_derive_newtype_class_coercible_main() { run_test("173_derive_newtype_class_coercible", "Main"); } + +#[rustfmt::skip] #[test] fn test_174_role_inference_phantom_main() { run_test("174_role_inference_phantom", "Main"); } + +#[rustfmt::skip] #[test] fn test_175_role_inference_representational_main() { run_test("175_role_inference_representational", "Main"); } + +#[rustfmt::skip] #[test] fn test_176_role_inference_nominal_constraint_main() { run_test("176_role_inference_nominal_constraint", "Main"); } + +#[rustfmt::skip] #[test] fn test_177_role_inference_nominal_parametric_main() { run_test("177_role_inference_nominal_parametric", "Main"); } + +#[rustfmt::skip] #[test] fn test_178_role_inference_nested_main() { run_test("178_role_inference_nested", "Main"); } + +#[rustfmt::skip] #[test] fn test_179_role_inference_recursive_main() { run_test("179_role_inference_recursive", "Main"); } + +#[rustfmt::skip] #[test] fn test_180_role_declaration_strengthen_main() { run_test("180_role_declaration_strengthen", "Main"); } + +#[rustfmt::skip] #[test] fn test_181_role_declaration_loosen_error_main() { run_test("181_role_declaration_loosen_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_182_role_declaration_foreign_main() { run_test("182_role_declaration_foreign", "Main"); } From 96d6906289835496fdeb4a7bd5a9d6127bff846c Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 02:34:41 +0800 Subject: [PATCH 43/62] Insert roles in prim_checked --- compiler-core/checking/src/algorithm.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/compiler-core/checking/src/algorithm.rs b/compiler-core/checking/src/algorithm.rs index 25703b23..c23f6330 100644 --- a/compiler-core/checking/src/algorithm.rs +++ b/compiler-core/checking/src/algorithm.rs @@ -58,6 +58,7 @@ pub mod type_item; pub mod unification; use std::slice; +use std::sync::Arc; use building_types::QueryResult; use files::FileId; @@ -65,7 +66,7 @@ use indexing::TermItemKind; use itertools::Itertools; use lowering::{Scc, TermItemIr}; -use crate::core::{Type, TypeId}; +use crate::core::{Role, Type, TypeId}; use crate::{CheckedModule, ExternalQueries}; pub fn check_source(queries: &impl ExternalQueries, file_id: FileId) -> QueryResult { @@ -432,5 +433,23 @@ pub fn check_prim(queries: &impl ExternalQueries, file_id: FileId) -> QueryResul insert_type("Symbol", type_core); insert_type("Row", type_to_type_core); + let mut insert_roles = |name: &str, roles: &[Role]| { + let (_, item_id) = lookup_type(name); + checked_module.roles.insert(item_id, Arc::from(roles)); + }; + + insert_roles("Type", &[]); + insert_roles("Function", &[Role::Representational, Role::Representational]); + insert_roles("Array", &[Role::Representational]); + insert_roles("Record", &[Role::Representational]); + insert_roles("Number", &[]); + insert_roles("Int", &[]); + insert_roles("String", &[]); + insert_roles("Char", &[]); + insert_roles("Boolean", &[]); + insert_roles("Constraint", &[]); + insert_roles("Symbol", &[]); + insert_roles("Row", &[Role::Representational]); + Ok(checked_module) } From e03722a76523da1716105f6f8a3be0621c4ea9b3 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 02:34:59 +0800 Subject: [PATCH 44/62] Add resolved and prim_resolved to CheckContext --- compiler-core/checking/src/algorithm/state.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index acdd9e2c..c7c89756 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -345,8 +345,10 @@ where pub grouped: Arc, pub bracketed: Arc, pub sectioned: Arc, + pub resolved: Arc, pub prim_indexed: Arc, + pub prim_resolved: Arc, } impl<'a, Q> CheckContext<'a, Q> @@ -372,8 +374,10 @@ where let prim_coerce = PrimCoerceCore::collect(queries)?; let known_types = KnownTypesCore::collect(queries)?; let known_generic = KnownGeneric::collect(queries, &mut state.storage)?; + let resolved = queries.resolved(id)?; let prim_id = queries.prim_id(); let prim_indexed = queries.indexed(prim_id)?; + let prim_resolved = queries.resolved(prim_id)?; Ok(CheckContext { queries, prim, @@ -391,7 +395,9 @@ where grouped, bracketed, sectioned, + resolved, prim_indexed, + prim_resolved, }) } } From f93c81022ec9481514a3b7516abbce44ee3de239 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 03:06:51 +0800 Subject: [PATCH 45/62] Implement is_term_in_scope --- compiler-core/resolving/src/lib.rs | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/compiler-core/resolving/src/lib.rs b/compiler-core/resolving/src/lib.rs index 306f9a6e..0fc900c1 100644 --- a/compiler-core/resolving/src/lib.rs +++ b/compiler-core/resolving/src/lib.rs @@ -141,6 +141,37 @@ impl ResolvedModule { ) -> Option<(FileId, TermItemId)> { self.class.lookup(class_id, name) } + + pub fn is_term_in_scope( + &self, + prim: &ResolvedModule, + file_id: FileId, + item_id: TermItemId, + ) -> bool { + if self.locals.contains_term(file_id, item_id) { + return true; + } + + for imports in self.unqualified.values() { + for import in imports { + if import.contains_term(file_id, item_id) { + return true; + } + } + } + + for import in self.qualified.values() { + if import.contains_term(file_id, item_id) { + return true; + } + } + + if prim.exports.contains_term(file_id, item_id) { + return true; + } + + false + } } type ResolvedImportsUnqualified = FxHashMap>; @@ -161,6 +192,10 @@ impl ResolvedLocals { self.types.get(name).copied() } + pub fn contains_term(&self, file: FileId, term: TermItemId) -> bool { + self.terms.values().any(|&(f, t)| f == file && t == term) + } + pub fn iter_terms(&self) -> impl Iterator { self.terms.iter().map(|(k, (f, i))| (k, *f, *i)) } @@ -191,6 +226,10 @@ impl ResolvedExports { self.types.get(name).copied().map(|(f, i, _)| (f, i)) } + pub fn contains_term(&self, file: FileId, term: TermItemId) -> bool { + self.terms.values().any(|&(f, t, _)| f == file && t == term) + } + pub fn iter_terms(&self) -> impl Iterator { self.terms.iter().map(|(k, (f, i, _))| (k, *f, *i)) } @@ -225,6 +264,12 @@ impl ResolvedImport { self.types.get(name).copied() } + pub fn contains_term(&self, file: FileId, term: TermItemId) -> bool { + self.terms + .values() + .any(|&(f, t, kind)| f == file && t == term && !matches!(kind, ImportKind::Hidden)) + } + pub fn iter_terms(&self) -> impl Iterator { self.terms.iter().map(|(k, (f, i, d))| (k, *f, *i, *d)) } From 93683726ef40bed44181886fe5e344821cbeb673 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 03:07:03 +0800 Subject: [PATCH 46/62] Use filter for snapshots --- compiler-scripts/src/bin/test-checking.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler-scripts/src/bin/test-checking.rs b/compiler-scripts/src/bin/test-checking.rs index 27fcfe94..818b8c8e 100644 --- a/compiler-scripts/src/bin/test-checking.rs +++ b/compiler-scripts/src/bin/test-checking.rs @@ -122,6 +122,11 @@ fn main() { Err(_) => continue, }; + // Skip snapshots that don't match any filter + if !filters.is_empty() && !filters.iter().any(|f| snap_path.contains(f)) { + continue; + } + let short_path = snap_path .strip_prefix(cwd.to_str().unwrap_or("")) .unwrap_or(&snap_path) From a9fda050236791a14befc2a7b15b6038c60e7bea Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 03:18:33 +0800 Subject: [PATCH 47/62] Initial constraint solver implementation --- .../checking/src/algorithm/constraint.rs | 36 +- .../algorithm/constraint/compiler_solved.rs | 322 +++++++++++++++++- .../checking/src/algorithm/derive.rs | 2 +- compiler-core/checking/src/error.rs | 4 + .../183_coercible_reflexivity/Main.purs | 12 + .../183_coercible_reflexivity/Main.snap | 10 + .../184_coercible_newtype_wrap/Main.purs | 19 ++ .../184_coercible_newtype_wrap/Main.snap | 29 ++ .../checking/185_coercible_phantom/Main.purs | 11 + .../checking/185_coercible_phantom/Main.snap | 20 ++ .../186_coercible_representational/Main.purs | 18 + .../186_coercible_representational/Main.snap | 36 ++ .../checking/187_coercible_array/Main.purs | 11 + .../checking/187_coercible_array/Main.snap | 20 ++ .../checking/188_coercible_record/Main.purs | 16 + .../checking/188_coercible_record/Main.snap | 28 ++ .../Main.purs | 9 + .../Main.snap | 31 ++ .../checking/190_coercible_nominal/Main.purs | 13 + .../checking/190_coercible_nominal/Main.snap | 16 + .../191_coercible_newtype_hidden/Lib.purs | 3 + .../191_coercible_newtype_hidden/Main.purs | 11 + .../191_coercible_newtype_hidden/Main.snap | 15 + .../192_coercible_newtype_qualified/Lib.purs | 3 + .../192_coercible_newtype_qualified/Main.purs | 10 + .../192_coercible_newtype_qualified/Main.snap | 9 + .../Lib.purs | 3 + .../Main.purs | 7 + .../Main.snap | 12 + .../194_coercible_transitivity/Main.purs | 18 + .../194_coercible_transitivity/Main.snap | 29 ++ .../195_coercible_nested_records/Main.purs | 30 ++ .../195_coercible_nested_records/Main.snap | 59 ++++ tests-integration/tests/checking/generated.rs | 26 ++ 34 files changed, 867 insertions(+), 31 deletions(-) create mode 100644 tests-integration/fixtures/checking/183_coercible_reflexivity/Main.purs create mode 100644 tests-integration/fixtures/checking/183_coercible_reflexivity/Main.snap create mode 100644 tests-integration/fixtures/checking/184_coercible_newtype_wrap/Main.purs create mode 100644 tests-integration/fixtures/checking/184_coercible_newtype_wrap/Main.snap create mode 100644 tests-integration/fixtures/checking/185_coercible_phantom/Main.purs create mode 100644 tests-integration/fixtures/checking/185_coercible_phantom/Main.snap create mode 100644 tests-integration/fixtures/checking/186_coercible_representational/Main.purs create mode 100644 tests-integration/fixtures/checking/186_coercible_representational/Main.snap create mode 100644 tests-integration/fixtures/checking/187_coercible_array/Main.purs create mode 100644 tests-integration/fixtures/checking/187_coercible_array/Main.snap create mode 100644 tests-integration/fixtures/checking/188_coercible_record/Main.purs create mode 100644 tests-integration/fixtures/checking/188_coercible_record/Main.snap create mode 100644 tests-integration/fixtures/checking/189_coercible_different_heads_error/Main.purs create mode 100644 tests-integration/fixtures/checking/189_coercible_different_heads_error/Main.snap create mode 100644 tests-integration/fixtures/checking/190_coercible_nominal/Main.purs create mode 100644 tests-integration/fixtures/checking/190_coercible_nominal/Main.snap create mode 100644 tests-integration/fixtures/checking/191_coercible_newtype_hidden/Lib.purs create mode 100644 tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.purs create mode 100644 tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.snap create mode 100644 tests-integration/fixtures/checking/192_coercible_newtype_qualified/Lib.purs create mode 100644 tests-integration/fixtures/checking/192_coercible_newtype_qualified/Main.purs create mode 100644 tests-integration/fixtures/checking/192_coercible_newtype_qualified/Main.snap create mode 100644 tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Lib.purs create mode 100644 tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.purs create mode 100644 tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.snap create mode 100644 tests-integration/fixtures/checking/194_coercible_transitivity/Main.purs create mode 100644 tests-integration/fixtures/checking/194_coercible_transitivity/Main.snap create mode 100644 tests-integration/fixtures/checking/195_coercible_nested_records/Main.purs create mode 100644 tests-integration/fixtures/checking/195_coercible_nested_records/Main.snap diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index 1261c8b7..4410e2fd 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -59,23 +59,21 @@ where Some(MatchInstance::Apart | MatchInstance::Stuck) | None => (), } - if let Some(result) = match_compiler_instances(state, context, &application) { - match result { - MatchInstance::Match { constraints, equalities } => { - for (t1, t2) in equalities { - if unification::unify(state, context, t1, t2)? { - made_progress = true; - } + match match_compiler_instances(state, context, &application)? { + Some(MatchInstance::Match { constraints, equalities }) => { + for (t1, t2) in equalities { + if unification::unify(state, context, t1, t2)? { + made_progress = true; } - work_queue.extend(constraints); - continue 'work; - } - MatchInstance::Apart => (), - MatchInstance::Stuck => { - residual.push(wanted); - continue 'work; } + work_queue.extend(constraints); + continue 'work; } + Some(MatchInstance::Stuck) => { + residual.push(wanted); + continue 'work; + } + Some(MatchInstance::Apart) | None => (), } let instance_chains = collect_instance_chains(state, context, &application)?; @@ -818,13 +816,13 @@ fn match_compiler_instances( state: &mut CheckState, context: &CheckContext, wanted: &ConstraintApplication, -) -> Option +) -> QueryResult> where Q: ExternalQueries, { let ConstraintApplication { file_id, item_id, ref arguments } = *wanted; - if file_id == context.prim_int.file_id { + let match_instance = if file_id == context.prim_int.file_id { if item_id == context.prim_int.add { prim_int_add(state, arguments) } else if item_id == context.prim_int.mul { @@ -866,13 +864,15 @@ where } } else if file_id == context.prim_coerce.file_id { if item_id == context.prim_coerce.coercible { - prim_coercible(state, arguments) + return prim_coercible(state, context, arguments); } else { None } } else { None - } + }; + + Ok(match_instance) } struct ApplyBindings<'a> { diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs index b9748320..0ab0e003 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs @@ -1,24 +1,31 @@ use std::cmp::Ordering; +use std::sync::Arc; -use itertools::EitherOrBoth; +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; +use itertools::{EitherOrBoth, izip}; use lowering::StringKind; use rustc_hash::FxHashSet; use smol_str::SmolStr; use super::MatchInstance; +use crate::algorithm::constraint; +use crate::algorithm::derive; use crate::algorithm::kind; +use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::core::{RowField, RowType}; +use crate::core::{Role, RowField, RowType}; use crate::{ExternalQueries, Type, TypeId}; fn extract_integer(state: &CheckState, id: TypeId) -> Option { - let Type::Integer(n) = state.storage[id] else { return None }; - Some(n) + let Type::Integer(value) = state.storage[id] else { return None }; + Some(value) } fn extract_symbol(state: &CheckState, id: TypeId) -> Option { - let Type::String(_, s) = &state.storage[id] else { return None }; - Some(s.clone()) + let Type::String(_, value) = &state.storage[id] else { return None }; + Some(SmolStr::clone(value)) } pub fn prim_int_add(state: &mut CheckState, arguments: &[TypeId]) -> Option { @@ -576,19 +583,310 @@ where Some(MatchInstance::Match { constraints: vec![], equalities: vec![(list, result)] }) } -pub fn prim_coercible(state: &mut CheckState, arguments: &[TypeId]) -> Option { +enum NewtypeCoercionResult { + Success(MatchInstance), + ConstructorNotInScope { file_id: FileId, item_id: TypeItemId }, + NotApplicable, +} + +pub fn prim_coercible( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ let &[left, right] = arguments else { - return None; + return Ok(None); }; let left = state.normalize_type(left); let right = state.normalize_type(right); - // Reflexivity if left == right { - return Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }); + return Ok(Some(MatchInstance::Match { constraints: vec![], equalities: vec![] })); + } + + if is_unification_head(state, left) || is_unification_head(state, right) { + return Ok(Some(MatchInstance::Stuck)); + } + + let newtype_result = try_newtype_coercion(state, context, left, right)?; + if let NewtypeCoercionResult::Success(result) = newtype_result { + return Ok(Some(result)); + } + + if let Some(result) = try_application_coercion(state, context, left, right)? { + return Ok(Some(result)); + } + + if let Some(result) = try_row_coercion(state, context, left, right) { + return Ok(Some(result)); + } + + if let NewtypeCoercionResult::ConstructorNotInScope { file_id, item_id } = newtype_result { + state.insert_error(crate::error::ErrorKind::CoercibleConstructorNotInScope { + file_id, + item_id, + }); + } + + Ok(Some(MatchInstance::Apart)) +} + +fn is_unification_head(state: &mut CheckState, mut type_id: TypeId) -> bool { + loop { + type_id = state.normalize_type(type_id); + match state.storage[type_id] { + Type::Unification(_) => return true, + Type::Application(function, _) | Type::KindApplication(function, _) => { + type_id = function; + } + _ => return false, + } + } +} + +fn try_newtype_coercion( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut hidden_newtype: Option<(FileId, TypeItemId)> = None; + + if let Some((file_id, type_id)) = derive::extract_type_constructor(state, left) { + if is_newtype(context, file_id, type_id)? { + if is_constructor_in_scope(context, file_id, type_id)? { + let inner = derive::get_newtype_inner(state, context, file_id, type_id, left)?; + let constraint = make_coercible_constraint(state, context, inner, right); + return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { + constraints: vec![constraint], + equalities: vec![], + })); + } else { + hidden_newtype = Some((file_id, type_id)); + } + } + } + + if let Some((file_id, type_id)) = derive::extract_type_constructor(state, right) { + if is_newtype(context, file_id, type_id)? { + if is_constructor_in_scope(context, file_id, type_id)? { + let inner = derive::get_newtype_inner(state, context, file_id, type_id, right)?; + let constraint = make_coercible_constraint(state, context, left, inner); + return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { + constraints: vec![constraint], + equalities: vec![], + })); + } else if hidden_newtype.is_none() { + hidden_newtype = Some((file_id, type_id)); + } + } + } + + if let Some((file_id, item_id)) = hidden_newtype { + return Ok(NewtypeCoercionResult::ConstructorNotInScope { file_id, item_id }); + } + + Ok(NewtypeCoercionResult::NotApplicable) +} + +fn is_newtype( + context: &CheckContext, + file_id: FileId, + type_id: TypeItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let is_newtype = if file_id == context.id { + matches!(context.indexed.items[type_id].kind, indexing::TypeItemKind::Newtype { .. }) + } else { + let indexed = context.queries.indexed(file_id)?; + matches!(indexed.items[type_id].kind, indexing::TypeItemKind::Newtype { .. }) + }; + Ok(is_newtype) +} + +fn is_constructor_in_scope( + context: &CheckContext, + file_id: FileId, + item_id: TypeItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let constructor_term_id = if file_id == context.id { + context.indexed.pairs.data_constructors(item_id).next() + } else { + let indexed = context.queries.indexed(file_id)?; + indexed.pairs.data_constructors(item_id).next() + }; + + let Some(constructor_term_id) = constructor_term_id else { + return Ok(false); + }; + + Ok(context.resolved.is_term_in_scope(&context.prim_resolved, file_id, constructor_term_id)) +} + +fn try_application_coercion( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let Some((left_file, left_id)) = derive::extract_type_constructor(state, left) else { + return Ok(None); + }; + let Some((right_file, right_id)) = derive::extract_type_constructor(state, right) else { + return Ok(None); + }; + + if left_file != right_file || left_id != right_id { + return Ok(None); + } + + let left = extract_type_arguments(state, left); + let right = extract_type_arguments(state, right); + + if left.len() != right.len() { + return Ok(Some(MatchInstance::Apart)); + } + + let Some(roles) = lookup_roles_for_type(state, context, left_file, left_id)? else { + return Ok(Some(MatchInstance::Stuck)); + }; + + debug_assert_eq!(roles.len(), left.len(), "critical failure: mismatched lengths"); + debug_assert_eq!(roles.len(), right.len(), "critical failure: mismatched lengths"); + + let mut constraints = vec![]; + let mut equalities = vec![]; + + for (role, &left, &right) in izip!(&*roles, &left, &right) { + match role { + Role::Phantom => (), + Role::Representational => { + let constraint = make_coercible_constraint(state, context, left, right); + constraints.push(constraint); + } + Role::Nominal => { + if left != right { + if constraint::can_unify(state, left, right).is_apart() { + return Ok(Some(MatchInstance::Apart)); + } + equalities.push((left, right)); + } + } + } } - // TODO: placeholder used for `derive instance Newtype` - Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) + Ok(Some(MatchInstance::Match { constraints, equalities })) +} + +fn lookup_roles_for_type( + state: &CheckState, + context: &CheckContext, + file_id: FileId, + type_id: TypeItemId, +) -> QueryResult>> +where + Q: ExternalQueries, +{ + if file_id == context.id { + Ok(state.checked.lookup_roles(type_id)) + } else { + let checked = context.queries.checked(file_id)?; + Ok(checked.lookup_roles(type_id)) + } +} + +fn extract_type_arguments(state: &mut CheckState, type_id: TypeId) -> Vec { + let mut arguments = vec![]; + let mut current_id = type_id; + + safe_loop! { + current_id = state.normalize_type(current_id); + match state.storage[current_id] { + Type::Application(function, argument) => { + arguments.push(argument); + current_id = function; + } + Type::KindApplication(function, _) => { + current_id = function; + } + _ => break, + } + } + + arguments.reverse(); + arguments +} + +fn try_row_coercion( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> Option +where + Q: ExternalQueries, +{ + let Type::Row(left_row) = &state.storage[left] else { return None }; + let Type::Row(right_row) = &state.storage[right] else { return None }; + + let left_row = left_row.clone(); + let right_row = right_row.clone(); + + if left_row.fields.len() != right_row.fields.len() { + return Some(MatchInstance::Apart); + } + + let mut constraints = vec![]; + + for (left_field, right_field) in izip!(&*left_row.fields, &*right_row.fields) { + if left_field.label != right_field.label { + return Some(MatchInstance::Apart); + } + let constraint = make_coercible_constraint(state, context, left_field.id, right_field.id); + constraints.push(constraint); + } + + match (left_row.tail, right_row.tail) { + (None, None) => (), + (Some(left_tail), Some(right_tail)) => { + let constraint = make_coercible_constraint(state, context, left_tail, right_tail); + constraints.push(constraint); + } + (None, Some(_)) | (Some(_), None) => { + return Some(MatchInstance::Apart); + } + } + + Some(MatchInstance::Match { constraints, equalities: vec![] }) +} + +fn make_coercible_constraint( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> TypeId +where + Q: ExternalQueries, +{ + let coerce = &context.prim_coerce; + let coercible = state.storage.intern(Type::Constructor(coerce.file_id, coerce.coercible)); + + let coercible = state.storage.intern(Type::Application(coercible, left)); + state.storage.intern(Type::Application(coercible, right)) } diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index eccbe674..d9c51d59 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -306,7 +306,7 @@ where /// /// Newtypes have exactly one constructor with exactly one field. /// This function extracts that field type, substituting any type parameters. -fn get_newtype_inner( +pub fn get_newtype_inner( state: &mut CheckState, context: &CheckContext, newtype_file: FileId, diff --git a/compiler-core/checking/src/error.rs b/compiler-core/checking/src/error.rs index 4f914bbd..44154f2d 100644 --- a/compiler-core/checking/src/error.rs +++ b/compiler-core/checking/src/error.rs @@ -93,6 +93,10 @@ pub enum ErrorKind { declared: crate::core::Role, inferred: crate::core::Role, }, + CoercibleConstructorNotInScope { + file_id: files::FileId, + item_id: indexing::TypeItemId, + }, } #[derive(Debug, PartialEq, Eq)] diff --git a/tests-integration/fixtures/checking/183_coercible_reflexivity/Main.purs b/tests-integration/fixtures/checking/183_coercible_reflexivity/Main.purs new file mode 100644 index 00000000..ff1b6009 --- /dev/null +++ b/tests-integration/fixtures/checking/183_coercible_reflexivity/Main.purs @@ -0,0 +1,12 @@ +module Main where + +import Safe.Coerce (coerce) + +testInt :: Int -> Int +testInt = coerce + +testString :: String -> String +testString = coerce + +testPoly :: forall a. a -> a +testPoly = coerce diff --git a/tests-integration/fixtures/checking/183_coercible_reflexivity/Main.snap b/tests-integration/fixtures/checking/183_coercible_reflexivity/Main.snap new file mode 100644 index 00000000..9e615ff2 --- /dev/null +++ b/tests-integration/fixtures/checking/183_coercible_reflexivity/Main.snap @@ -0,0 +1,10 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +testInt :: Int -> Int +testString :: String -> String +testPoly :: forall (a :: Type). a -> a + +Types diff --git a/tests-integration/fixtures/checking/184_coercible_newtype_wrap/Main.purs b/tests-integration/fixtures/checking/184_coercible_newtype_wrap/Main.purs new file mode 100644 index 00000000..875e3ce9 --- /dev/null +++ b/tests-integration/fixtures/checking/184_coercible_newtype_wrap/Main.purs @@ -0,0 +1,19 @@ +module Main where + +import Safe.Coerce (coerce) + +newtype Age = Age Int + +wrapAge :: Int -> Age +wrapAge = coerce + +unwrapAge :: Age -> Int +unwrapAge = coerce + +newtype Wrapper a = Wrapper a + +wrapValue :: forall a. a -> Wrapper a +wrapValue = coerce + +unwrapValue :: forall a. Wrapper a -> a +unwrapValue = coerce diff --git a/tests-integration/fixtures/checking/184_coercible_newtype_wrap/Main.snap b/tests-integration/fixtures/checking/184_coercible_newtype_wrap/Main.snap new file mode 100644 index 00000000..7bd9d00f --- /dev/null +++ b/tests-integration/fixtures/checking/184_coercible_newtype_wrap/Main.snap @@ -0,0 +1,29 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Age :: Int -> Age +wrapAge :: Int -> Age +unwrapAge :: Age -> Int +Wrapper :: forall (a :: Type). a -> Wrapper a +wrapValue :: forall (a :: Type). a -> Wrapper a +unwrapValue :: forall (a :: Type). Wrapper a -> a + +Types +Age :: Type +Wrapper :: Type -> Type + +Data +Age + Quantified = :0 + Kind = :0 + +Wrapper + Quantified = :0 + Kind = :0 + + +Roles +Age = [] +Wrapper = [Representational] diff --git a/tests-integration/fixtures/checking/185_coercible_phantom/Main.purs b/tests-integration/fixtures/checking/185_coercible_phantom/Main.purs new file mode 100644 index 00000000..a0ac3b63 --- /dev/null +++ b/tests-integration/fixtures/checking/185_coercible_phantom/Main.purs @@ -0,0 +1,11 @@ +module Main where + +import Safe.Coerce (coerce) + +data Proxy a = Proxy + +coerceProxy :: forall a b. Proxy a -> Proxy b +coerceProxy = coerce + +coerceProxyIntString :: Proxy Int -> Proxy String +coerceProxyIntString = coerce diff --git a/tests-integration/fixtures/checking/185_coercible_phantom/Main.snap b/tests-integration/fixtures/checking/185_coercible_phantom/Main.snap new file mode 100644 index 00000000..d4a9853f --- /dev/null +++ b/tests-integration/fixtures/checking/185_coercible_phantom/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Proxy :: forall (t0 :: Type) (a :: t0). Proxy @t0 a +coerceProxy :: forall (t4 :: Type) (t6 :: Type) (a :: t4) (b :: t6). Proxy @t4 a -> Proxy @t6 b +coerceProxyIntString :: Proxy @Type Int -> Proxy @Type String + +Types +Proxy :: forall (t0 :: Type). t0 -> Type + +Data +Proxy + Quantified = :1 + Kind = :0 + + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking/186_coercible_representational/Main.purs b/tests-integration/fixtures/checking/186_coercible_representational/Main.purs new file mode 100644 index 00000000..3af53be0 --- /dev/null +++ b/tests-integration/fixtures/checking/186_coercible_representational/Main.purs @@ -0,0 +1,18 @@ +module Main where + +import Safe.Coerce (coerce) + +data Maybe a = Nothing | Just a + +newtype Age = Age Int + +coerceMaybe :: Maybe Age -> Maybe Int +coerceMaybe = coerce + +coerceMaybeReverse :: Maybe Int -> Maybe Age +coerceMaybeReverse = coerce + +newtype UserId = UserId Int + +coerceNested :: Maybe UserId -> Maybe Int +coerceNested = coerce diff --git a/tests-integration/fixtures/checking/186_coercible_representational/Main.snap b/tests-integration/fixtures/checking/186_coercible_representational/Main.snap new file mode 100644 index 00000000..5de52980 --- /dev/null +++ b/tests-integration/fixtures/checking/186_coercible_representational/Main.snap @@ -0,0 +1,36 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe a +Just :: forall (a :: Type). a -> Maybe a +Age :: Int -> Age +coerceMaybe :: Maybe Age -> Maybe Int +coerceMaybeReverse :: Maybe Int -> Maybe Age +UserId :: Int -> UserId +coerceNested :: Maybe UserId -> Maybe Int + +Types +Maybe :: Type -> Type +Age :: Type +UserId :: Type + +Data +Maybe + Quantified = :0 + Kind = :0 + +Age + Quantified = :0 + Kind = :0 + +UserId + Quantified = :0 + Kind = :0 + + +Roles +Maybe = [Representational] +Age = [] +UserId = [] diff --git a/tests-integration/fixtures/checking/187_coercible_array/Main.purs b/tests-integration/fixtures/checking/187_coercible_array/Main.purs new file mode 100644 index 00000000..290137fa --- /dev/null +++ b/tests-integration/fixtures/checking/187_coercible_array/Main.purs @@ -0,0 +1,11 @@ +module Main where + +import Safe.Coerce (coerce) + +newtype Age = Age Int + +coerceArray :: Array Age -> Array Int +coerceArray = coerce + +coerceArrayReverse :: Array Int -> Array Age +coerceArrayReverse = coerce diff --git a/tests-integration/fixtures/checking/187_coercible_array/Main.snap b/tests-integration/fixtures/checking/187_coercible_array/Main.snap new file mode 100644 index 00000000..4706bde1 --- /dev/null +++ b/tests-integration/fixtures/checking/187_coercible_array/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Age :: Int -> Age +coerceArray :: Array Age -> Array Int +coerceArrayReverse :: Array Int -> Array Age + +Types +Age :: Type + +Data +Age + Quantified = :0 + Kind = :0 + + +Roles +Age = [] diff --git a/tests-integration/fixtures/checking/188_coercible_record/Main.purs b/tests-integration/fixtures/checking/188_coercible_record/Main.purs new file mode 100644 index 00000000..42dc5819 --- /dev/null +++ b/tests-integration/fixtures/checking/188_coercible_record/Main.purs @@ -0,0 +1,16 @@ +module Main where + +import Safe.Coerce (coerce) + +newtype Age = Age Int + +coerceRecord :: { name :: String, age :: Age } -> { name :: String, age :: Int } +coerceRecord = coerce + +coerceRecordReverse :: { name :: String, age :: Int } -> { name :: String, age :: Age } +coerceRecordReverse = coerce + +newtype UserId = UserId Int + +coerceMultiple :: { age :: Age, id :: UserId } -> { age :: Int, id :: Int } +coerceMultiple = coerce diff --git a/tests-integration/fixtures/checking/188_coercible_record/Main.snap b/tests-integration/fixtures/checking/188_coercible_record/Main.snap new file mode 100644 index 00000000..42374f0c --- /dev/null +++ b/tests-integration/fixtures/checking/188_coercible_record/Main.snap @@ -0,0 +1,28 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Age :: Int -> Age +coerceRecord :: { age :: Age, name :: String } -> { age :: Int, name :: String } +coerceRecordReverse :: { age :: Int, name :: String } -> { age :: Age, name :: String } +UserId :: Int -> UserId +coerceMultiple :: { age :: Age, id :: UserId } -> { age :: Int, id :: Int } + +Types +Age :: Type +UserId :: Type + +Data +Age + Quantified = :0 + Kind = :0 + +UserId + Quantified = :0 + Kind = :0 + + +Roles +Age = [] +UserId = [] diff --git a/tests-integration/fixtures/checking/189_coercible_different_heads_error/Main.purs b/tests-integration/fixtures/checking/189_coercible_different_heads_error/Main.purs new file mode 100644 index 00000000..e4945c35 --- /dev/null +++ b/tests-integration/fixtures/checking/189_coercible_different_heads_error/Main.purs @@ -0,0 +1,9 @@ +module Main where + +import Safe.Coerce (coerce) + +data Maybe a = Nothing | Just a +data Either a b = Left a | Right b + +coerceDifferent :: Maybe Int -> Either Int String +coerceDifferent = coerce diff --git a/tests-integration/fixtures/checking/189_coercible_different_heads_error/Main.snap b/tests-integration/fixtures/checking/189_coercible_different_heads_error/Main.snap new file mode 100644 index 00000000..7aed004b --- /dev/null +++ b/tests-integration/fixtures/checking/189_coercible_different_heads_error/Main.snap @@ -0,0 +1,31 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe a +Just :: forall (a :: Type). a -> Maybe a +Left :: forall (a :: Type) (b :: Type). a -> Either a b +Right :: forall (a :: Type) (b :: Type). b -> Either a b +coerceDifferent :: Maybe Int -> Either Int String + +Types +Maybe :: Type -> Type +Either :: Type -> Type -> Type + +Data +Maybe + Quantified = :0 + Kind = :0 + +Either + Quantified = :0 + Kind = :0 + + +Roles +Maybe = [Representational] +Either = [Representational, Representational] + +Errors +NoInstanceFound { Coercible @Type (Maybe Int) (Either Int String) } at [TermDeclaration(Idx::(4))] diff --git a/tests-integration/fixtures/checking/190_coercible_nominal/Main.purs b/tests-integration/fixtures/checking/190_coercible_nominal/Main.purs new file mode 100644 index 00000000..b42e1690 --- /dev/null +++ b/tests-integration/fixtures/checking/190_coercible_nominal/Main.purs @@ -0,0 +1,13 @@ +module Main where + +import Safe.Coerce (coerce) + +foreign import data Nominal :: Type -> Type + +type role Nominal nominal + +coerceNominalSame :: Nominal Int -> Nominal Int +coerceNominalSame = coerce + +coerceNominalDifferent :: Nominal Int -> Nominal String +coerceNominalDifferent = coerce diff --git a/tests-integration/fixtures/checking/190_coercible_nominal/Main.snap b/tests-integration/fixtures/checking/190_coercible_nominal/Main.snap new file mode 100644 index 00000000..3858b498 --- /dev/null +++ b/tests-integration/fixtures/checking/190_coercible_nominal/Main.snap @@ -0,0 +1,16 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +coerceNominalSame :: Nominal Int -> Nominal Int +coerceNominalDifferent :: Nominal Int -> Nominal String + +Types +Nominal :: Type -> Type + +Roles +Nominal = [Nominal] + +Errors +NoInstanceFound { Coercible @Type (Nominal Int) (Nominal String) } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Lib.purs b/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Lib.purs new file mode 100644 index 00000000..4eb11d87 --- /dev/null +++ b/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Lib.purs @@ -0,0 +1,3 @@ +module Lib (HiddenAge) where + +newtype HiddenAge = HiddenAge Int diff --git a/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.purs b/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.purs new file mode 100644 index 00000000..80480037 --- /dev/null +++ b/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.purs @@ -0,0 +1,11 @@ +module Main where + +import Lib (HiddenAge) +import Lib as L +import Safe.Coerce (coerce) + +coerceHidden :: Int -> HiddenAge +coerceHidden = coerce + +coerceQualified :: Int -> L.HiddenAge +coerceQualified = coerce diff --git a/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.snap b/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.snap new file mode 100644 index 00000000..7bd5e523 --- /dev/null +++ b/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.snap @@ -0,0 +1,15 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +coerceHidden :: Int -> HiddenAge +coerceQualified :: Int -> HiddenAge + +Types + +Errors +CoercibleConstructorNotInScope { file_id: Idx::(25), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +NoInstanceFound { Coercible @Type Int HiddenAge } at [TermDeclaration(Idx::(0))] +CoercibleConstructorNotInScope { file_id: Idx::(25), item_id: Idx::(0) } at [TermDeclaration(Idx::(1))] +NoInstanceFound { Coercible @Type Int HiddenAge } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/192_coercible_newtype_qualified/Lib.purs b/tests-integration/fixtures/checking/192_coercible_newtype_qualified/Lib.purs new file mode 100644 index 00000000..a4ad0b02 --- /dev/null +++ b/tests-integration/fixtures/checking/192_coercible_newtype_qualified/Lib.purs @@ -0,0 +1,3 @@ +module Lib (Age(..)) where + +newtype Age = Age Int diff --git a/tests-integration/fixtures/checking/192_coercible_newtype_qualified/Main.purs b/tests-integration/fixtures/checking/192_coercible_newtype_qualified/Main.purs new file mode 100644 index 00000000..7bcc7502 --- /dev/null +++ b/tests-integration/fixtures/checking/192_coercible_newtype_qualified/Main.purs @@ -0,0 +1,10 @@ +module Main where + +import Lib as L +import Safe.Coerce (coerce) + +coerceQualified :: Int -> L.Age +coerceQualified = coerce + +unwrapQualified :: L.Age -> Int +unwrapQualified = coerce diff --git a/tests-integration/fixtures/checking/192_coercible_newtype_qualified/Main.snap b/tests-integration/fixtures/checking/192_coercible_newtype_qualified/Main.snap new file mode 100644 index 00000000..39d2e92c --- /dev/null +++ b/tests-integration/fixtures/checking/192_coercible_newtype_qualified/Main.snap @@ -0,0 +1,9 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +coerceQualified :: Int -> Age +unwrapQualified :: Age -> Int + +Types diff --git a/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Lib.purs b/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Lib.purs new file mode 100644 index 00000000..4eb11d87 --- /dev/null +++ b/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Lib.purs @@ -0,0 +1,3 @@ +module Lib (HiddenAge) where + +newtype HiddenAge = HiddenAge Int diff --git a/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.purs b/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.purs new file mode 100644 index 00000000..86b390c1 --- /dev/null +++ b/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Lib +import Safe.Coerce (coerce) + +coerceOpen :: Int -> HiddenAge +coerceOpen = coerce diff --git a/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.snap b/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.snap new file mode 100644 index 00000000..7f8672c5 --- /dev/null +++ b/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.snap @@ -0,0 +1,12 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +coerceOpen :: Int -> HiddenAge + +Types + +Errors +CoercibleConstructorNotInScope { file_id: Idx::(25), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +NoInstanceFound { Coercible @Type Int HiddenAge } at [TermDeclaration(Idx::(0))] diff --git a/tests-integration/fixtures/checking/194_coercible_transitivity/Main.purs b/tests-integration/fixtures/checking/194_coercible_transitivity/Main.purs new file mode 100644 index 00000000..9cd68596 --- /dev/null +++ b/tests-integration/fixtures/checking/194_coercible_transitivity/Main.purs @@ -0,0 +1,18 @@ +module Main where + +import Safe.Coerce (coerce) + +newtype Age = Age Int +newtype Years = Years Age + +coerceTransitive :: Int -> Years +coerceTransitive = coerce + +unwrapTransitive :: Years -> Int +unwrapTransitive = coerce + +step1 :: Int -> Age +step1 = coerce + +step2 :: Age -> Years +step2 = coerce diff --git a/tests-integration/fixtures/checking/194_coercible_transitivity/Main.snap b/tests-integration/fixtures/checking/194_coercible_transitivity/Main.snap new file mode 100644 index 00000000..50920907 --- /dev/null +++ b/tests-integration/fixtures/checking/194_coercible_transitivity/Main.snap @@ -0,0 +1,29 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Age :: Int -> Age +Years :: Age -> Years +coerceTransitive :: Int -> Years +unwrapTransitive :: Years -> Int +step1 :: Int -> Age +step2 :: Age -> Years + +Types +Age :: Type +Years :: Type + +Data +Age + Quantified = :0 + Kind = :0 + +Years + Quantified = :0 + Kind = :0 + + +Roles +Age = [] +Years = [] diff --git a/tests-integration/fixtures/checking/195_coercible_nested_records/Main.purs b/tests-integration/fixtures/checking/195_coercible_nested_records/Main.purs new file mode 100644 index 00000000..75eec92f --- /dev/null +++ b/tests-integration/fixtures/checking/195_coercible_nested_records/Main.purs @@ -0,0 +1,30 @@ +module Main where + +import Safe.Coerce (coerce) + +newtype Name = Name String +newtype Age = Age Int + +newtype Person = Person { name :: Name, age :: Age } +newtype Company = Company { ceo :: Person, name :: Name } + +type RawPerson = { name :: String, age :: Int } +type RawCompany = { ceo :: RawPerson, name :: String } + +unwrapCompany :: Company -> { ceo :: Person, name :: Name } +unwrapCompany = coerce + +fullyUnwrap :: Company -> RawCompany +fullyUnwrap = coerce + +fullyWrap :: RawCompany -> Company +fullyWrap = coerce + +unwrapPerson :: Person -> RawPerson +unwrapPerson = coerce + +nestedFieldCoerce :: { person :: Person } -> { person :: RawPerson } +nestedFieldCoerce = coerce + +arrayOfRecords :: Array { name :: Name } -> Array { name :: String } +arrayOfRecords = coerce diff --git a/tests-integration/fixtures/checking/195_coercible_nested_records/Main.snap b/tests-integration/fixtures/checking/195_coercible_nested_records/Main.snap new file mode 100644 index 00000000..fe7ee0f9 --- /dev/null +++ b/tests-integration/fixtures/checking/195_coercible_nested_records/Main.snap @@ -0,0 +1,59 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Name :: String -> Name +Age :: Int -> Age +Person :: { age :: Age, name :: Name } -> Person +Company :: { ceo :: Person, name :: Name } -> Company +unwrapCompany :: Company -> { ceo :: Person, name :: Name } +fullyUnwrap :: Company -> { ceo :: { age :: Int, name :: String }, name :: String } +fullyWrap :: { ceo :: { age :: Int, name :: String }, name :: String } -> Company +unwrapPerson :: Person -> { age :: Int, name :: String } +nestedFieldCoerce :: { person :: Person } -> { person :: { age :: Int, name :: String } } +arrayOfRecords :: Array { name :: Name } -> Array { name :: String } + +Types +Name :: Type +Age :: Type +Person :: Type +Company :: Type +RawPerson :: Type +RawCompany :: Type + +Synonyms +RawPerson = { age :: Int, name :: String } + Quantified = :0 + Kind = :0 + Type = :0 + +RawCompany = { ceo :: { age :: Int, name :: String }, name :: String } + Quantified = :0 + Kind = :0 + Type = :0 + + +Data +Name + Quantified = :0 + Kind = :0 + +Age + Quantified = :0 + Kind = :0 + +Person + Quantified = :0 + Kind = :0 + +Company + Quantified = :0 + Kind = :0 + + +Roles +Name = [] +Age = [] +Person = [] +Company = [] diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index a7de8463..924f6180 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -375,3 +375,29 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_181_role_declaration_loosen_error_main() { run_test("181_role_declaration_loosen_error", "Main"); } #[rustfmt::skip] #[test] fn test_182_role_declaration_foreign_main() { run_test("182_role_declaration_foreign", "Main"); } + +#[rustfmt::skip] #[test] fn test_183_coercible_reflexivity_main() { run_test("183_coercible_reflexivity", "Main"); } + +#[rustfmt::skip] #[test] fn test_184_coercible_newtype_wrap_main() { run_test("184_coercible_newtype_wrap", "Main"); } + +#[rustfmt::skip] #[test] fn test_185_coercible_phantom_main() { run_test("185_coercible_phantom", "Main"); } + +#[rustfmt::skip] #[test] fn test_186_coercible_representational_main() { run_test("186_coercible_representational", "Main"); } + +#[rustfmt::skip] #[test] fn test_187_coercible_array_main() { run_test("187_coercible_array", "Main"); } + +#[rustfmt::skip] #[test] fn test_188_coercible_record_main() { run_test("188_coercible_record", "Main"); } + +#[rustfmt::skip] #[test] fn test_189_coercible_different_heads_error_main() { run_test("189_coercible_different_heads_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_190_coercible_nominal_main() { run_test("190_coercible_nominal", "Main"); } + +#[rustfmt::skip] #[test] fn test_191_coercible_newtype_hidden_main() { run_test("191_coercible_newtype_hidden", "Main"); } + +#[rustfmt::skip] #[test] fn test_192_coercible_newtype_qualified_main() { run_test("192_coercible_newtype_qualified", "Main"); } + +#[rustfmt::skip] #[test] fn test_193_coercible_newtype_open_hidden_main() { run_test("193_coercible_newtype_open_hidden", "Main"); } + +#[rustfmt::skip] #[test] fn test_194_coercible_transitivity_main() { run_test("194_coercible_transitivity", "Main"); } + +#[rustfmt::skip] #[test] fn test_195_coercible_nested_records_main() { run_test("195_coercible_nested_records", "Main"); } From 26c45f3d70a38c80078d44f4279a4213f6ad6b98 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 05:18:05 +0800 Subject: [PATCH 48/62] Implement higher kinded coercion This commit implements coercion for `f <~> g` given that `f a <~> g a` can be solved. See the try_higher_kinded_coercion function for details. --- .../algorithm/constraint/compiler_solved.rs | 160 +++++++++++++++--- compiler-core/checking/src/algorithm/state.rs | 10 +- .../196_coercible_higher_kinded/Lib.purs | 8 + .../196_coercible_higher_kinded/Main.purs | 10 ++ .../196_coercible_higher_kinded/Main.snap | 9 + .../Main.purs | 12 ++ .../Main.snap | 33 ++++ .../Main.purs | 13 ++ .../Main.snap | 30 ++++ .../Main.purs | 18 ++ .../Main.snap | 30 ++++ tests-integration/tests/checking/generated.rs | 8 + 12 files changed, 318 insertions(+), 23 deletions(-) create mode 100644 tests-integration/fixtures/checking/196_coercible_higher_kinded/Lib.purs create mode 100644 tests-integration/fixtures/checking/196_coercible_higher_kinded/Main.purs create mode 100644 tests-integration/fixtures/checking/196_coercible_higher_kinded/Main.snap create mode 100644 tests-integration/fixtures/checking/197_coercible_higher_kinded_error/Main.purs create mode 100644 tests-integration/fixtures/checking/197_coercible_higher_kinded_error/Main.snap create mode 100644 tests-integration/fixtures/checking/198_coercible_higher_kinded_multi/Main.purs create mode 100644 tests-integration/fixtures/checking/198_coercible_higher_kinded_multi/Main.snap create mode 100644 tests-integration/fixtures/checking/199_coercible_higher_kinded_polykinded/Main.purs create mode 100644 tests-integration/fixtures/checking/199_coercible_higher_kinded_polykinded/Main.snap diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs index 0ab0e003..b8869644 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs @@ -15,6 +15,7 @@ use crate::algorithm::derive; use crate::algorithm::kind; use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::substitute; use crate::core::{Role, RowField, RowType}; use crate::{ExternalQueries, Type, TypeId}; @@ -621,6 +622,10 @@ where return Ok(Some(result)); } + if let Some(result) = try_higher_kinded_coercion(state, context, left, right)? { + return Ok(Some(result)); + } + if let Some(result) = try_row_coercion(state, context, left, right) { return Ok(Some(result)); } @@ -659,32 +664,36 @@ where { let mut hidden_newtype: Option<(FileId, TypeItemId)> = None; - if let Some((file_id, type_id)) = derive::extract_type_constructor(state, left) { - if is_newtype(context, file_id, type_id)? { - if is_constructor_in_scope(context, file_id, type_id)? { - let inner = derive::get_newtype_inner(state, context, file_id, type_id, left)?; - let constraint = make_coercible_constraint(state, context, inner, right); - return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { - constraints: vec![constraint], - equalities: vec![], - })); - } else { - hidden_newtype = Some((file_id, type_id)); + if has_type_kind(state, context, left)? { + if let Some((file_id, type_id)) = derive::extract_type_constructor(state, left) { + if is_newtype(context, file_id, type_id)? { + if is_constructor_in_scope(context, file_id, type_id)? { + let inner = derive::get_newtype_inner(state, context, file_id, type_id, left)?; + let constraint = make_coercible_constraint(state, context, inner, right); + return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { + constraints: vec![constraint], + equalities: vec![], + })); + } else { + hidden_newtype = Some((file_id, type_id)); + } } } } - if let Some((file_id, type_id)) = derive::extract_type_constructor(state, right) { - if is_newtype(context, file_id, type_id)? { - if is_constructor_in_scope(context, file_id, type_id)? { - let inner = derive::get_newtype_inner(state, context, file_id, type_id, right)?; - let constraint = make_coercible_constraint(state, context, left, inner); - return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { - constraints: vec![constraint], - equalities: vec![], - })); - } else if hidden_newtype.is_none() { - hidden_newtype = Some((file_id, type_id)); + if has_type_kind(state, context, right)? { + if let Some((file_id, type_id)) = derive::extract_type_constructor(state, right) { + if is_newtype(context, file_id, type_id)? { + if is_constructor_in_scope(context, file_id, type_id)? { + let inner = derive::get_newtype_inner(state, context, file_id, type_id, right)?; + let constraint = make_coercible_constraint(state, context, left, inner); + return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { + constraints: vec![constraint], + equalities: vec![], + })); + } else if hidden_newtype.is_none() { + hidden_newtype = Some((file_id, type_id)); + } } } } @@ -696,6 +705,19 @@ where Ok(NewtypeCoercionResult::NotApplicable) } +fn has_type_kind( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let kind = kind::elaborate_kind(state, context, type_id)?; + let kind = state.normalize_type(kind); + Ok(kind == context.prim.t) +} + fn is_newtype( context: &CheckContext, file_id: FileId, @@ -875,6 +897,100 @@ where Some(MatchInstance::Match { constraints, equalities: vec![] }) } +fn try_higher_kinded_coercion( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + // Let's say we're attempting to coerce the following types: + // + // data Maybe :: forall k. k -> Type -> Type + // data Maybe n a = Just a | Nothing + // + // newtype MaybeAlias :: forall k. k -> Type -> Type + // newtype MaybeAlias n a = MaybeAlias (Maybe n a) + // + // solve[Coercible Maybe MaybeAlias] + // + // In order to solve coercion for higher-kinded types like + // this, we need to be able to solve the following coercion. + // + // solve[Coercible (Maybe ~a) (MaybeAlias ~a)] + // + // To begin, we get the kinds of these types + // + // left_kind := forall k. k -> Type -> Type + // right_kind := forall k. k -> Type -> Type + let left_kind = kind::elaborate_kind(state, context, left)?; + let right_kind = kind::elaborate_kind(state, context, right)?; + + // decompose_kind_for_coercion instantiates the variables into + // skolem variables, then returns the first argument, which in + // this case is the already-skolemized `~k` + // + // left_kind_applied := Maybe @~k + // left_domain := ~k + let Some((left_kind_applied, left_domain)) = + decompose_kind_for_coercion(state, left, left_kind) + else { + return Ok(None); + }; + + // right_kind_applied := MaybeAlias @~k + // right_domain := ~k + let Some((right_kind_applied, right_domain)) = + decompose_kind_for_coercion(state, right, right_kind) + else { + return Ok(None); + }; + + if constraint::can_unify(state, left_domain, right_domain).is_apart() { + return Ok(Some(MatchInstance::Apart)); + } + + // Given left_domain ~ right_domain, create a skolem kinded by `~k` + let argument = state.fresh_skolem_kinded(left_domain); + + // Finally, we can saturated left_kind_applied and right_kind_applied + // + // left := Maybe @~k (~a :: ~k) + // right := MaybeAlias @~k (~a :: ~k) + // + // Finally, we emit `left <~> right` as a constraint and rely on the + // remaining rules to solve this for us, particularly newtype coercion. + let left = state.storage.intern(Type::Application(left_kind_applied, argument)); + let right = state.storage.intern(Type::Application(right_kind_applied, argument)); + let constraint = make_coercible_constraint(state, context, left, right); + + Ok(Some(MatchInstance::Match { constraints: vec![constraint], equalities: vec![] })) +} + +fn decompose_kind_for_coercion( + state: &mut CheckState, + mut type_id: TypeId, + mut kind_id: TypeId, +) -> Option<(TypeId, TypeId)> { + safe_loop! { + kind_id = state.normalize_type(kind_id); + + let forall = match &state.storage[kind_id] { + Type::Forall(binder, inner) => Some((binder.kind, binder.level, *inner)), + Type::Function(domain, _) => return Some((type_id, *domain)), + _ => return None, + }; + + if let Some((binder_kind, binder_level, inner_kind)) = forall { + let fresh_kind = state.fresh_skolem_kinded(binder_kind); + type_id = state.storage.intern(Type::KindApplication(type_id, fresh_kind)); + kind_id = substitute::SubstituteBound::on(state, binder_level, fresh_kind, inner_kind); + } + } +} + fn make_coercible_constraint( state: &mut CheckState, context: &CheckContext, diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index c7c89756..048a3e81 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -18,7 +18,7 @@ use rustc_hash::FxHashMap; use sugar::{Bracketed, Sectioned}; use crate::algorithm::{constraint, transfer}; -use crate::core::{Type, TypeId, TypeInterner, debruijn}; +use crate::core::{Type, TypeId, TypeInterner, Variable, debruijn}; use crate::error::{CheckError, ErrorKind, ErrorStep}; use crate::{CheckedModule, ExternalQueries}; @@ -922,6 +922,14 @@ impl CheckState { { self.fresh_unification_kinded(context.prim.t) } + + /// Creates a fresh skolem variable with the provided kind. + pub fn fresh_skolem_kinded(&mut self, kind: TypeId) -> TypeId { + let domain = self.type_scope.size(); + let level = debruijn::Level(domain.0); + let skolem = Variable::Skolem(level, kind); + self.storage.intern(Type::Variable(skolem)) + } } impl CheckState { diff --git a/tests-integration/fixtures/checking/196_coercible_higher_kinded/Lib.purs b/tests-integration/fixtures/checking/196_coercible_higher_kinded/Lib.purs new file mode 100644 index 00000000..50ce45a2 --- /dev/null +++ b/tests-integration/fixtures/checking/196_coercible_higher_kinded/Lib.purs @@ -0,0 +1,8 @@ +module Lib where + +data Maybe a = Nothing | Just a + +newtype MaybeAlias a = MaybeAlias (Maybe a) + +foreign import data Container :: forall k. k -> Type +type role Container representational diff --git a/tests-integration/fixtures/checking/196_coercible_higher_kinded/Main.purs b/tests-integration/fixtures/checking/196_coercible_higher_kinded/Main.purs new file mode 100644 index 00000000..53b9f8ae --- /dev/null +++ b/tests-integration/fixtures/checking/196_coercible_higher_kinded/Main.purs @@ -0,0 +1,10 @@ +module Main where + +import Safe.Coerce (coerce) +import Lib (Maybe(..), MaybeAlias(..), Container) + +coerceContainer :: Container Maybe -> Container MaybeAlias +coerceContainer = coerce + +coerceContainerReverse :: Container MaybeAlias -> Container Maybe +coerceContainerReverse = coerce diff --git a/tests-integration/fixtures/checking/196_coercible_higher_kinded/Main.snap b/tests-integration/fixtures/checking/196_coercible_higher_kinded/Main.snap new file mode 100644 index 00000000..c5568dc6 --- /dev/null +++ b/tests-integration/fixtures/checking/196_coercible_higher_kinded/Main.snap @@ -0,0 +1,9 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +coerceContainer :: Container @(Type -> Type) Maybe -> Container @(Type -> Type) MaybeAlias +coerceContainerReverse :: Container @(Type -> Type) MaybeAlias -> Container @(Type -> Type) Maybe + +Types diff --git a/tests-integration/fixtures/checking/197_coercible_higher_kinded_error/Main.purs b/tests-integration/fixtures/checking/197_coercible_higher_kinded_error/Main.purs new file mode 100644 index 00000000..9702dacc --- /dev/null +++ b/tests-integration/fixtures/checking/197_coercible_higher_kinded_error/Main.purs @@ -0,0 +1,12 @@ +module Main where + +import Safe.Coerce (coerce) + +data Maybe a = Nothing | Just a +data List a = Nil | Cons a (List a) + +foreign import data Container :: (Type -> Type) -> Type +type role Container representational + +coerceContainerDifferent :: Container Maybe -> Container List +coerceContainerDifferent = coerce diff --git a/tests-integration/fixtures/checking/197_coercible_higher_kinded_error/Main.snap b/tests-integration/fixtures/checking/197_coercible_higher_kinded_error/Main.snap new file mode 100644 index 00000000..25d52571 --- /dev/null +++ b/tests-integration/fixtures/checking/197_coercible_higher_kinded_error/Main.snap @@ -0,0 +1,33 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe a +Just :: forall (a :: Type). a -> Maybe a +Nil :: forall (a :: Type). List a +Cons :: forall (a :: Type). a -> List a -> List a +coerceContainerDifferent :: Container Maybe -> Container List + +Types +Maybe :: Type -> Type +List :: Type -> Type +Container :: (Type -> Type) -> Type + +Data +Maybe + Quantified = :0 + Kind = :0 + +List + Quantified = :0 + Kind = :0 + + +Roles +Maybe = [Representational] +List = [Representational] +Container = [Representational] + +Errors +NoInstanceFound { Coercible (Maybe ~&0) (List ~&0) } at [TermDeclaration(Idx::(4))] diff --git a/tests-integration/fixtures/checking/198_coercible_higher_kinded_multi/Main.purs b/tests-integration/fixtures/checking/198_coercible_higher_kinded_multi/Main.purs new file mode 100644 index 00000000..d335a1b3 --- /dev/null +++ b/tests-integration/fixtures/checking/198_coercible_higher_kinded_multi/Main.purs @@ -0,0 +1,13 @@ +module Main where + +import Safe.Coerce (coerce) + +data Either a b = Left a | Right b + +newtype EitherAlias a b = EitherAlias (Either a b) + +foreign import data Container :: forall k. k -> Type +type role Container representational + +coerceContainer :: Container Either -> Container EitherAlias +coerceContainer = coerce diff --git a/tests-integration/fixtures/checking/198_coercible_higher_kinded_multi/Main.snap b/tests-integration/fixtures/checking/198_coercible_higher_kinded_multi/Main.snap new file mode 100644 index 00000000..0c9438ce --- /dev/null +++ b/tests-integration/fixtures/checking/198_coercible_higher_kinded_multi/Main.snap @@ -0,0 +1,30 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Left :: forall (a :: Type) (b :: Type). a -> Either a b +Right :: forall (a :: Type) (b :: Type). b -> Either a b +EitherAlias :: forall (a :: Type) (b :: Type). Either a b -> EitherAlias a b +coerceContainer :: + Container @(Type -> Type -> Type) Either -> Container @(Type -> Type -> Type) EitherAlias + +Types +Either :: Type -> Type -> Type +EitherAlias :: Type -> Type -> Type +Container :: forall (k :: Type). k -> Type + +Data +Either + Quantified = :0 + Kind = :0 + +EitherAlias + Quantified = :0 + Kind = :0 + + +Roles +Either = [Representational, Representational] +EitherAlias = [Representational, Representational] +Container = [Representational] diff --git a/tests-integration/fixtures/checking/199_coercible_higher_kinded_polykinded/Main.purs b/tests-integration/fixtures/checking/199_coercible_higher_kinded_polykinded/Main.purs new file mode 100644 index 00000000..4d0535d2 --- /dev/null +++ b/tests-integration/fixtures/checking/199_coercible_higher_kinded_polykinded/Main.purs @@ -0,0 +1,18 @@ +module Main where + +import Safe.Coerce (coerce) + +data Maybe :: forall k. k -> Type -> Type +data Maybe n a = Just a | Nothing + +newtype MaybeAlias :: forall k. k -> Type -> Type +newtype MaybeAlias n a = MaybeAlias (Maybe n a) + +foreign import data Container :: (Type -> Type -> Type) -> Type +type role Container representational + +coerceContainer :: Container Maybe -> Container MaybeAlias +coerceContainer = coerce + +coerceContainerReverse :: Container MaybeAlias -> Container Maybe +coerceContainerReverse = coerce diff --git a/tests-integration/fixtures/checking/199_coercible_higher_kinded_polykinded/Main.snap b/tests-integration/fixtures/checking/199_coercible_higher_kinded_polykinded/Main.snap new file mode 100644 index 00000000..7cc42cf1 --- /dev/null +++ b/tests-integration/fixtures/checking/199_coercible_higher_kinded_polykinded/Main.snap @@ -0,0 +1,30 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Just :: forall (k :: Type) (n :: k) (a :: Type). a -> Maybe @k n a +Nothing :: forall (k :: Type) (n :: k) (a :: Type). Maybe @k n a +MaybeAlias :: forall (k :: Type) (n :: k) (a :: Type). Maybe @k n a -> MaybeAlias @k n a +coerceContainer :: Container Maybe -> Container MaybeAlias +coerceContainerReverse :: Container MaybeAlias -> Container Maybe + +Types +Maybe :: forall (k :: Type). k -> Type -> Type +MaybeAlias :: forall (k :: Type). k -> Type -> Type +Container :: (Type -> Type -> Type) -> Type + +Data +Maybe + Quantified = :0 + Kind = :1 + +MaybeAlias + Quantified = :0 + Kind = :1 + + +Roles +Maybe = [Phantom, Representational] +MaybeAlias = [Representational, Representational] +Container = [Representational] diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 924f6180..251ffb27 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -401,3 +401,11 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_194_coercible_transitivity_main() { run_test("194_coercible_transitivity", "Main"); } #[rustfmt::skip] #[test] fn test_195_coercible_nested_records_main() { run_test("195_coercible_nested_records", "Main"); } + +#[rustfmt::skip] #[test] fn test_196_coercible_higher_kinded_main() { run_test("196_coercible_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_197_coercible_higher_kinded_error_main() { run_test("197_coercible_higher_kinded_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_198_coercible_higher_kinded_multi_main() { run_test("198_coercible_higher_kinded_multi", "Main"); } + +#[rustfmt::skip] #[test] fn test_199_coercible_higher_kinded_polykinded_main() { run_test("199_coercible_higher_kinded_polykinded", "Main"); } From 79d3775527570ea1a33eec623489d46353fd647f Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 06:36:24 +0800 Subject: [PATCH 49/62] Implement transitive solving for Prim.Int.Compare --- .../checking/src/algorithm/constraint.rs | 5 +- .../algorithm/constraint/compiler_solved.rs | 98 +++++++++++++++++-- .../032_recursive_synonym_expansion/Main.snap | 18 ++-- .../123_incomplete_instance_head/Main.snap | 2 +- .../191_coercible_newtype_hidden/Main.snap | 4 +- .../Main.snap | 2 +- .../200_int_compare_transitive/Main.purs | 75 ++++++++++++++ .../200_int_compare_transitive/Main.snap | 70 +++++++++++++ .../201_int_compare_concrete/Main.purs | 42 ++++++++ .../201_int_compare_concrete/Main.snap | 20 ++++ .../202_int_compare_invalid/Main.purs | 21 ++++ .../202_int_compare_invalid/Main.snap | 22 +++++ .../prelude/Prim.Int.Compare.Proofs.purs | 75 ++++++++++++++ tests-integration/tests/checking/generated.rs | 6 ++ 14 files changed, 435 insertions(+), 25 deletions(-) create mode 100644 tests-integration/fixtures/checking/200_int_compare_transitive/Main.purs create mode 100644 tests-integration/fixtures/checking/200_int_compare_transitive/Main.snap create mode 100644 tests-integration/fixtures/checking/201_int_compare_concrete/Main.purs create mode 100644 tests-integration/fixtures/checking/201_int_compare_concrete/Main.snap create mode 100644 tests-integration/fixtures/checking/202_int_compare_invalid/Main.purs create mode 100644 tests-integration/fixtures/checking/202_int_compare_invalid/Main.snap create mode 100644 tests-integration/fixtures/checking/prelude/Prim.Int.Compare.Proofs.purs diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index 4410e2fd..06dcc3cd 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -59,7 +59,7 @@ where Some(MatchInstance::Apart | MatchInstance::Stuck) | None => (), } - match match_compiler_instances(state, context, &application)? { + match match_compiler_instances(state, context, &application, &given)? { Some(MatchInstance::Match { constraints, equalities }) => { for (t1, t2) in equalities { if unification::unify(state, context, t1, t2)? { @@ -816,6 +816,7 @@ fn match_compiler_instances( state: &mut CheckState, context: &CheckContext, wanted: &ConstraintApplication, + given: &[ConstraintApplication], ) -> QueryResult> where Q: ExternalQueries, @@ -828,7 +829,7 @@ where } else if item_id == context.prim_int.mul { prim_int_mul(state, arguments) } else if item_id == context.prim_int.compare { - prim_int_compare(state, context, arguments) + prim_int_compare(state, context, arguments, given) } else if item_id == context.prim_int.to_string { prim_int_to_string(state, arguments) } else { diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs index b8869644..57f678b9 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs @@ -6,6 +6,8 @@ use files::FileId; use indexing::TypeItemId; use itertools::{EitherOrBoth, izip}; use lowering::StringKind; +use petgraph::algo::has_path_connecting; +use petgraph::graphmap::DiGraphMap; use rustc_hash::FxHashSet; use smol_str::SmolStr; @@ -98,6 +100,7 @@ pub fn prim_int_compare( state: &mut CheckState, context: &CheckContext, arguments: &[TypeId], + given: &[constraint::ConstraintApplication], ) -> Option where Q: ExternalQueries, @@ -110,18 +113,93 @@ where let right = state.normalize_type(right); let ordering = state.normalize_type(ordering); - let Some(left_int) = extract_integer(state, left) else { - return Some(MatchInstance::Stuck); - }; + let left_int = extract_integer(state, left); + let right_int = extract_integer(state, right); - let Some(right_int) = extract_integer(state, right) else { - return Some(MatchInstance::Stuck); - }; + if let (Some(left_int), Some(right_int)) = (left_int, right_int) { + let result = match left_int.cmp(&right_int) { + Ordering::Less => context.prim_ordering.lt, + Ordering::Equal => context.prim_ordering.eq, + Ordering::Greater => context.prim_ordering.gt, + }; - let result = match left_int.cmp(&right_int) { - Ordering::Less => context.prim_ordering.lt, - Ordering::Equal => context.prim_ordering.eq, - Ordering::Greater => context.prim_ordering.gt, + if super::can_unify(state, ordering, result).is_apart() { + return Some(MatchInstance::Apart); + } + + return Some(MatchInstance::Match { + constraints: vec![], + equalities: vec![(ordering, result)], + }); + } + + prim_int_compare_transitive(state, context, left, right, ordering, given) +} + +/// Uses a graph-based approach to derive ordering relationships transitively. +/// +/// We build a directed graph where `a -> b` means `a < b`: +/// - `Compare a b LT`: add edge `a -> b` +/// - `Compare a b EQ`: add edges `a -> b` and `b -> a` +/// - `Compare a b GT`: add edge `b -> a` (since `a > b` means `b < a`) +/// +/// Then we compute reachability to determine the ordering: +/// - Path from `left` to `right` only: LT +/// - Path from `right` to `left` only: GT +/// - Paths in both directions: EQ +/// - No path: Unknown/Stuck +fn prim_int_compare_transitive( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, + ordering: TypeId, + given: &[constraint::ConstraintApplication], +) -> Option +where + Q: ExternalQueries, +{ + let prim_int = &context.prim_int; + let lt = context.prim_ordering.lt; + let eq = context.prim_ordering.eq; + let gt = context.prim_ordering.gt; + + let mut graph: DiGraphMap = DiGraphMap::new(); + + for constraint in given { + if constraint.file_id != prim_int.file_id || constraint.item_id != prim_int.compare { + continue; + } + + let &[a, b, ordering] = constraint.arguments.as_slice() else { continue }; + + let a = state.normalize_type(a); + let b = state.normalize_type(b); + + let ordering = state.normalize_type(ordering); + + if ordering == lt { + // a < b: add edge a -> b + graph.add_edge(a, b, ()); + } else if ordering == eq { + // a = b: add edges both ways + graph.add_edge(a, b, ()); + graph.add_edge(b, a, ()); + } else if ordering == gt { + // a > b means b < a: add edge b -> a + graph.add_edge(b, a, ()); + } + } + + // Check reachability in both directions + let left_reaches_right = has_path_connecting(&graph, left, right, None); + let right_reaches_left = has_path_connecting(&graph, right, left, None); + + let result = match (left_reaches_right, right_reaches_left) { + (true, true) => eq, + (true, false) => lt, + (false, true) => gt, + (false, false) => return Some(MatchInstance::Stuck), }; if super::can_unify(state, ordering, result).is_apart() { diff --git a/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap b/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap index 55ec31f7..9edb38b9 100644 --- a/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap +++ b/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap @@ -37,12 +37,12 @@ Valid = Int Errors -RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] -RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] -RecursiveSynonymExpansion { file_id: Idx::(25), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap b/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap index 316f0572..121c0284 100644 --- a/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap +++ b/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap @@ -18,4 +18,4 @@ instance Pair (Int :: Type) chain: 0 Errors -InstanceHeadMismatch { class_file: Idx::(25), class_item: Idx::(0), expected: 2, actual: 1 } at [TermDeclaration(Idx::(2))] +InstanceHeadMismatch { class_file: Idx::(26), class_item: Idx::(0), expected: 2, actual: 1 } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.snap b/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.snap index 7bd5e523..8c6a2243 100644 --- a/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.snap +++ b/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.snap @@ -9,7 +9,7 @@ coerceQualified :: Int -> HiddenAge Types Errors -CoercibleConstructorNotInScope { file_id: Idx::(25), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +CoercibleConstructorNotInScope { file_id: Idx::(26), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] NoInstanceFound { Coercible @Type Int HiddenAge } at [TermDeclaration(Idx::(0))] -CoercibleConstructorNotInScope { file_id: Idx::(25), item_id: Idx::(0) } at [TermDeclaration(Idx::(1))] +CoercibleConstructorNotInScope { file_id: Idx::(26), item_id: Idx::(0) } at [TermDeclaration(Idx::(1))] NoInstanceFound { Coercible @Type Int HiddenAge } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.snap b/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.snap index 7f8672c5..575c42be 100644 --- a/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.snap +++ b/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.snap @@ -8,5 +8,5 @@ coerceOpen :: Int -> HiddenAge Types Errors -CoercibleConstructorNotInScope { file_id: Idx::(25), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +CoercibleConstructorNotInScope { file_id: Idx::(26), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] NoInstanceFound { Coercible @Type Int HiddenAge } at [TermDeclaration(Idx::(0))] diff --git a/tests-integration/fixtures/checking/200_int_compare_transitive/Main.purs b/tests-integration/fixtures/checking/200_int_compare_transitive/Main.purs new file mode 100644 index 00000000..be6a39dd --- /dev/null +++ b/tests-integration/fixtures/checking/200_int_compare_transitive/Main.purs @@ -0,0 +1,75 @@ +module Main where + +import Prim.Int (class Compare) +import Prim.Ordering (LT, EQ, GT) + +data Proxy :: forall k. k -> Type +data Proxy a = Proxy + +-- Assertion helpers (using row types to capture the comparison) +assertLesser :: forall l r. Compare l r LT => Proxy ( left :: l, right :: r ) +assertLesser = Proxy + +assertGreater :: forall l r. Compare l r GT => Proxy ( left :: l, right :: r ) +assertGreater = Proxy + +assertEqual :: forall l r. Compare l r EQ => Proxy ( left :: l, right :: r ) +assertEqual = Proxy + +-- Symmetry tests +symmLt :: forall m n. Compare m n GT => Proxy ( left :: n, right :: m ) +symmLt = assertLesser + +symmGt :: forall m n. Compare m n LT => Proxy ( left :: n, right :: m ) +symmGt = assertGreater + +symmEq :: forall m n. Compare m n EQ => Proxy ( left :: n, right :: m ) +symmEq = assertEqual + +-- Reflexivity +reflEq :: forall (n :: Int). Proxy ( left :: n, right :: n ) +reflEq = assertEqual + +-- Basic transitivity +transLt :: forall m n p. Compare m n LT => Compare n p LT => Proxy n -> Proxy ( left :: m, right :: p ) +transLt _ = assertLesser + +transLtEq :: forall m n p. Compare m n LT => Compare n p EQ => Proxy n -> Proxy ( left :: m, right :: p ) +transLtEq _ = assertLesser + +transEqLt :: forall m n p. Compare m n EQ => Compare n p LT => Proxy n -> Proxy ( left :: m, right :: p ) +transEqLt _ = assertLesser + +transGt :: forall m n p. Compare m n GT => Compare n p GT => Proxy n -> Proxy ( left :: m, right :: p ) +transGt _ = assertGreater + +transGtEq :: forall m n p. Compare m n GT => Compare n p EQ => Proxy n -> Proxy ( left :: m, right :: p ) +transGtEq _ = assertGreater + +transEqGt :: forall m n p. Compare m n EQ => Compare n p GT => Proxy n -> Proxy ( left :: m, right :: p ) +transEqGt _ = assertGreater + +transEq :: forall m n p. Compare m n EQ => Compare n p EQ => Proxy n -> Proxy ( left :: m, right :: p ) +transEq _ = assertEqual + +-- Transitivity with symmetry (constraint direction differs from chain direction) +transSymmLt :: forall m n p. Compare n m GT => Compare n p LT => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmLt _ = assertLesser + +transSymmLtEq :: forall m n p. Compare n m GT => Compare n p EQ => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmLtEq _ = assertLesser + +transSymmEqLt :: forall m n p. Compare n m EQ => Compare n p LT => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmEqLt _ = assertLesser + +transSymmGt :: forall m n p. Compare n m LT => Compare n p GT => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmGt _ = assertGreater + +transSymmGtEq :: forall m n p. Compare n m LT => Compare n p EQ => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmGtEq _ = assertGreater + +transSymmEqGt :: forall m n p. Compare n m EQ => Compare n p GT => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmEqGt _ = assertGreater + +transSymmEq :: forall m n p. Compare n m EQ => Compare n p EQ => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmEq _ = assertEqual diff --git a/tests-integration/fixtures/checking/200_int_compare_transitive/Main.snap b/tests-integration/fixtures/checking/200_int_compare_transitive/Main.snap new file mode 100644 index 00000000..a08bad86 --- /dev/null +++ b/tests-integration/fixtures/checking/200_int_compare_transitive/Main.snap @@ -0,0 +1,70 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Proxy :: forall (k :: Type) (a :: k). Proxy @k a +assertLesser :: + forall (l :: Int) (r :: Int). Compare l r LT => Proxy @(Row Int) ( left :: l, right :: r ) +assertGreater :: + forall (l :: Int) (r :: Int). Compare l r GT => Proxy @(Row Int) ( left :: l, right :: r ) +assertEqual :: + forall (l :: Int) (r :: Int). Compare l r EQ => Proxy @(Row Int) ( left :: l, right :: r ) +symmLt :: forall (m :: Int) (n :: Int). Compare m n GT => Proxy @(Row Int) ( left :: n, right :: m ) +symmGt :: forall (m :: Int) (n :: Int). Compare m n LT => Proxy @(Row Int) ( left :: n, right :: m ) +symmEq :: forall (m :: Int) (n :: Int). Compare m n EQ => Proxy @(Row Int) ( left :: n, right :: m ) +reflEq :: forall (n :: Int). Proxy @(Row Int) ( left :: n, right :: n ) +transLt :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare m n LT => Compare n p LT => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transLtEq :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare m n LT => Compare n p EQ => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transEqLt :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare m n EQ => Compare n p LT => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transGt :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare m n GT => Compare n p GT => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transGtEq :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare m n GT => Compare n p EQ => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transEqGt :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare m n EQ => Compare n p GT => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transEq :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare m n EQ => Compare n p EQ => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transSymmLt :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare n m GT => Compare n p LT => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transSymmLtEq :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare n m GT => Compare n p EQ => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transSymmEqLt :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare n m EQ => Compare n p LT => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transSymmGt :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare n m LT => Compare n p GT => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transSymmGtEq :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare n m LT => Compare n p EQ => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transSymmEqGt :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare n m EQ => Compare n p GT => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) +transSymmEq :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare n m EQ => Compare n p EQ => Proxy @Int n -> Proxy @(Row Int) ( left :: m, right :: p ) + +Types +Proxy :: forall (k :: Type). k -> Type + +Data +Proxy + Quantified = :0 + Kind = :1 + + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking/201_int_compare_concrete/Main.purs b/tests-integration/fixtures/checking/201_int_compare_concrete/Main.purs new file mode 100644 index 00000000..f86e8a7c --- /dev/null +++ b/tests-integration/fixtures/checking/201_int_compare_concrete/Main.purs @@ -0,0 +1,42 @@ +module Main where + +import Prim.Int.Compare.Proofs + +-- Literal comparisons +litLt :: Proxy ( left :: 0, right :: 1 ) +litLt = assertLesser + +litGt :: Proxy ( left :: 1, right :: 0 ) +litGt = assertGreater + +litEq :: Proxy ( left :: 0, right :: 0 ) +litEq = assertEqual + +-- Concrete transitivity proofs +testTransLt :: Proxy ( left :: 1, right :: 10 ) +testTransLt = transLt (Proxy :: Proxy 5) + +testTransLtEq :: Proxy ( left :: 1, right :: 5 ) +testTransLtEq = transLtEq (Proxy :: Proxy 5) + +testTransEqLt :: Proxy ( left :: 5, right :: 10 ) +testTransEqLt = transEqLt (Proxy :: Proxy 5) + +testTransGt :: Proxy ( left :: 10, right :: 1 ) +testTransGt = transGt (Proxy :: Proxy 5) + +testTransGtEq :: Proxy ( left :: 10, right :: 5 ) +testTransGtEq = transGtEq (Proxy :: Proxy 5) + +testTransEqGt :: Proxy ( left :: 5, right :: 1 ) +testTransEqGt = transEqGt (Proxy :: Proxy 5) + +testTransEq :: Proxy ( left :: 5, right :: 5 ) +testTransEq = transEq (Proxy :: Proxy 5) + +-- Concrete transitivity with symmetry +testTransSymmLt :: Proxy ( left :: 1, right :: 10 ) +testTransSymmLt = transSymmLt (Proxy :: Proxy 5) + +testTransSymmGt :: Proxy ( left :: 10, right :: 1 ) +testTransSymmGt = transSymmGt (Proxy :: Proxy 5) diff --git a/tests-integration/fixtures/checking/201_int_compare_concrete/Main.snap b/tests-integration/fixtures/checking/201_int_compare_concrete/Main.snap new file mode 100644 index 00000000..c2e81daa --- /dev/null +++ b/tests-integration/fixtures/checking/201_int_compare_concrete/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking/generated.rs +assertion_line: 12 +expression: report +--- +Terms +litLt :: Proxy @(Row Int) ( left :: 0, right :: 1 ) +litGt :: Proxy @(Row Int) ( left :: 1, right :: 0 ) +litEq :: Proxy @(Row Int) ( left :: 0, right :: 0 ) +testTransLt :: Proxy @(Row Int) ( left :: 1, right :: 10 ) +testTransLtEq :: Proxy @(Row Int) ( left :: 1, right :: 5 ) +testTransEqLt :: Proxy @(Row Int) ( left :: 5, right :: 10 ) +testTransGt :: Proxy @(Row Int) ( left :: 10, right :: 1 ) +testTransGtEq :: Proxy @(Row Int) ( left :: 10, right :: 5 ) +testTransEqGt :: Proxy @(Row Int) ( left :: 5, right :: 1 ) +testTransEq :: Proxy @(Row Int) ( left :: 5, right :: 5 ) +testTransSymmLt :: Proxy @(Row Int) ( left :: 1, right :: 10 ) +testTransSymmGt :: Proxy @(Row Int) ( left :: 10, right :: 1 ) + +Types diff --git a/tests-integration/fixtures/checking/202_int_compare_invalid/Main.purs b/tests-integration/fixtures/checking/202_int_compare_invalid/Main.purs new file mode 100644 index 00000000..370d1f04 --- /dev/null +++ b/tests-integration/fixtures/checking/202_int_compare_invalid/Main.purs @@ -0,0 +1,21 @@ +module Main where + +import Prim.Int.Compare.Proofs + +-- Invalid: trying to prove 10 < 1 via transLt (requires 10 < n < 1, impossible) +invalidTransLt :: Proxy ( left :: 10, right :: 1 ) +invalidTransLt = transLt (Proxy :: Proxy 5) + +-- Invalid: trying to prove 1 > 10 via transGt (requires 1 > n > 10, impossible) +invalidTransGt :: Proxy ( left :: 1, right :: 10 ) +invalidTransGt = transGt (Proxy :: Proxy 5) + +-- Invalid: direct comparison failures +invalidLt :: Proxy ( left :: 5, right :: 1 ) +invalidLt = assertLesser + +invalidGt :: Proxy ( left :: 1, right :: 5 ) +invalidGt = assertGreater + +invalidEq :: Proxy ( left :: 1, right :: 5 ) +invalidEq = assertEqual diff --git a/tests-integration/fixtures/checking/202_int_compare_invalid/Main.snap b/tests-integration/fixtures/checking/202_int_compare_invalid/Main.snap new file mode 100644 index 00000000..f6f770a2 --- /dev/null +++ b/tests-integration/fixtures/checking/202_int_compare_invalid/Main.snap @@ -0,0 +1,22 @@ +--- +source: tests-integration/tests/checking/generated.rs +assertion_line: 12 +expression: report +--- +Terms +invalidTransLt :: Proxy @(Row Int) ( left :: 10, right :: 1 ) +invalidTransGt :: Proxy @(Row Int) ( left :: 1, right :: 10 ) +invalidLt :: Proxy @(Row Int) ( left :: 5, right :: 1 ) +invalidGt :: Proxy @(Row Int) ( left :: 1, right :: 5 ) +invalidEq :: Proxy @(Row Int) ( left :: 1, right :: 5 ) + +Types + +Errors +NoInstanceFound { Compare 10 5 LT } at [TermDeclaration(Idx::(0))] +NoInstanceFound { Compare 5 1 LT } at [TermDeclaration(Idx::(0))] +NoInstanceFound { Compare 1 5 GT } at [TermDeclaration(Idx::(1))] +NoInstanceFound { Compare 5 10 GT } at [TermDeclaration(Idx::(1))] +NoInstanceFound { Compare 5 1 LT } at [TermDeclaration(Idx::(2))] +NoInstanceFound { Compare 1 5 GT } at [TermDeclaration(Idx::(3))] +NoInstanceFound { Compare 1 5 EQ } at [TermDeclaration(Idx::(4))] diff --git a/tests-integration/fixtures/checking/prelude/Prim.Int.Compare.Proofs.purs b/tests-integration/fixtures/checking/prelude/Prim.Int.Compare.Proofs.purs new file mode 100644 index 00000000..82709aca --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Prim.Int.Compare.Proofs.purs @@ -0,0 +1,75 @@ +module Prim.Int.Compare.Proofs where + +import Prim.Int (class Compare) +import Prim.Ordering (LT, EQ, GT) + +data Proxy :: forall k. k -> Type +data Proxy a = Proxy + +-- Assertion helpers (using row types to capture the comparison) +assertLesser :: forall l r. Compare l r LT => Proxy ( left :: l, right :: r ) +assertLesser = Proxy + +assertGreater :: forall l r. Compare l r GT => Proxy ( left :: l, right :: r ) +assertGreater = Proxy + +assertEqual :: forall l r. Compare l r EQ => Proxy ( left :: l, right :: r ) +assertEqual = Proxy + +-- Symmetry tests +symmLt :: forall m n. Compare m n GT => Proxy ( left :: n, right :: m ) +symmLt = assertLesser + +symmGt :: forall m n. Compare m n LT => Proxy ( left :: n, right :: m ) +symmGt = assertGreater + +symmEq :: forall m n. Compare m n EQ => Proxy ( left :: n, right :: m ) +symmEq = assertEqual + +-- Reflexivity +reflEq :: forall (n :: Int). Proxy ( left :: n, right :: n ) +reflEq = assertEqual + +-- Basic transitivity +transLt :: forall m n p. Compare m n LT => Compare n p LT => Proxy n -> Proxy ( left :: m, right :: p ) +transLt _ = assertLesser + +transLtEq :: forall m n p. Compare m n LT => Compare n p EQ => Proxy n -> Proxy ( left :: m, right :: p ) +transLtEq _ = assertLesser + +transEqLt :: forall m n p. Compare m n EQ => Compare n p LT => Proxy n -> Proxy ( left :: m, right :: p ) +transEqLt _ = assertLesser + +transGt :: forall m n p. Compare m n GT => Compare n p GT => Proxy n -> Proxy ( left :: m, right :: p ) +transGt _ = assertGreater + +transGtEq :: forall m n p. Compare m n GT => Compare n p EQ => Proxy n -> Proxy ( left :: m, right :: p ) +transGtEq _ = assertGreater + +transEqGt :: forall m n p. Compare m n EQ => Compare n p GT => Proxy n -> Proxy ( left :: m, right :: p ) +transEqGt _ = assertGreater + +transEq :: forall m n p. Compare m n EQ => Compare n p EQ => Proxy n -> Proxy ( left :: m, right :: p ) +transEq _ = assertEqual + +-- Transitivity with symmetry (constraint direction differs from chain direction) +transSymmLt :: forall m n p. Compare n m GT => Compare n p LT => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmLt _ = assertLesser + +transSymmLtEq :: forall m n p. Compare n m GT => Compare n p EQ => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmLtEq _ = assertLesser + +transSymmEqLt :: forall m n p. Compare n m EQ => Compare n p LT => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmEqLt _ = assertLesser + +transSymmGt :: forall m n p. Compare n m LT => Compare n p GT => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmGt _ = assertGreater + +transSymmGtEq :: forall m n p. Compare n m LT => Compare n p EQ => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmGtEq _ = assertGreater + +transSymmEqGt :: forall m n p. Compare n m EQ => Compare n p GT => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmEqGt _ = assertGreater + +transSymmEq :: forall m n p. Compare n m EQ => Compare n p EQ => Proxy n -> Proxy ( left :: m, right :: p ) +transSymmEq _ = assertEqual diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 251ffb27..e378ec70 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -409,3 +409,9 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_198_coercible_higher_kinded_multi_main() { run_test("198_coercible_higher_kinded_multi", "Main"); } #[rustfmt::skip] #[test] fn test_199_coercible_higher_kinded_polykinded_main() { run_test("199_coercible_higher_kinded_polykinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_200_int_compare_transitive_main() { run_test("200_int_compare_transitive", "Main"); } + +#[rustfmt::skip] #[test] fn test_201_int_compare_concrete_main() { run_test("201_int_compare_concrete", "Main"); } + +#[rustfmt::skip] #[test] fn test_202_int_compare_invalid_main() { run_test("202_int_compare_invalid", "Main"); } From bb8a2659cbec503ffe355c7e8ba3c231b0b7279c Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 06:56:10 +0800 Subject: [PATCH 50/62] Implement solving for IsSymbol and Reflectable --- .../checking/src/algorithm/constraint.rs | 4 ++ .../algorithm/constraint/compiler_solved.rs | 70 +++++++++++++++++++ compiler-core/checking/src/algorithm/state.rs | 48 +++++++++++++ .../032_recursive_synonym_expansion/Main.snap | 18 ++--- .../123_incomplete_instance_head/Main.snap | 2 +- .../191_coercible_newtype_hidden/Main.snap | 4 +- .../Main.snap | 2 +- .../fixtures/checking/203_is_symbol/Main.purs | 14 ++++ .../fixtures/checking/203_is_symbol/Main.snap | 11 +++ .../checking/204_reflectable/Main.purs | 42 +++++++++++ .../checking/204_reflectable/Main.snap | 21 ++++++ .../checking/prelude/Data.Ordering.purs | 3 + .../checking/prelude/Data.Reflectable.purs | 7 ++ .../checking/prelude/Data.Symbol.purs | 6 ++ .../fixtures/checking/prelude/Type.Proxy.purs | 4 ++ tests-integration/tests/checking/generated.rs | 4 ++ ...ecking__invalid_type_operator_ternary.snap | 2 +- ...checking__invalid_type_operator_unary.snap | 2 +- 18 files changed, 249 insertions(+), 15 deletions(-) create mode 100644 tests-integration/fixtures/checking/203_is_symbol/Main.purs create mode 100644 tests-integration/fixtures/checking/203_is_symbol/Main.snap create mode 100644 tests-integration/fixtures/checking/204_reflectable/Main.purs create mode 100644 tests-integration/fixtures/checking/204_reflectable/Main.snap create mode 100644 tests-integration/fixtures/checking/prelude/Data.Ordering.purs create mode 100644 tests-integration/fixtures/checking/prelude/Data.Reflectable.purs create mode 100644 tests-integration/fixtures/checking/prelude/Data.Symbol.purs create mode 100644 tests-integration/fixtures/checking/prelude/Type.Proxy.purs diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index 06dcc3cd..cce8a9fe 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -869,6 +869,10 @@ where } else { None } + } else if context.known_reflectable.is_symbol == Some((file_id, item_id)) { + prim_is_symbol(state, arguments) + } else if context.known_reflectable.reflectable == Some((file_id, item_id)) { + prim_reflectable(state, context, arguments) } else { None }; diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs index 57f678b9..a7ad30f7 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs @@ -1084,3 +1084,73 @@ where let coercible = state.storage.intern(Type::Application(coercible, left)); state.storage.intern(Type::Application(coercible, right)) } + +pub fn prim_is_symbol(state: &mut CheckState, arguments: &[TypeId]) -> Option { + let &[symbol] = arguments else { return None }; + let symbol = state.normalize_type(symbol); + + if extract_symbol(state, symbol).is_some() { + Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) + } else if matches!(state.storage[symbol], Type::Unification(_)) { + Some(MatchInstance::Stuck) + } else { + Some(MatchInstance::Apart) + } +} + +pub fn prim_reflectable( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> Option +where + Q: ExternalQueries, +{ + let &[v, t] = arguments else { return None }; + + let v = state.normalize_type(v); + let t = state.normalize_type(t); + + if extract_symbol(state, v).is_some() { + let expected = context.prim.string; + return check_reflectable_match(state, t, expected); + } + + if extract_integer(state, v).is_some() { + let expected = context.prim.int; + return check_reflectable_match(state, t, expected); + } + + if v == context.prim_boolean.true_ || v == context.prim_boolean.false_ { + let expected = context.prim.boolean; + return check_reflectable_match(state, t, expected); + } + + if v == context.prim_ordering.lt + || v == context.prim_ordering.eq + || v == context.prim_ordering.gt + { + let Some(expected) = context.known_reflectable.ordering else { + return Some(MatchInstance::Stuck); + }; + return check_reflectable_match(state, t, expected); + } + + if matches!(state.storage[v], Type::Unification(_)) { + return Some(MatchInstance::Stuck); + } + + Some(MatchInstance::Apart) +} + +fn check_reflectable_match( + state: &mut CheckState, + actual: TypeId, + expected: TypeId, +) -> Option { + if super::can_unify(state, actual, expected).is_apart() { + Some(MatchInstance::Apart) + } else { + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(actual, expected)] }) + } +} diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index 048a3e81..66c6a1cd 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -331,12 +331,14 @@ where pub queries: &'a Q, pub prim: PrimCore, pub prim_int: PrimIntCore, + pub prim_boolean: PrimBooleanCore, pub prim_ordering: PrimOrderingCore, pub prim_symbol: PrimSymbolCore, pub prim_row: PrimRowCore, pub prim_row_list: PrimRowListCore, pub prim_coerce: PrimCoerceCore, pub known_types: KnownTypesCore, + pub known_reflectable: KnownReflectableCore, pub known_generic: Option, pub id: FileId, @@ -367,12 +369,14 @@ where let sectioned = queries.sectioned(id)?; let prim = PrimCore::collect(queries, state)?; let prim_int = PrimIntCore::collect(queries, state)?; + let prim_boolean = PrimBooleanCore::collect(queries, state)?; let prim_ordering = PrimOrderingCore::collect(queries, state)?; let prim_symbol = PrimSymbolCore::collect(queries, state)?; let prim_row = PrimRowCore::collect(queries, state)?; let prim_row_list = PrimRowListCore::collect(queries, state)?; let prim_coerce = PrimCoerceCore::collect(queries)?; let known_types = KnownTypesCore::collect(queries)?; + let known_reflectable = KnownReflectableCore::collect(queries, &mut state.storage)?; let known_generic = KnownGeneric::collect(queries, &mut state.storage)?; let resolved = queries.resolved(id)?; let prim_id = queries.prim_id(); @@ -382,12 +386,14 @@ where queries, prim, prim_int, + prim_boolean, prim_ordering, prim_symbol, prim_row, prim_row_list, prim_coerce, known_types, + known_reflectable, known_generic, id, indexed, @@ -511,6 +517,30 @@ impl PrimIntCore { } } +pub struct PrimBooleanCore { + pub true_: TypeId, + pub false_: TypeId, +} + +impl PrimBooleanCore { + fn collect( + queries: &impl ExternalQueries, + state: &mut CheckState, + ) -> QueryResult { + let file_id = queries + .module_file("Prim.Boolean") + .unwrap_or_else(|| unreachable!("invariant violated: Prim.Boolean not found")); + + let resolved = queries.resolved(file_id)?; + let mut lookup = PrimLookup::new(&resolved, &mut state.storage, "Prim.Boolean"); + + Ok(PrimBooleanCore { + true_: lookup.type_constructor("True"), + false_: lookup.type_constructor("False"), + }) + } +} + pub struct PrimOrderingCore { pub lt: TypeId, pub eq: TypeId, @@ -725,6 +755,24 @@ impl KnownTypesCore { } } +pub struct KnownReflectableCore { + pub is_symbol: Option<(FileId, TypeItemId)>, + pub reflectable: Option<(FileId, TypeItemId)>, + pub ordering: Option, +} + +impl KnownReflectableCore { + fn collect( + queries: &impl ExternalQueries, + storage: &mut TypeInterner, + ) -> QueryResult { + let is_symbol = fetch_known_type(queries, "Data.Symbol", "IsSymbol")?; + let reflectable = fetch_known_type(queries, "Data.Reflectable", "Reflectable")?; + let ordering = fetch_known_constructor(queries, storage, "Data.Ordering", "Ordering")?; + Ok(KnownReflectableCore { is_symbol, reflectable, ordering }) + } +} + pub struct KnownGeneric { pub no_constructors: TypeId, pub constructor: TypeId, diff --git a/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap b/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap index 9edb38b9..202c8d28 100644 --- a/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap +++ b/tests-integration/fixtures/checking/032_recursive_synonym_expansion/Main.snap @@ -37,12 +37,12 @@ Valid = Int Errors -RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] -RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] -RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] -RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] -RecursiveSynonymExpansion { file_id: Idx::(26), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(30), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(30), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(30), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +RecursiveSynonymExpansion { file_id: Idx::(30), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(30), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(30), item_id: Idx::(1) } at [TermDeclaration(Idx::(1))] +RecursiveSynonymExpansion { file_id: Idx::(30), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(30), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] +RecursiveSynonymExpansion { file_id: Idx::(30), item_id: Idx::(2) } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap b/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap index 121c0284..48c1a0f4 100644 --- a/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap +++ b/tests-integration/fixtures/checking/123_incomplete_instance_head/Main.snap @@ -18,4 +18,4 @@ instance Pair (Int :: Type) chain: 0 Errors -InstanceHeadMismatch { class_file: Idx::(26), class_item: Idx::(0), expected: 2, actual: 1 } at [TermDeclaration(Idx::(2))] +InstanceHeadMismatch { class_file: Idx::(30), class_item: Idx::(0), expected: 2, actual: 1 } at [TermDeclaration(Idx::(2))] diff --git a/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.snap b/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.snap index 8c6a2243..4fa9f60e 100644 --- a/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.snap +++ b/tests-integration/fixtures/checking/191_coercible_newtype_hidden/Main.snap @@ -9,7 +9,7 @@ coerceQualified :: Int -> HiddenAge Types Errors -CoercibleConstructorNotInScope { file_id: Idx::(26), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +CoercibleConstructorNotInScope { file_id: Idx::(30), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] NoInstanceFound { Coercible @Type Int HiddenAge } at [TermDeclaration(Idx::(0))] -CoercibleConstructorNotInScope { file_id: Idx::(26), item_id: Idx::(0) } at [TermDeclaration(Idx::(1))] +CoercibleConstructorNotInScope { file_id: Idx::(30), item_id: Idx::(0) } at [TermDeclaration(Idx::(1))] NoInstanceFound { Coercible @Type Int HiddenAge } at [TermDeclaration(Idx::(1))] diff --git a/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.snap b/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.snap index 575c42be..cffd0690 100644 --- a/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.snap +++ b/tests-integration/fixtures/checking/193_coercible_newtype_open_hidden/Main.snap @@ -8,5 +8,5 @@ coerceOpen :: Int -> HiddenAge Types Errors -CoercibleConstructorNotInScope { file_id: Idx::(26), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +CoercibleConstructorNotInScope { file_id: Idx::(30), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] NoInstanceFound { Coercible @Type Int HiddenAge } at [TermDeclaration(Idx::(0))] diff --git a/tests-integration/fixtures/checking/203_is_symbol/Main.purs b/tests-integration/fixtures/checking/203_is_symbol/Main.purs new file mode 100644 index 00000000..3072c375 --- /dev/null +++ b/tests-integration/fixtures/checking/203_is_symbol/Main.purs @@ -0,0 +1,14 @@ +module Main where + +import Type.Proxy (Proxy(..)) +import Data.Symbol (class IsSymbol, reflectSymbol) + +test :: String +test = reflectSymbol (Proxy :: Proxy "hello") + +test' = reflectSymbol (Proxy :: Proxy "hello") + +testEmpty :: String +testEmpty = reflectSymbol (Proxy :: Proxy "") + +testEmpty' = reflectSymbol (Proxy :: Proxy "") diff --git a/tests-integration/fixtures/checking/203_is_symbol/Main.snap b/tests-integration/fixtures/checking/203_is_symbol/Main.snap new file mode 100644 index 00000000..6f391b1d --- /dev/null +++ b/tests-integration/fixtures/checking/203_is_symbol/Main.snap @@ -0,0 +1,11 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +test :: String +test' :: String +testEmpty :: String +testEmpty' :: String + +Types diff --git a/tests-integration/fixtures/checking/204_reflectable/Main.purs b/tests-integration/fixtures/checking/204_reflectable/Main.purs new file mode 100644 index 00000000..19958383 --- /dev/null +++ b/tests-integration/fixtures/checking/204_reflectable/Main.purs @@ -0,0 +1,42 @@ +module Main where + +import Type.Proxy (Proxy(..)) +import Data.Reflectable (class Reflectable, reflectType) +import Data.Ordering (Ordering) +import Prim.Boolean (True, False) +import Prim.Ordering (LT, EQ, GT) + +testSymbol :: String +testSymbol = reflectType (Proxy :: Proxy "hello") + +testSymbol' = reflectType (Proxy :: Proxy "hello") + +testInt :: Int +testInt = reflectType (Proxy :: Proxy 42) + +testInt' = reflectType (Proxy :: Proxy 42) + +testTrue :: Boolean +testTrue = reflectType (Proxy :: Proxy True) + +testTrue' = reflectType (Proxy :: Proxy True) + +testFalse :: Boolean +testFalse = reflectType (Proxy :: Proxy False) + +testFalse' = reflectType (Proxy :: Proxy False) + +testLT :: Ordering +testLT = reflectType (Proxy :: Proxy LT) + +testLT' = reflectType (Proxy :: Proxy LT) + +testEQ :: Ordering +testEQ = reflectType (Proxy :: Proxy EQ) + +testEQ' = reflectType (Proxy :: Proxy EQ) + +testGT :: Ordering +testGT = reflectType (Proxy :: Proxy GT) + +testGT' = reflectType (Proxy :: Proxy GT) diff --git a/tests-integration/fixtures/checking/204_reflectable/Main.snap b/tests-integration/fixtures/checking/204_reflectable/Main.snap new file mode 100644 index 00000000..6f04370a --- /dev/null +++ b/tests-integration/fixtures/checking/204_reflectable/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +testSymbol :: String +testSymbol' :: String +testInt :: Int +testInt' :: Int +testTrue :: Boolean +testTrue' :: Boolean +testFalse :: Boolean +testFalse' :: Boolean +testLT :: Ordering +testLT' :: Ordering +testEQ :: Ordering +testEQ' :: Ordering +testGT :: Ordering +testGT' :: Ordering + +Types diff --git a/tests-integration/fixtures/checking/prelude/Data.Ordering.purs b/tests-integration/fixtures/checking/prelude/Data.Ordering.purs new file mode 100644 index 00000000..19430b7a --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Data.Ordering.purs @@ -0,0 +1,3 @@ +module Data.Ordering where + +data Ordering = LT | EQ | GT diff --git a/tests-integration/fixtures/checking/prelude/Data.Reflectable.purs b/tests-integration/fixtures/checking/prelude/Data.Reflectable.purs new file mode 100644 index 00000000..1683350e --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Data.Reflectable.purs @@ -0,0 +1,7 @@ +module Data.Reflectable where + +import Type.Proxy (Proxy) + +class Reflectable :: forall k. k -> Type -> Constraint +class Reflectable v t | v -> t where + reflectType :: Proxy v -> t diff --git a/tests-integration/fixtures/checking/prelude/Data.Symbol.purs b/tests-integration/fixtures/checking/prelude/Data.Symbol.purs new file mode 100644 index 00000000..bec65bce --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Data.Symbol.purs @@ -0,0 +1,6 @@ +module Data.Symbol where + +import Type.Proxy (Proxy) + +class IsSymbol (sym :: Symbol) where + reflectSymbol :: Proxy sym -> String diff --git a/tests-integration/fixtures/checking/prelude/Type.Proxy.purs b/tests-integration/fixtures/checking/prelude/Type.Proxy.purs new file mode 100644 index 00000000..8282072f --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Type.Proxy.purs @@ -0,0 +1,4 @@ +module Type.Proxy where + +data Proxy :: forall k. k -> Type +data Proxy a = Proxy diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index e378ec70..c32538b1 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -415,3 +415,7 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_201_int_compare_concrete_main() { run_test("201_int_compare_concrete", "Main"); } #[rustfmt::skip] #[test] fn test_202_int_compare_invalid_main() { run_test("202_int_compare_invalid", "Main"); } + +#[rustfmt::skip] #[test] fn test_203_is_symbol_main() { run_test("203_is_symbol", "Main"); } + +#[rustfmt::skip] #[test] fn test_204_reflectable_main() { run_test("204_reflectable", "Main"); } diff --git a/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap b/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap index 4541c5fa..20cd9485 100644 --- a/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap +++ b/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap @@ -5,7 +5,7 @@ expression: checked.errors [ CheckError { kind: InvalidTypeOperator { - id: Id(40), + id: Id(42), }, step: [], }, diff --git a/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap b/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap index 174777a7..0c8828ab 100644 --- a/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap +++ b/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap @@ -5,7 +5,7 @@ expression: checked.errors [ CheckError { kind: InvalidTypeOperator { - id: Id(28), + id: Id(30), }, step: [], }, From 070af8874013c271f271ba028ccbf35f6e26e698 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 06:56:48 +0800 Subject: [PATCH 51/62] Format files --- tests-integration/src/generated/basic.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests-integration/src/generated/basic.rs b/tests-integration/src/generated/basic.rs index b95c5028..46ce8c26 100644 --- a/tests-integration/src/generated/basic.rs +++ b/tests-integration/src/generated/basic.rs @@ -306,8 +306,9 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { writeln!(snapshot, "\nRoles").unwrap(); } for (id, TypeItem { name, kind, .. }) in indexed.items.iter_types() { - let (TypeItemKind::Data { .. } | TypeItemKind::Newtype { .. } | TypeItemKind::Foreign { .. }) = - kind + let (TypeItemKind::Data { .. } + | TypeItemKind::Newtype { .. } + | TypeItemKind::Foreign { .. }) = kind else { continue; }; From 9c96f010691344ab26316b3494cadcfc3011ea88 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 06:57:03 +0800 Subject: [PATCH 52/62] Format imports --- .../checking/src/algorithm/constraint/compiler_solved.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs index a7ad30f7..a31f829f 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs @@ -12,12 +12,9 @@ use rustc_hash::FxHashSet; use smol_str::SmolStr; use super::MatchInstance; -use crate::algorithm::constraint; -use crate::algorithm::derive; -use crate::algorithm::kind; use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::substitute; +use crate::algorithm::{constraint, derive, kind, substitute}; use crate::core::{Role, RowField, RowType}; use crate::{ExternalQueries, Type, TypeId}; From a04fcdceeb3386d2841474096828b325cf9c4f20 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 07:13:28 +0800 Subject: [PATCH 53/62] Reduce super:: usage --- .../algorithm/constraint/compiler_solved.rs | 25 ++++++++++--------- .../src/algorithm/derive/contravariant.rs | 6 ++--- .../checking/src/algorithm/derive/eq1.rs | 4 +-- .../checking/src/algorithm/derive/foldable.rs | 6 ++--- .../checking/src/algorithm/derive/functor.rs | 6 ++--- .../checking/src/algorithm/derive/newtype.rs | 2 +- .../src/algorithm/derive/traversable.rs | 6 ++--- .../checking/src/algorithm/derive/variance.rs | 4 +-- 8 files changed, 30 insertions(+), 29 deletions(-) diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs index a31f829f..97f39c77 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs @@ -11,10 +11,11 @@ use petgraph::graphmap::DiGraphMap; use rustc_hash::FxHashSet; use smol_str::SmolStr; -use super::MatchInstance; +use crate::algorithm::constraint::{self, MatchInstance}; use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::{constraint, derive, kind, substitute}; +use crate::algorithm::{derive, kind, substitute}; + use crate::core::{Role, RowField, RowType}; use crate::{ExternalQueries, Type, TypeId}; @@ -44,21 +45,21 @@ pub fn prim_int_add(state: &mut CheckState, arguments: &[TypeId]) -> Option { let result = state.storage.intern(Type::Integer(left + right)); - if super::can_unify(state, sum, result).is_apart() { + if constraint::can_unify(state, sum, result).is_apart() { return Some(MatchInstance::Apart); } Some(MatchInstance::Match { constraints: vec![], equalities: vec![(sum, result)] }) } (Some(left), _, Some(sum)) => { let result = state.storage.intern(Type::Integer(sum - left)); - if super::can_unify(state, right, result).is_apart() { + if constraint::can_unify(state, right, result).is_apart() { return Some(MatchInstance::Apart); } Some(MatchInstance::Match { constraints: vec![], equalities: vec![(right, result)] }) } (_, Some(right), Some(sum)) => { let result = state.storage.intern(Type::Integer(sum - right)); - if super::can_unify(state, left, result).is_apart() { + if constraint::can_unify(state, left, result).is_apart() { return Some(MatchInstance::Apart); } Some(MatchInstance::Match { constraints: vec![], equalities: vec![(left, result)] }) @@ -86,7 +87,7 @@ pub fn prim_int_mul(state: &mut CheckState, arguments: &[TypeId]) -> Option context.prim_ordering.gt, }; - if super::can_unify(state, ordering, result).is_apart() { + if constraint::can_unify(state, ordering, result).is_apart() { return Some(MatchInstance::Apart); } @@ -199,7 +200,7 @@ where (false, false) => return Some(MatchInstance::Stuck), }; - if super::can_unify(state, ordering, result).is_apart() { + if constraint::can_unify(state, ordering, result).is_apart() { return Some(MatchInstance::Apart); } @@ -221,7 +222,7 @@ pub fn prim_int_to_string(state: &mut CheckState, arguments: &[TypeId]) -> Optio let value: SmolStr = value.to_string().into(); let result = state.storage.intern(Type::String(StringKind::String, value)); - if super::can_unify(state, symbol, result).is_apart() { + if constraint::can_unify(state, symbol, result).is_apart() { return Some(MatchInstance::Apart); } @@ -392,7 +393,7 @@ fn merge_row_fields( EitherOrBoth::Both(left, right) => { let left_type = state.normalize_type(left.id); let right_type = state.normalize_type(right.id); - if super::can_unify(state, left_type, right_type).is_apart() { + if constraint::can_unify(state, left_type, right_type).is_apart() { return None; } result.push(left.clone()); @@ -423,7 +424,7 @@ fn subtract_row_fields( Ordering::Equal => { let field_ty = state.normalize_type(field.id); let removed_ty = state.normalize_type(remove_field.id); - if super::can_unify(state, field_ty, removed_ty).is_apart() { + if constraint::can_unify(state, field_ty, removed_ty).is_apart() { return None; } equalities.push((field.id, remove_field.id)); @@ -1145,7 +1146,7 @@ fn check_reflectable_match( actual: TypeId, expected: TypeId, ) -> Option { - if super::can_unify(state, actual, expected).is_apart() { + if constraint::can_unify(state, actual, expected).is_apart() { Some(MatchInstance::Apart) } else { Some(MatchInstance::Match { constraints: vec![], equalities: vec![(actual, expected)] }) diff --git a/compiler-core/checking/src/algorithm/derive/contravariant.rs b/compiler-core/checking/src/algorithm/derive/contravariant.rs index 59426a0f..9e21cf17 100644 --- a/compiler-core/checking/src/algorithm/derive/contravariant.rs +++ b/compiler-core/checking/src/algorithm/derive/contravariant.rs @@ -3,8 +3,8 @@ use building_types::QueryResult; use crate::ExternalQueries; -use crate::algorithm::derive::tools; use crate::algorithm::derive::variance::{Variance, VarianceConfig, generate_variance_constraints}; +use crate::algorithm::derive::{self, tools}; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::transfer; use crate::error::ErrorKind; @@ -28,7 +28,7 @@ where return Ok(()); }; - let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let Some((data_file, data_id)) = derive::extract_type_constructor(state, derived_type) else { let global_type = transfer::globalize(state, context, derived_type); state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); return Ok(()); @@ -64,7 +64,7 @@ where return Ok(()); }; - let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let Some((data_file, data_id)) = derive::extract_type_constructor(state, derived_type) else { let global_type = transfer::globalize(state, context, derived_type); state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); return Ok(()); diff --git a/compiler-core/checking/src/algorithm/derive/eq1.rs b/compiler-core/checking/src/algorithm/derive/eq1.rs index 3a486721..9915e73a 100644 --- a/compiler-core/checking/src/algorithm/derive/eq1.rs +++ b/compiler-core/checking/src/algorithm/derive/eq1.rs @@ -15,7 +15,7 @@ use files::FileId; use indexing::TypeItemId; use crate::ExternalQueries; -use crate::algorithm::derive::tools; +use crate::algorithm::derive::{self, tools}; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::transfer; use crate::core::{Type, Variable, debruijn}; @@ -81,7 +81,7 @@ where return Ok(()); }; - if super::extract_type_constructor(state, derived_type).is_none() { + if derive::extract_type_constructor(state, derived_type).is_none() { let global_type = transfer::globalize(state, context, derived_type); state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); return Ok(()); diff --git a/compiler-core/checking/src/algorithm/derive/foldable.rs b/compiler-core/checking/src/algorithm/derive/foldable.rs index ff9005c8..96b061b5 100644 --- a/compiler-core/checking/src/algorithm/derive/foldable.rs +++ b/compiler-core/checking/src/algorithm/derive/foldable.rs @@ -3,8 +3,8 @@ use building_types::QueryResult; use crate::ExternalQueries; -use crate::algorithm::derive::tools; use crate::algorithm::derive::variance::{Variance, VarianceConfig, generate_variance_constraints}; +use crate::algorithm::derive::{self, tools}; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::transfer; use crate::error::ErrorKind; @@ -28,7 +28,7 @@ where return Ok(()); }; - let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let Some((data_file, data_id)) = derive::extract_type_constructor(state, derived_type) else { let global_type = transfer::globalize(state, context, derived_type); state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); return Ok(()); @@ -64,7 +64,7 @@ where return Ok(()); }; - let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let Some((data_file, data_id)) = derive::extract_type_constructor(state, derived_type) else { let global_type = transfer::globalize(state, context, derived_type); state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); return Ok(()); diff --git a/compiler-core/checking/src/algorithm/derive/functor.rs b/compiler-core/checking/src/algorithm/derive/functor.rs index 43cbc7cc..e7466672 100644 --- a/compiler-core/checking/src/algorithm/derive/functor.rs +++ b/compiler-core/checking/src/algorithm/derive/functor.rs @@ -3,8 +3,8 @@ use building_types::QueryResult; use crate::ExternalQueries; -use crate::algorithm::derive::tools; use crate::algorithm::derive::variance::{Variance, VarianceConfig, generate_variance_constraints}; +use crate::algorithm::derive::{self, tools}; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::transfer; use crate::error::ErrorKind; @@ -28,7 +28,7 @@ where return Ok(()); }; - let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let Some((data_file, data_id)) = derive::extract_type_constructor(state, derived_type) else { let global_type = transfer::globalize(state, context, derived_type); state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); return Ok(()); @@ -64,7 +64,7 @@ where return Ok(()); }; - let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let Some((data_file, data_id)) = derive::extract_type_constructor(state, derived_type) else { let global_type = transfer::globalize(state, context, derived_type); state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); return Ok(()); diff --git a/compiler-core/checking/src/algorithm/derive/newtype.rs b/compiler-core/checking/src/algorithm/derive/newtype.rs index 634fa403..3c74bb3a 100644 --- a/compiler-core/checking/src/algorithm/derive/newtype.rs +++ b/compiler-core/checking/src/algorithm/derive/newtype.rs @@ -26,7 +26,7 @@ where return Ok(()); }; - let Some((newtype_file, newtype_id)) = super::extract_type_constructor(state, newtype_type) + let Some((newtype_file, newtype_id)) = derive::extract_type_constructor(state, newtype_type) else { let global_type = transfer::globalize(state, context, newtype_type); state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); diff --git a/compiler-core/checking/src/algorithm/derive/traversable.rs b/compiler-core/checking/src/algorithm/derive/traversable.rs index b59e9d14..e32c4d2c 100644 --- a/compiler-core/checking/src/algorithm/derive/traversable.rs +++ b/compiler-core/checking/src/algorithm/derive/traversable.rs @@ -3,8 +3,8 @@ use building_types::QueryResult; use crate::ExternalQueries; -use crate::algorithm::derive::tools; use crate::algorithm::derive::variance::{Variance, VarianceConfig, generate_variance_constraints}; +use crate::algorithm::derive::{self, tools}; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::transfer; use crate::error::ErrorKind; @@ -28,7 +28,7 @@ where return Ok(()); }; - let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let Some((data_file, data_id)) = derive::extract_type_constructor(state, derived_type) else { let global_type = transfer::globalize(state, context, derived_type); state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); return Ok(()); @@ -64,7 +64,7 @@ where return Ok(()); }; - let Some((data_file, data_id)) = super::extract_type_constructor(state, derived_type) else { + let Some((data_file, data_id)) = derive::extract_type_constructor(state, derived_type) else { let global_type = transfer::globalize(state, context, derived_type); state.insert_error(ErrorKind::CannotDeriveForType { type_id: global_type }); return Ok(()); diff --git a/compiler-core/checking/src/algorithm/derive/variance.rs b/compiler-core/checking/src/algorithm/derive/variance.rs index acce51e9..09a99592 100644 --- a/compiler-core/checking/src/algorithm/derive/variance.rs +++ b/compiler-core/checking/src/algorithm/derive/variance.rs @@ -8,7 +8,7 @@ use files::FileId; use indexing::TypeItemId; use crate::ExternalQueries; -use crate::algorithm::derive::{extract_type_arguments, tools}; +use crate::algorithm::derive::{self, extract_type_arguments, tools}; use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::{substitute, transfer}; @@ -98,7 +98,7 @@ where for constructor_id in constructors { let constructor_type = - super::lookup_local_term_type(state, context, data_file, constructor_id)?; + derive::lookup_local_term_type(state, context, data_file, constructor_id)?; let Some(constructor_type) = constructor_type else { continue; From ffea4474604e472f71029342496e534b7c8f5039 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 16:25:24 +0800 Subject: [PATCH 54/62] Implement compiler-solved Warn and Fail --- .../checking/src/algorithm/constraint.rs | 8 + .../algorithm/constraint/compiler_solved.rs | 184 +++++++++++++++++- compiler-core/checking/src/algorithm/state.rs | 36 ++++ compiler-core/checking/src/error.rs | 6 + compiler-core/checking/src/lib.rs | 1 + .../checking/205_builtin_warn/Main.purs | 54 +++++ .../checking/205_builtin_warn/Main.snap | 59 ++++++ .../checking/206_builtin_fail/Main.purs | 18 ++ .../checking/206_builtin_fail/Main.snap | 31 +++ tests-integration/src/generated/basic.rs | 14 ++ tests-integration/tests/checking/generated.rs | 4 + ...ecking__invalid_type_operator_ternary.snap | 2 +- ...checking__invalid_type_operator_unary.snap | 2 +- 13 files changed, 416 insertions(+), 3 deletions(-) create mode 100644 tests-integration/fixtures/checking/205_builtin_warn/Main.purs create mode 100644 tests-integration/fixtures/checking/205_builtin_warn/Main.snap create mode 100644 tests-integration/fixtures/checking/206_builtin_fail/Main.purs create mode 100644 tests-integration/fixtures/checking/206_builtin_fail/Main.snap diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index cce8a9fe..1d76d2a3 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -869,6 +869,14 @@ where } else { None } + } else if file_id == context.prim_type_error.file_id { + if item_id == context.prim_type_error.warn { + prim_warn(state, context, arguments) + } else if item_id == context.prim_type_error.fail { + prim_fail(state, context, arguments) + } else { + None + } } else if context.known_reflectable.is_symbol == Some((file_id, item_id)) { prim_is_symbol(state, arguments) } else if context.known_reflectable.reflectable == Some((file_id, item_id)) { diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs index 97f39c77..b8c90678 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs @@ -15,8 +15,9 @@ use crate::algorithm::constraint::{self, MatchInstance}; use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::{derive, kind, substitute}; - +use crate::core::pretty; use crate::core::{Role, RowField, RowType}; +use crate::error::ErrorKind; use crate::{ExternalQueries, Type, TypeId}; fn extract_integer(state: &CheckState, id: TypeId) -> Option { @@ -1152,3 +1153,184 @@ fn check_reflectable_match( Some(MatchInstance::Match { constraints: vec![], equalities: vec![(actual, expected)] }) } } + +/// Decomposes a type application into its constructor and arguments. +fn decompose_application( + state: &mut CheckState, + mut type_id: TypeId, +) -> Option<(TypeId, Vec)> { + let mut arguments = vec![]; + + safe_loop! { + type_id = state.normalize_type(type_id); + match state.storage[type_id] { + Type::Application(function, argument) => { + arguments.push(argument); + type_id = function; + } + Type::KindApplication(function, _) => { + type_id = function; + } + _ => break, + } + } + + arguments.reverse(); + Some((type_id, arguments)) +} + +/// Checks if a type is stuck on a unification variable at its head. +fn is_stuck(state: &mut CheckState, type_id: TypeId) -> bool { + let type_id = state.normalize_type(type_id); + matches!(state.storage[type_id], Type::Unification(_)) +} + +/// Extracts a symbol from a type, returning `None` if stuck on a unification variable. +fn extract_symbol_or_stuck(state: &mut CheckState, id: TypeId) -> Option { + let id = state.normalize_type(id); + + if matches!(state.storage[id], Type::Unification(_)) { + return None; + } + + extract_symbol(state, id).map(|s| s.to_string()) +} + +/// Extracts a symbol with its kind from a type, returning `None` if stuck. +fn extract_symbol_with_kind( + state: &mut CheckState, + id: TypeId, +) -> Option<(StringKind, SmolStr)> { + let id = state.normalize_type(id); + + if matches!(state.storage[id], Type::Unification(_)) { + return None; + } + + match &state.storage[id] { + Type::String(kind, value) => Some((*kind, SmolStr::clone(value))), + _ => None, + } +} + +/// Checks if a string is a valid PureScript label. +/// +/// Matches the lexer rules: starts with lowercase letter or `_`, +/// continues with alphanumeric, `_`, or `'`. +fn is_valid_label(s: &str) -> bool { + let mut chars = s.chars(); + match chars.next() { + Some(c) if c.is_lowercase() || c == '_' => {} + _ => return false, + } + chars.all(|c| c.is_alphanumeric() || c == '_' || c == '\'') +} + +/// Renders a label, quoting it if necessary. +/// +/// Preserves the original string kind (regular vs raw) when quoting. +fn render_label(kind: StringKind, s: &str) -> String { + if is_valid_label(s) { + s.to_string() + } else { + match kind { + StringKind::String => format!(r#""{s}""#), + StringKind::RawString => format!(r#""""{s}""""#), + } + } +} + +/// Renders a `Doc` type into a string for custom type error messages. +/// +/// Returns `None` if the doc is stuck on a unification variable. +fn render_doc( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, +) -> Option +where + Q: ExternalQueries, +{ + let type_id = state.normalize_type(type_id); + + if matches!(state.storage[type_id], Type::Unification(_)) { + return None; + } + + let (constructor, arguments) = decompose_application(state, type_id)?; + let prim = &context.prim_type_error; + + if constructor == prim.text { + let &[symbol] = arguments.as_slice() else { return None }; + extract_symbol_or_stuck(state, symbol) + } else if constructor == prim.quote { + let &[t] = arguments.as_slice() else { return None }; + if is_stuck(state, t) { + return None; + } + Some(pretty::print_local(state, context, t)) + } else if constructor == prim.quote_label { + let &[symbol] = arguments.as_slice() else { return None }; + extract_symbol_with_kind(state, symbol).map(|(kind, s)| render_label(kind, &s)) + } else if constructor == prim.beside { + let &[left, right] = arguments.as_slice() else { return None }; + let l = render_doc(state, context, left)?; + let r = render_doc(state, context, right)?; + Some(format!("{}{}", l, r)) + } else if constructor == prim.above { + let &[upper, lower] = arguments.as_slice() else { return None }; + let u = render_doc(state, context, upper)?; + let d = render_doc(state, context, lower)?; + Some(format!("{}\n{}", u, d)) + } else { + None + } +} + +/// Solver for `Prim.TypeError.Warn`. +/// +/// Emits a custom warning message and satisfies the constraint. +pub fn prim_warn( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> Option +where + Q: ExternalQueries, +{ + let &[doc] = arguments else { return None }; + + let Some(message) = render_doc(state, context, doc) else { + return Some(MatchInstance::Stuck); + }; + + let message_id = state.checked.custom_messages.len() as u32; + state.checked.custom_messages.push(message); + state.insert_error(ErrorKind::CustomWarning { message_id }); + + Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) +} + +/// Solver for `Prim.TypeError.Fail`. +/// +/// Emits a custom error message and satisfies the constraint. +pub fn prim_fail( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> Option +where + Q: ExternalQueries, +{ + let &[doc] = arguments else { return None }; + + let Some(message) = render_doc(state, context, doc) else { + return Some(MatchInstance::Stuck); + }; + + let message_id = state.checked.custom_messages.len() as u32; + state.checked.custom_messages.push(message); + state.insert_error(ErrorKind::CustomFailure { message_id }); + + Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) +} diff --git a/compiler-core/checking/src/algorithm/state.rs b/compiler-core/checking/src/algorithm/state.rs index 66c6a1cd..618f9874 100644 --- a/compiler-core/checking/src/algorithm/state.rs +++ b/compiler-core/checking/src/algorithm/state.rs @@ -337,6 +337,7 @@ where pub prim_row: PrimRowCore, pub prim_row_list: PrimRowListCore, pub prim_coerce: PrimCoerceCore, + pub prim_type_error: PrimTypeErrorCore, pub known_types: KnownTypesCore, pub known_reflectable: KnownReflectableCore, pub known_generic: Option, @@ -375,6 +376,7 @@ where let prim_row = PrimRowCore::collect(queries, state)?; let prim_row_list = PrimRowListCore::collect(queries, state)?; let prim_coerce = PrimCoerceCore::collect(queries)?; + let prim_type_error = PrimTypeErrorCore::collect(queries, state)?; let known_types = KnownTypesCore::collect(queries)?; let known_reflectable = KnownReflectableCore::collect(queries, &mut state.storage)?; let known_generic = KnownGeneric::collect(queries, &mut state.storage)?; @@ -392,6 +394,7 @@ where prim_row, prim_row_list, prim_coerce, + prim_type_error, known_types, known_reflectable, known_generic, @@ -671,6 +674,39 @@ impl PrimCoerceCore { } } +pub struct PrimTypeErrorCore { + pub file_id: FileId, + pub warn: TypeItemId, + pub fail: TypeItemId, + pub text: TypeId, + pub quote: TypeId, + pub quote_label: TypeId, + pub beside: TypeId, + pub above: TypeId, +} + +impl PrimTypeErrorCore { + fn collect(queries: &impl ExternalQueries, state: &mut CheckState) -> QueryResult { + let file_id = queries + .module_file("Prim.TypeError") + .unwrap_or_else(|| unreachable!("invariant violated: Prim.TypeError not found")); + + let resolved = queries.resolved(file_id)?; + let mut lookup = PrimLookup::new(&resolved, &mut state.storage, "Prim.TypeError"); + + Ok(PrimTypeErrorCore { + file_id, + warn: lookup.type_item("Warn"), + fail: lookup.type_item("Fail"), + text: lookup.type_constructor("Text"), + quote: lookup.type_constructor("Quote"), + quote_label: lookup.type_constructor("QuoteLabel"), + beside: lookup.type_constructor("Beside"), + above: lookup.type_constructor("Above"), + }) + } +} + fn fetch_known_type( queries: &impl ExternalQueries, m: &str, diff --git a/compiler-core/checking/src/error.rs b/compiler-core/checking/src/error.rs index 44154f2d..6e5178cd 100644 --- a/compiler-core/checking/src/error.rs +++ b/compiler-core/checking/src/error.rs @@ -97,6 +97,12 @@ pub enum ErrorKind { file_id: files::FileId, item_id: indexing::TypeItemId, }, + CustomWarning { + message_id: u32, + }, + CustomFailure { + message_id: u32, + }, } #[derive(Debug, PartialEq, Eq)] diff --git a/compiler-core/checking/src/lib.rs b/compiler-core/checking/src/lib.rs index b672f958..91731e7e 100644 --- a/compiler-core/checking/src/lib.rs +++ b/compiler-core/checking/src/lib.rs @@ -45,6 +45,7 @@ pub struct CheckedModule { pub roles: FxHashMap>, pub errors: Vec, + pub custom_messages: Vec, } impl CheckedModule { diff --git a/tests-integration/fixtures/checking/205_builtin_warn/Main.purs b/tests-integration/fixtures/checking/205_builtin_warn/Main.purs new file mode 100644 index 00000000..d5447949 --- /dev/null +++ b/tests-integration/fixtures/checking/205_builtin_warn/Main.purs @@ -0,0 +1,54 @@ +module Main where + +import Prim.TypeError (class Warn, Text, Quote, QuoteLabel, Beside, Above) + +data Proxy :: forall k. k -> Type +data Proxy a = Proxy + +warnBasic :: forall a. Warn (Text "This function is deprecated") => a -> a +warnBasic x = x + +useWarnBasic :: Int +useWarnBasic = warnBasic 42 + +warnBeside :: forall a. Warn (Beside (Text "Left ") (Text "Right")) => a -> a +warnBeside x = x + +useWarnBeside :: Int +useWarnBeside = warnBeside 42 + +warnAbove :: forall a. Warn (Above (Text "Line 1") (Text "Line 2")) => a -> a +warnAbove x = x + +useWarnAbove :: Int +useWarnAbove = warnAbove 42 + +warnQuote :: forall a. Warn (Beside (Text "Got type: ") (Quote a)) => Proxy a -> Proxy a +warnQuote p = p + +useWarnQuote :: Proxy Int +useWarnQuote = warnQuote Proxy + +warnQuoteLabel :: forall a. Warn (Beside (Text "Label: ") (QuoteLabel "myField")) => a -> a +warnQuoteLabel x = x + +useWarnQuoteLabel :: Int +useWarnQuoteLabel = warnQuoteLabel 42 + +warnQuoteLabelSpaces :: forall a. Warn (Beside (Text "Label: ") (QuoteLabel "h e l l o")) => a -> a +warnQuoteLabelSpaces x = x + +useWarnQuoteLabelSpaces :: Int +useWarnQuoteLabelSpaces = warnQuoteLabelSpaces 42 + +warnQuoteLabelQuote :: forall a. Warn (Beside (Text "Label: ") (QuoteLabel "hel\"lo")) => a -> a +warnQuoteLabelQuote x = x + +useWarnQuoteLabelQuote :: Int +useWarnQuoteLabelQuote = warnQuoteLabelQuote 42 + +warnQuoteLabelRaw :: forall a. Warn (Beside (Text "Label: ") (QuoteLabel """raw\nstring""")) => a -> a +warnQuoteLabelRaw x = x + +useWarnQuoteLabelRaw :: Int +useWarnQuoteLabelRaw = warnQuoteLabelRaw 42 diff --git a/tests-integration/fixtures/checking/205_builtin_warn/Main.snap b/tests-integration/fixtures/checking/205_builtin_warn/Main.snap new file mode 100644 index 00000000..c4c44b38 --- /dev/null +++ b/tests-integration/fixtures/checking/205_builtin_warn/Main.snap @@ -0,0 +1,59 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Proxy :: forall (k :: Type) (a :: k). Proxy @k a +warnBasic :: forall (a :: Type). Warn (Text "This function is deprecated") => a -> a +useWarnBasic :: Int +warnBeside :: forall (a :: Type). Warn (Beside (Text "Left ") (Text "Right")) => a -> a +useWarnBeside :: Int +warnAbove :: forall (a :: Type). Warn (Above (Text "Line 1") (Text "Line 2")) => a -> a +useWarnAbove :: Int +warnQuote :: + forall (t10 :: Type) (a :: t10). + Warn (Beside (Text "Got type: ") (Quote @t10 a)) => Proxy @t10 a -> Proxy @t10 a +useWarnQuote :: Proxy @Type Int +warnQuoteLabel :: + forall (a :: Type). Warn (Beside (Text "Label: ") (QuoteLabel "myField")) => a -> a +useWarnQuoteLabel :: Int +warnQuoteLabelSpaces :: + forall (a :: Type). Warn (Beside (Text "Label: ") (QuoteLabel "h e l l o")) => a -> a +useWarnQuoteLabelSpaces :: Int +warnQuoteLabelQuote :: + forall (a :: Type). Warn (Beside (Text "Label: ") (QuoteLabel "hel\"lo")) => a -> a +useWarnQuoteLabelQuote :: Int +warnQuoteLabelRaw :: + forall (a :: Type). Warn (Beside (Text "Label: ") (QuoteLabel """raw\nstring""")) => a -> a +useWarnQuoteLabelRaw :: Int + +Types +Proxy :: forall (k :: Type). k -> Type + +Data +Proxy + Quantified = :0 + Kind = :1 + + +Roles +Proxy = [Phantom] + +Errors +CustomWarning { .. } at [TermDeclaration(Idx::(2))] + This function is deprecated +CustomWarning { .. } at [TermDeclaration(Idx::(4))] + Left Right +CustomWarning { .. } at [TermDeclaration(Idx::(6))] + Line 1 + Line 2 +CustomWarning { .. } at [TermDeclaration(Idx::(8))] + Got type: Int +CustomWarning { .. } at [TermDeclaration(Idx::(10))] + Label: myField +CustomWarning { .. } at [TermDeclaration(Idx::(12))] + Label: "h e l l o" +CustomWarning { .. } at [TermDeclaration(Idx::(14))] + Label: "hel\"lo" +CustomWarning { .. } at [TermDeclaration(Idx::(16))] + Label: """raw\nstring""" diff --git a/tests-integration/fixtures/checking/206_builtin_fail/Main.purs b/tests-integration/fixtures/checking/206_builtin_fail/Main.purs new file mode 100644 index 00000000..59995c57 --- /dev/null +++ b/tests-integration/fixtures/checking/206_builtin_fail/Main.purs @@ -0,0 +1,18 @@ +module Main where + +import Prim.TypeError (class Fail, Text, Quote, Beside, Above) + +data Proxy :: forall k. k -> Type +data Proxy a = Proxy + +failBasic :: forall a. Fail (Text "This operation is not allowed") => a -> a +failBasic x = x + +useFailBasic :: Int +useFailBasic = failBasic 42 + +failComplex :: forall a. Fail (Above (Text "Error:") (Beside (Text "Type ") (Quote a))) => Proxy a -> Proxy a +failComplex p = p + +useFailComplex :: Proxy String +useFailComplex = failComplex Proxy diff --git a/tests-integration/fixtures/checking/206_builtin_fail/Main.snap b/tests-integration/fixtures/checking/206_builtin_fail/Main.snap new file mode 100644 index 00000000..586f54b9 --- /dev/null +++ b/tests-integration/fixtures/checking/206_builtin_fail/Main.snap @@ -0,0 +1,31 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Proxy :: forall (k :: Type) (a :: k). Proxy @k a +failBasic :: forall (a :: Type). Fail (Text "This operation is not allowed") => a -> a +useFailBasic :: Int +failComplex :: + forall (t8 :: Type) (a :: t8). + Fail (Above (Text "Error:") (Beside (Text "Type ") (Quote @t8 a))) => Proxy @t8 a -> Proxy @t8 a +useFailComplex :: Proxy @Type String + +Types +Proxy :: forall (k :: Type). k -> Type + +Data +Proxy + Quantified = :0 + Kind = :1 + + +Roles +Proxy = [Phantom] + +Errors +CustomFailure { .. } at [TermDeclaration(Idx::(2))] + This operation is not allowed +CustomFailure { .. } at [TermDeclaration(Idx::(4))] + Error: + Type String diff --git a/tests-integration/src/generated/basic.rs b/tests-integration/src/generated/basic.rs index 46ce8c26..6dbfa91c 100644 --- a/tests-integration/src/generated/basic.rs +++ b/tests-integration/src/generated/basic.rs @@ -409,6 +409,20 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { ) .unwrap(); } + CustomWarning { message_id } => { + let message = &checked.custom_messages[message_id as usize]; + writeln!(snapshot, "CustomWarning {{ .. }} at {step:?}").unwrap(); + for line in message.lines() { + writeln!(snapshot, " {line}").unwrap(); + } + } + CustomFailure { message_id } => { + let message = &checked.custom_messages[message_id as usize]; + writeln!(snapshot, "CustomFailure {{ .. }} at {step:?}").unwrap(); + for line in message.lines() { + writeln!(snapshot, " {line}").unwrap(); + } + } _ => { writeln!(snapshot, "{:?} at {step:?}", error.kind).unwrap(); } diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index c32538b1..80117684 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -419,3 +419,7 @@ fn run_test(folder: &str, file: &str) { #[rustfmt::skip] #[test] fn test_203_is_symbol_main() { run_test("203_is_symbol", "Main"); } #[rustfmt::skip] #[test] fn test_204_reflectable_main() { run_test("204_reflectable", "Main"); } + +#[rustfmt::skip] #[test] fn test_205_builtin_warn_main() { run_test("205_builtin_warn", "Main"); } + +#[rustfmt::skip] #[test] fn test_206_builtin_fail_main() { run_test("206_builtin_fail", "Main"); } diff --git a/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap b/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap index 20cd9485..2cfe121b 100644 --- a/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap +++ b/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap @@ -5,7 +5,7 @@ expression: checked.errors [ CheckError { kind: InvalidTypeOperator { - id: Id(42), + id: Id(47), }, step: [], }, diff --git a/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap b/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap index 0c8828ab..6d13e102 100644 --- a/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap +++ b/tests-integration/tests/snapshots/checking__invalid_type_operator_unary.snap @@ -5,7 +5,7 @@ expression: checked.errors [ CheckError { kind: InvalidTypeOperator { - id: Id(30), + id: Id(35), }, step: [], }, From 757a675de360decea9b429a4eb9c14de97a4eb3e Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 16:31:35 +0800 Subject: [PATCH 55/62] Move compiler-solved classes into submodules --- .../algorithm/constraint/compiler_solved.rs | 1344 +---------------- .../constraint/compiler_solved/prim_coerce.rs | 436 ++++++ .../constraint/compiler_solved/prim_int.rs | 212 +++ .../compiler_solved/prim_reflectable.rs | 62 + .../constraint/compiler_solved/prim_row.rs | 250 +++ .../compiler_solved/prim_row_list.rs | 75 + .../constraint/compiler_solved/prim_symbol.rs | 155 ++ .../compiler_solved/prim_type_error.rs | 187 +++ 8 files changed, 1396 insertions(+), 1325 deletions(-) create mode 100644 compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs create mode 100644 compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_int.rs create mode 100644 compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_reflectable.rs create mode 100644 compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_row.rs create mode 100644 compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_row_list.rs create mode 100644 compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_symbol.rs create mode 100644 compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_type_error.rs diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs index b8c90678..4fc1ee24 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs @@ -1,1336 +1,30 @@ -use std::cmp::Ordering; -use std::sync::Arc; +mod prim_coerce; +mod prim_int; +mod prim_reflectable; +mod prim_row; +mod prim_row_list; +mod prim_symbol; +mod prim_type_error; + +pub use prim_coerce::*; +pub use prim_int::*; +pub use prim_reflectable::*; +pub use prim_row::*; +pub use prim_row_list::*; +pub use prim_symbol::*; +pub use prim_type_error::*; -use building_types::QueryResult; -use files::FileId; -use indexing::TypeItemId; -use itertools::{EitherOrBoth, izip}; -use lowering::StringKind; -use petgraph::algo::has_path_connecting; -use petgraph::graphmap::DiGraphMap; -use rustc_hash::FxHashSet; use smol_str::SmolStr; -use crate::algorithm::constraint::{self, MatchInstance}; -use crate::algorithm::safety::safe_loop; -use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::{derive, kind, substitute}; -use crate::core::pretty; -use crate::core::{Role, RowField, RowType}; -use crate::error::ErrorKind; -use crate::{ExternalQueries, Type, TypeId}; +use crate::algorithm::state::CheckState; +use crate::{Type, TypeId}; -fn extract_integer(state: &CheckState, id: TypeId) -> Option { +pub(super) fn extract_integer(state: &CheckState, id: TypeId) -> Option { let Type::Integer(value) = state.storage[id] else { return None }; Some(value) } -fn extract_symbol(state: &CheckState, id: TypeId) -> Option { +pub(super) fn extract_symbol(state: &CheckState, id: TypeId) -> Option { let Type::String(_, value) = &state.storage[id] else { return None }; Some(SmolStr::clone(value)) } - -pub fn prim_int_add(state: &mut CheckState, arguments: &[TypeId]) -> Option { - let &[left, right, sum] = arguments else { - return None; - }; - - let left = state.normalize_type(left); - let right = state.normalize_type(right); - let sum = state.normalize_type(sum); - - let left_int = extract_integer(state, left); - let right_int = extract_integer(state, right); - let sum_int = extract_integer(state, sum); - - match (left_int, right_int, sum_int) { - (Some(left), Some(right), _) => { - let result = state.storage.intern(Type::Integer(left + right)); - if constraint::can_unify(state, sum, result).is_apart() { - return Some(MatchInstance::Apart); - } - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(sum, result)] }) - } - (Some(left), _, Some(sum)) => { - let result = state.storage.intern(Type::Integer(sum - left)); - if constraint::can_unify(state, right, result).is_apart() { - return Some(MatchInstance::Apart); - } - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(right, result)] }) - } - (_, Some(right), Some(sum)) => { - let result = state.storage.intern(Type::Integer(sum - right)); - if constraint::can_unify(state, left, result).is_apart() { - return Some(MatchInstance::Apart); - } - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(left, result)] }) - } - _ => Some(MatchInstance::Stuck), - } -} - -pub fn prim_int_mul(state: &mut CheckState, arguments: &[TypeId]) -> Option { - let &[left, right, product] = arguments else { - return None; - }; - - let left = state.normalize_type(left); - let right = state.normalize_type(right); - let product = state.normalize_type(product); - - let Some(left_int) = extract_integer(state, left) else { - return Some(MatchInstance::Stuck); - }; - - let Some(right_int) = extract_integer(state, right) else { - return Some(MatchInstance::Stuck); - }; - - let result = state.storage.intern(Type::Integer(left_int * right_int)); - - if constraint::can_unify(state, product, result).is_apart() { - return Some(MatchInstance::Apart); - } - - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(product, result)] }) -} - -pub fn prim_int_compare( - state: &mut CheckState, - context: &CheckContext, - arguments: &[TypeId], - given: &[constraint::ConstraintApplication], -) -> Option -where - Q: ExternalQueries, -{ - let &[left, right, ordering] = arguments else { - return None; - }; - - let left = state.normalize_type(left); - let right = state.normalize_type(right); - let ordering = state.normalize_type(ordering); - - let left_int = extract_integer(state, left); - let right_int = extract_integer(state, right); - - if let (Some(left_int), Some(right_int)) = (left_int, right_int) { - let result = match left_int.cmp(&right_int) { - Ordering::Less => context.prim_ordering.lt, - Ordering::Equal => context.prim_ordering.eq, - Ordering::Greater => context.prim_ordering.gt, - }; - - if constraint::can_unify(state, ordering, result).is_apart() { - return Some(MatchInstance::Apart); - } - - return Some(MatchInstance::Match { - constraints: vec![], - equalities: vec![(ordering, result)], - }); - } - - prim_int_compare_transitive(state, context, left, right, ordering, given) -} - -/// Uses a graph-based approach to derive ordering relationships transitively. -/// -/// We build a directed graph where `a -> b` means `a < b`: -/// - `Compare a b LT`: add edge `a -> b` -/// - `Compare a b EQ`: add edges `a -> b` and `b -> a` -/// - `Compare a b GT`: add edge `b -> a` (since `a > b` means `b < a`) -/// -/// Then we compute reachability to determine the ordering: -/// - Path from `left` to `right` only: LT -/// - Path from `right` to `left` only: GT -/// - Paths in both directions: EQ -/// - No path: Unknown/Stuck -fn prim_int_compare_transitive( - state: &mut CheckState, - context: &CheckContext, - left: TypeId, - right: TypeId, - ordering: TypeId, - given: &[constraint::ConstraintApplication], -) -> Option -where - Q: ExternalQueries, -{ - let prim_int = &context.prim_int; - let lt = context.prim_ordering.lt; - let eq = context.prim_ordering.eq; - let gt = context.prim_ordering.gt; - - let mut graph: DiGraphMap = DiGraphMap::new(); - - for constraint in given { - if constraint.file_id != prim_int.file_id || constraint.item_id != prim_int.compare { - continue; - } - - let &[a, b, ordering] = constraint.arguments.as_slice() else { continue }; - - let a = state.normalize_type(a); - let b = state.normalize_type(b); - - let ordering = state.normalize_type(ordering); - - if ordering == lt { - // a < b: add edge a -> b - graph.add_edge(a, b, ()); - } else if ordering == eq { - // a = b: add edges both ways - graph.add_edge(a, b, ()); - graph.add_edge(b, a, ()); - } else if ordering == gt { - // a > b means b < a: add edge b -> a - graph.add_edge(b, a, ()); - } - } - - // Check reachability in both directions - let left_reaches_right = has_path_connecting(&graph, left, right, None); - let right_reaches_left = has_path_connecting(&graph, right, left, None); - - let result = match (left_reaches_right, right_reaches_left) { - (true, true) => eq, - (true, false) => lt, - (false, true) => gt, - (false, false) => return Some(MatchInstance::Stuck), - }; - - if constraint::can_unify(state, ordering, result).is_apart() { - return Some(MatchInstance::Apart); - } - - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(ordering, result)] }) -} - -pub fn prim_int_to_string(state: &mut CheckState, arguments: &[TypeId]) -> Option { - let &[int, symbol] = arguments else { - return None; - }; - - let int = state.normalize_type(int); - let symbol = state.normalize_type(symbol); - - let Some(value) = extract_integer(state, int) else { - return Some(MatchInstance::Stuck); - }; - - let value: SmolStr = value.to_string().into(); - let result = state.storage.intern(Type::String(StringKind::String, value)); - - if constraint::can_unify(state, symbol, result).is_apart() { - return Some(MatchInstance::Apart); - } - - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(symbol, result)] }) -} - -pub fn prim_symbol_append(state: &mut CheckState, arguments: &[TypeId]) -> Option { - let &[left, right, appended] = arguments else { - return None; - }; - - let left = state.normalize_type(left); - let right = state.normalize_type(right); - let appended = state.normalize_type(appended); - - let left_symbol = extract_symbol(state, left); - let right_symbol = extract_symbol(state, right); - let appended_symbol = extract_symbol(state, appended); - - match (left_symbol, right_symbol, appended_symbol) { - (Some(left), Some(right), _) => { - let result: SmolStr = format!("{left}{right}").into(); - let result = state.storage.intern(Type::String(StringKind::String, result)); - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(appended, result)] }) - } - (_, Some(right), Some(appended)) => { - if let Some(left_value) = appended.strip_suffix(right.as_str()) { - let result: SmolStr = left_value.into(); - let result = state.storage.intern(Type::String(StringKind::String, result)); - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(left, result)] }) - } else { - Some(MatchInstance::Apart) - } - } - (Some(left), _, Some(appended)) => { - if let Some(right_value) = appended.strip_prefix(left.as_str()) { - let result: SmolStr = right_value.into(); - let result = state.storage.intern(Type::String(StringKind::String, result)); - Some(MatchInstance::Match { - constraints: vec![], - equalities: vec![(right, result)], - }) - } else { - Some(MatchInstance::Apart) - } - } - _ => Some(MatchInstance::Stuck), - } -} - -pub fn prim_symbol_compare( - state: &mut CheckState, - context: &CheckContext, - arguments: &[TypeId], -) -> Option -where - Q: ExternalQueries, -{ - let &[left, right, ordering] = arguments else { - return None; - }; - - let left = state.normalize_type(left); - let right = state.normalize_type(right); - let ordering = state.normalize_type(ordering); - - let Some(left_symbol) = extract_symbol(state, left) else { - return Some(MatchInstance::Stuck); - }; - - let Some(right_symbol) = extract_symbol(state, right) else { - return Some(MatchInstance::Stuck); - }; - - let result = match left_symbol.cmp(&right_symbol) { - Ordering::Less => context.prim_ordering.lt, - Ordering::Equal => context.prim_ordering.eq, - Ordering::Greater => context.prim_ordering.gt, - }; - - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(ordering, result)] }) -} - -pub fn prim_symbol_cons(state: &mut CheckState, arguments: &[TypeId]) -> Option { - let &[head, tail, symbol] = arguments else { - return None; - }; - - let head = state.normalize_type(head); - let tail = state.normalize_type(tail); - let symbol = state.normalize_type(symbol); - - let head_symbol = extract_symbol(state, head); - let tail_symbol = extract_symbol(state, tail); - let symbol_symbol = extract_symbol(state, symbol); - - match (&head_symbol, &tail_symbol, &symbol_symbol) { - (Some(head), Some(tail), _) => { - let mut chars = head.chars(); - if let (Some(c), None) = (chars.next(), chars.next()) { - let result: SmolStr = format!("{c}{tail}").into(); - let result = state.storage.intern(Type::String(StringKind::String, result)); - Some(MatchInstance::Match { - constraints: vec![], - equalities: vec![(symbol, result)], - }) - } else { - Some(MatchInstance::Apart) - } - } - (_, _, Some(symbol_value)) => { - let mut chars = symbol_value.chars(); - if let Some(c) = chars.next() { - if let Some(head_symbol) = head_symbol { - let mut head_chars = head_symbol.chars(); - if head_chars.next() != Some(c) || head_chars.next().is_some() { - return Some(MatchInstance::Apart); - } - } - - let head_result: SmolStr = c.to_string().into(); - let tail_result: SmolStr = chars.as_str().into(); - let head_result = - state.storage.intern(Type::String(StringKind::String, head_result)); - let tail_result = - state.storage.intern(Type::String(StringKind::String, tail_result)); - Some(MatchInstance::Match { - constraints: vec![], - equalities: vec![(head, head_result), (tail, tail_result)], - }) - } else { - Some(MatchInstance::Apart) - } - } - _ => Some(MatchInstance::Stuck), - } -} - -fn extract_closed_row(state: &CheckState, id: TypeId) -> Option { - let Type::Row(row) = &state.storage[id] else { return None }; - if row.tail.is_some() { - return None; - } - Some(row.clone()) -} - -fn extract_row(state: &CheckState, id: TypeId) -> Option { - let Type::Row(row) = &state.storage[id] else { return None }; - Some(row.clone()) -} - -fn merge_row_fields( - state: &mut CheckState, - left: &[RowField], - right: &[RowField], -) -> Option> { - let left = left.iter(); - let right = right.iter(); - - let merged_by_label = - itertools::merge_join_by(left, right, |left, right| left.label.cmp(&right.label)); - - let mut result = vec![]; - for field in merged_by_label { - match field { - EitherOrBoth::Left(left) => result.push(left.clone()), - EitherOrBoth::Right(right) => result.push(right.clone()), - EitherOrBoth::Both(left, right) => { - let left_type = state.normalize_type(left.id); - let right_type = state.normalize_type(right.id); - if constraint::can_unify(state, left_type, right_type).is_apart() { - return None; - } - result.push(left.clone()); - } - } - } - - Some(result) -} - -type SubtractResult = (Vec, Vec<(TypeId, TypeId)>); - -fn subtract_row_fields( - state: &mut CheckState, - source: &[RowField], - to_remove: &[RowField], -) -> Option { - let mut result = vec![]; - let mut equalities = vec![]; - let mut to_remove_iter = to_remove.iter().peekable(); - - for field in source { - if let Some(remove_field) = to_remove_iter.peek() { - match field.label.cmp(&remove_field.label) { - Ordering::Less => { - result.push(field.clone()); - } - Ordering::Equal => { - let field_ty = state.normalize_type(field.id); - let removed_ty = state.normalize_type(remove_field.id); - if constraint::can_unify(state, field_ty, removed_ty).is_apart() { - return None; - } - equalities.push((field.id, remove_field.id)); - to_remove_iter.next(); - } - Ordering::Greater => { - return None; - } - } - } else { - result.push(field.clone()); - } - } - - if to_remove_iter.next().is_some() { - return None; - } - - Some((result, equalities)) -} - -pub fn prim_row_union(state: &mut CheckState, arguments: &[TypeId]) -> Option { - let &[left, right, union] = arguments else { - return None; - }; - - let left = state.normalize_type(left); - let right = state.normalize_type(right); - let union = state.normalize_type(union); - - let left_row = extract_closed_row(state, left); - let right_row = extract_closed_row(state, right); - let union_row = extract_closed_row(state, union); - - match (left_row, right_row, union_row) { - (Some(left_row), Some(right_row), _) => { - if let Some(merged) = merge_row_fields(state, &left_row.fields, &right_row.fields) { - let result = state.storage.intern(Type::Row(RowType::closed(merged))); - Some(MatchInstance::Match { - constraints: vec![], - equalities: vec![(union, result)], - }) - } else { - Some(MatchInstance::Apart) - } - } - (_, Some(right_row), Some(union_row)) => { - if let Some((remaining, mut equalities)) = - subtract_row_fields(state, &union_row.fields, &right_row.fields) - { - let result = state.storage.intern(Type::Row(RowType::closed(remaining))); - equalities.push((left, result)); - Some(MatchInstance::Match { constraints: vec![], equalities }) - } else { - Some(MatchInstance::Apart) - } - } - (Some(left_row), _, Some(union_row)) => { - if let Some((remaining, mut equalities)) = - subtract_row_fields(state, &union_row.fields, &left_row.fields) - { - let result = state.storage.intern(Type::Row(RowType::closed(remaining))); - equalities.push((right, result)); - Some(MatchInstance::Match { constraints: vec![], equalities }) - } else { - Some(MatchInstance::Apart) - } - } - _ => Some(MatchInstance::Stuck), - } -} - -pub fn prim_row_cons(state: &mut CheckState, arguments: &[TypeId]) -> Option { - let &[label, a, tail, row] = arguments else { - return None; - }; - - let label = state.normalize_type(label); - let a = state.normalize_type(a); - let tail = state.normalize_type(tail); - let row = state.normalize_type(row); - - let label_symbol = extract_symbol(state, label); - let tail_row = extract_closed_row(state, tail); - let row_row = extract_closed_row(state, row); - - match (label_symbol, tail_row, row_row) { - (Some(label_value), Some(tail_row), _) => { - let mut fields = vec![RowField { label: label_value, id: a }]; - fields.extend(tail_row.fields.iter().cloned()); - - let result_row = RowType::from_unsorted(fields, None); - let result = state.storage.intern(Type::Row(result_row)); - - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(row, result)] }) - } - - (Some(label_value), _, Some(row_row)) => { - let mut remaining = vec![]; - let mut found_type = None; - - for field in row_row.fields.iter() { - if field.label == label_value && found_type.is_none() { - found_type = Some(field.id); - } else { - remaining.push(field.clone()); - } - } - - if let Some(field_type) = found_type { - let tail_result = state.storage.intern(Type::Row(RowType::closed(remaining))); - Some(MatchInstance::Match { - constraints: vec![], - equalities: vec![(a, field_type), (tail, tail_result)], - }) - } else { - Some(MatchInstance::Apart) - } - } - _ => Some(MatchInstance::Stuck), - } -} - -pub fn prim_row_lacks(state: &mut CheckState, arguments: &[TypeId]) -> Option { - let &[label, row] = arguments else { - return None; - }; - - let label = state.normalize_type(label); - let row = state.normalize_type(row); - - let Some(label_value) = extract_symbol(state, label) else { - return Some(MatchInstance::Stuck); - }; - - let Some(row_row) = extract_row(state, row) else { - return Some(MatchInstance::Stuck); - }; - - let has_label = row_row.fields.iter().any(|field| field.label == label_value); - - if has_label { - Some(MatchInstance::Apart) - } else if row_row.tail.is_some() { - Some(MatchInstance::Stuck) - } else { - Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) - } -} - -pub fn prim_row_nub(state: &mut CheckState, arguments: &[TypeId]) -> Option { - let &[original, nubbed] = arguments else { - return None; - }; - - let original = state.normalize_type(original); - let nubbed = state.normalize_type(nubbed); - - let Some(original_row) = extract_closed_row(state, original) else { - return Some(MatchInstance::Stuck); - }; - - let mut seen = FxHashSet::default(); - let mut fields = vec![]; - - for field in original_row.fields.iter() { - if seen.insert(field.label.clone()) { - fields.push(field.clone()); - } - } - - let result = state.storage.intern(Type::Row(RowType::closed(fields))); - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(nubbed, result)] }) -} - -fn extract_row_element_kind( - state: &mut CheckState, - context: &CheckContext, - type_id: TypeId, -) -> TypeId -where - Q: ExternalQueries, -{ - let type_id = state.normalize_type(type_id); - - if let Type::Row(ref row_type) = state.storage[type_id] - && let Some(field) = row_type.fields.first() - && let Ok(kind) = kind::elaborate_kind(state, context, field.id) - { - return kind; - } - - state.fresh_unification_type(context) -} - -pub fn prim_rowlist_row_to_list( - state: &mut CheckState, - context: &CheckContext, - arguments: &[TypeId], -) -> Option -where - Q: ExternalQueries, -{ - let &[row, list] = arguments else { - return None; - }; - - let row = state.normalize_type(row); - let list = state.normalize_type(list); - - let Some(row_row) = extract_closed_row(state, row) else { - return Some(MatchInstance::Stuck); - }; - - let element_kind = extract_row_element_kind(state, context, row); - - let mut result = - state.storage.intern(Type::KindApplication(context.prim_row_list.nil, element_kind)); - - let cons_kinded = - state.storage.intern(Type::KindApplication(context.prim_row_list.cons, element_kind)); - - for field in row_row.fields.iter().rev() { - let label_type = - state.storage.intern(Type::String(StringKind::String, field.label.clone())); - - let cons_label = state.storage.intern(Type::Application(cons_kinded, label_type)); - let cons_type = state.storage.intern(Type::Application(cons_label, field.id)); - - result = state.storage.intern(Type::Application(cons_type, result)); - } - - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(list, result)] }) -} - -enum NewtypeCoercionResult { - Success(MatchInstance), - ConstructorNotInScope { file_id: FileId, item_id: TypeItemId }, - NotApplicable, -} - -pub fn prim_coercible( - state: &mut CheckState, - context: &CheckContext, - arguments: &[TypeId], -) -> QueryResult> -where - Q: ExternalQueries, -{ - let &[left, right] = arguments else { - return Ok(None); - }; - - let left = state.normalize_type(left); - let right = state.normalize_type(right); - - if left == right { - return Ok(Some(MatchInstance::Match { constraints: vec![], equalities: vec![] })); - } - - if is_unification_head(state, left) || is_unification_head(state, right) { - return Ok(Some(MatchInstance::Stuck)); - } - - let newtype_result = try_newtype_coercion(state, context, left, right)?; - if let NewtypeCoercionResult::Success(result) = newtype_result { - return Ok(Some(result)); - } - - if let Some(result) = try_application_coercion(state, context, left, right)? { - return Ok(Some(result)); - } - - if let Some(result) = try_higher_kinded_coercion(state, context, left, right)? { - return Ok(Some(result)); - } - - if let Some(result) = try_row_coercion(state, context, left, right) { - return Ok(Some(result)); - } - - if let NewtypeCoercionResult::ConstructorNotInScope { file_id, item_id } = newtype_result { - state.insert_error(crate::error::ErrorKind::CoercibleConstructorNotInScope { - file_id, - item_id, - }); - } - - Ok(Some(MatchInstance::Apart)) -} - -fn is_unification_head(state: &mut CheckState, mut type_id: TypeId) -> bool { - loop { - type_id = state.normalize_type(type_id); - match state.storage[type_id] { - Type::Unification(_) => return true, - Type::Application(function, _) | Type::KindApplication(function, _) => { - type_id = function; - } - _ => return false, - } - } -} - -fn try_newtype_coercion( - state: &mut CheckState, - context: &CheckContext, - left: TypeId, - right: TypeId, -) -> QueryResult -where - Q: ExternalQueries, -{ - let mut hidden_newtype: Option<(FileId, TypeItemId)> = None; - - if has_type_kind(state, context, left)? { - if let Some((file_id, type_id)) = derive::extract_type_constructor(state, left) { - if is_newtype(context, file_id, type_id)? { - if is_constructor_in_scope(context, file_id, type_id)? { - let inner = derive::get_newtype_inner(state, context, file_id, type_id, left)?; - let constraint = make_coercible_constraint(state, context, inner, right); - return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { - constraints: vec![constraint], - equalities: vec![], - })); - } else { - hidden_newtype = Some((file_id, type_id)); - } - } - } - } - - if has_type_kind(state, context, right)? { - if let Some((file_id, type_id)) = derive::extract_type_constructor(state, right) { - if is_newtype(context, file_id, type_id)? { - if is_constructor_in_scope(context, file_id, type_id)? { - let inner = derive::get_newtype_inner(state, context, file_id, type_id, right)?; - let constraint = make_coercible_constraint(state, context, left, inner); - return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { - constraints: vec![constraint], - equalities: vec![], - })); - } else if hidden_newtype.is_none() { - hidden_newtype = Some((file_id, type_id)); - } - } - } - } - - if let Some((file_id, item_id)) = hidden_newtype { - return Ok(NewtypeCoercionResult::ConstructorNotInScope { file_id, item_id }); - } - - Ok(NewtypeCoercionResult::NotApplicable) -} - -fn has_type_kind( - state: &mut CheckState, - context: &CheckContext, - type_id: TypeId, -) -> QueryResult -where - Q: ExternalQueries, -{ - let kind = kind::elaborate_kind(state, context, type_id)?; - let kind = state.normalize_type(kind); - Ok(kind == context.prim.t) -} - -fn is_newtype( - context: &CheckContext, - file_id: FileId, - type_id: TypeItemId, -) -> QueryResult -where - Q: ExternalQueries, -{ - let is_newtype = if file_id == context.id { - matches!(context.indexed.items[type_id].kind, indexing::TypeItemKind::Newtype { .. }) - } else { - let indexed = context.queries.indexed(file_id)?; - matches!(indexed.items[type_id].kind, indexing::TypeItemKind::Newtype { .. }) - }; - Ok(is_newtype) -} - -fn is_constructor_in_scope( - context: &CheckContext, - file_id: FileId, - item_id: TypeItemId, -) -> QueryResult -where - Q: ExternalQueries, -{ - let constructor_term_id = if file_id == context.id { - context.indexed.pairs.data_constructors(item_id).next() - } else { - let indexed = context.queries.indexed(file_id)?; - indexed.pairs.data_constructors(item_id).next() - }; - - let Some(constructor_term_id) = constructor_term_id else { - return Ok(false); - }; - - Ok(context.resolved.is_term_in_scope(&context.prim_resolved, file_id, constructor_term_id)) -} - -fn try_application_coercion( - state: &mut CheckState, - context: &CheckContext, - left: TypeId, - right: TypeId, -) -> QueryResult> -where - Q: ExternalQueries, -{ - let Some((left_file, left_id)) = derive::extract_type_constructor(state, left) else { - return Ok(None); - }; - let Some((right_file, right_id)) = derive::extract_type_constructor(state, right) else { - return Ok(None); - }; - - if left_file != right_file || left_id != right_id { - return Ok(None); - } - - let left = extract_type_arguments(state, left); - let right = extract_type_arguments(state, right); - - if left.len() != right.len() { - return Ok(Some(MatchInstance::Apart)); - } - - let Some(roles) = lookup_roles_for_type(state, context, left_file, left_id)? else { - return Ok(Some(MatchInstance::Stuck)); - }; - - debug_assert_eq!(roles.len(), left.len(), "critical failure: mismatched lengths"); - debug_assert_eq!(roles.len(), right.len(), "critical failure: mismatched lengths"); - - let mut constraints = vec![]; - let mut equalities = vec![]; - - for (role, &left, &right) in izip!(&*roles, &left, &right) { - match role { - Role::Phantom => (), - Role::Representational => { - let constraint = make_coercible_constraint(state, context, left, right); - constraints.push(constraint); - } - Role::Nominal => { - if left != right { - if constraint::can_unify(state, left, right).is_apart() { - return Ok(Some(MatchInstance::Apart)); - } - equalities.push((left, right)); - } - } - } - } - - Ok(Some(MatchInstance::Match { constraints, equalities })) -} - -fn lookup_roles_for_type( - state: &CheckState, - context: &CheckContext, - file_id: FileId, - type_id: TypeItemId, -) -> QueryResult>> -where - Q: ExternalQueries, -{ - if file_id == context.id { - Ok(state.checked.lookup_roles(type_id)) - } else { - let checked = context.queries.checked(file_id)?; - Ok(checked.lookup_roles(type_id)) - } -} - -fn extract_type_arguments(state: &mut CheckState, type_id: TypeId) -> Vec { - let mut arguments = vec![]; - let mut current_id = type_id; - - safe_loop! { - current_id = state.normalize_type(current_id); - match state.storage[current_id] { - Type::Application(function, argument) => { - arguments.push(argument); - current_id = function; - } - Type::KindApplication(function, _) => { - current_id = function; - } - _ => break, - } - } - - arguments.reverse(); - arguments -} - -fn try_row_coercion( - state: &mut CheckState, - context: &CheckContext, - left: TypeId, - right: TypeId, -) -> Option -where - Q: ExternalQueries, -{ - let Type::Row(left_row) = &state.storage[left] else { return None }; - let Type::Row(right_row) = &state.storage[right] else { return None }; - - let left_row = left_row.clone(); - let right_row = right_row.clone(); - - if left_row.fields.len() != right_row.fields.len() { - return Some(MatchInstance::Apart); - } - - let mut constraints = vec![]; - - for (left_field, right_field) in izip!(&*left_row.fields, &*right_row.fields) { - if left_field.label != right_field.label { - return Some(MatchInstance::Apart); - } - let constraint = make_coercible_constraint(state, context, left_field.id, right_field.id); - constraints.push(constraint); - } - - match (left_row.tail, right_row.tail) { - (None, None) => (), - (Some(left_tail), Some(right_tail)) => { - let constraint = make_coercible_constraint(state, context, left_tail, right_tail); - constraints.push(constraint); - } - (None, Some(_)) | (Some(_), None) => { - return Some(MatchInstance::Apart); - } - } - - Some(MatchInstance::Match { constraints, equalities: vec![] }) -} - -fn try_higher_kinded_coercion( - state: &mut CheckState, - context: &CheckContext, - left: TypeId, - right: TypeId, -) -> QueryResult> -where - Q: ExternalQueries, -{ - // Let's say we're attempting to coerce the following types: - // - // data Maybe :: forall k. k -> Type -> Type - // data Maybe n a = Just a | Nothing - // - // newtype MaybeAlias :: forall k. k -> Type -> Type - // newtype MaybeAlias n a = MaybeAlias (Maybe n a) - // - // solve[Coercible Maybe MaybeAlias] - // - // In order to solve coercion for higher-kinded types like - // this, we need to be able to solve the following coercion. - // - // solve[Coercible (Maybe ~a) (MaybeAlias ~a)] - // - // To begin, we get the kinds of these types - // - // left_kind := forall k. k -> Type -> Type - // right_kind := forall k. k -> Type -> Type - let left_kind = kind::elaborate_kind(state, context, left)?; - let right_kind = kind::elaborate_kind(state, context, right)?; - - // decompose_kind_for_coercion instantiates the variables into - // skolem variables, then returns the first argument, which in - // this case is the already-skolemized `~k` - // - // left_kind_applied := Maybe @~k - // left_domain := ~k - let Some((left_kind_applied, left_domain)) = - decompose_kind_for_coercion(state, left, left_kind) - else { - return Ok(None); - }; - - // right_kind_applied := MaybeAlias @~k - // right_domain := ~k - let Some((right_kind_applied, right_domain)) = - decompose_kind_for_coercion(state, right, right_kind) - else { - return Ok(None); - }; - - if constraint::can_unify(state, left_domain, right_domain).is_apart() { - return Ok(Some(MatchInstance::Apart)); - } - - // Given left_domain ~ right_domain, create a skolem kinded by `~k` - let argument = state.fresh_skolem_kinded(left_domain); - - // Finally, we can saturated left_kind_applied and right_kind_applied - // - // left := Maybe @~k (~a :: ~k) - // right := MaybeAlias @~k (~a :: ~k) - // - // Finally, we emit `left <~> right` as a constraint and rely on the - // remaining rules to solve this for us, particularly newtype coercion. - let left = state.storage.intern(Type::Application(left_kind_applied, argument)); - let right = state.storage.intern(Type::Application(right_kind_applied, argument)); - let constraint = make_coercible_constraint(state, context, left, right); - - Ok(Some(MatchInstance::Match { constraints: vec![constraint], equalities: vec![] })) -} - -fn decompose_kind_for_coercion( - state: &mut CheckState, - mut type_id: TypeId, - mut kind_id: TypeId, -) -> Option<(TypeId, TypeId)> { - safe_loop! { - kind_id = state.normalize_type(kind_id); - - let forall = match &state.storage[kind_id] { - Type::Forall(binder, inner) => Some((binder.kind, binder.level, *inner)), - Type::Function(domain, _) => return Some((type_id, *domain)), - _ => return None, - }; - - if let Some((binder_kind, binder_level, inner_kind)) = forall { - let fresh_kind = state.fresh_skolem_kinded(binder_kind); - type_id = state.storage.intern(Type::KindApplication(type_id, fresh_kind)); - kind_id = substitute::SubstituteBound::on(state, binder_level, fresh_kind, inner_kind); - } - } -} - -fn make_coercible_constraint( - state: &mut CheckState, - context: &CheckContext, - left: TypeId, - right: TypeId, -) -> TypeId -where - Q: ExternalQueries, -{ - let coerce = &context.prim_coerce; - let coercible = state.storage.intern(Type::Constructor(coerce.file_id, coerce.coercible)); - - let coercible = state.storage.intern(Type::Application(coercible, left)); - state.storage.intern(Type::Application(coercible, right)) -} - -pub fn prim_is_symbol(state: &mut CheckState, arguments: &[TypeId]) -> Option { - let &[symbol] = arguments else { return None }; - let symbol = state.normalize_type(symbol); - - if extract_symbol(state, symbol).is_some() { - Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) - } else if matches!(state.storage[symbol], Type::Unification(_)) { - Some(MatchInstance::Stuck) - } else { - Some(MatchInstance::Apart) - } -} - -pub fn prim_reflectable( - state: &mut CheckState, - context: &CheckContext, - arguments: &[TypeId], -) -> Option -where - Q: ExternalQueries, -{ - let &[v, t] = arguments else { return None }; - - let v = state.normalize_type(v); - let t = state.normalize_type(t); - - if extract_symbol(state, v).is_some() { - let expected = context.prim.string; - return check_reflectable_match(state, t, expected); - } - - if extract_integer(state, v).is_some() { - let expected = context.prim.int; - return check_reflectable_match(state, t, expected); - } - - if v == context.prim_boolean.true_ || v == context.prim_boolean.false_ { - let expected = context.prim.boolean; - return check_reflectable_match(state, t, expected); - } - - if v == context.prim_ordering.lt - || v == context.prim_ordering.eq - || v == context.prim_ordering.gt - { - let Some(expected) = context.known_reflectable.ordering else { - return Some(MatchInstance::Stuck); - }; - return check_reflectable_match(state, t, expected); - } - - if matches!(state.storage[v], Type::Unification(_)) { - return Some(MatchInstance::Stuck); - } - - Some(MatchInstance::Apart) -} - -fn check_reflectable_match( - state: &mut CheckState, - actual: TypeId, - expected: TypeId, -) -> Option { - if constraint::can_unify(state, actual, expected).is_apart() { - Some(MatchInstance::Apart) - } else { - Some(MatchInstance::Match { constraints: vec![], equalities: vec![(actual, expected)] }) - } -} - -/// Decomposes a type application into its constructor and arguments. -fn decompose_application( - state: &mut CheckState, - mut type_id: TypeId, -) -> Option<(TypeId, Vec)> { - let mut arguments = vec![]; - - safe_loop! { - type_id = state.normalize_type(type_id); - match state.storage[type_id] { - Type::Application(function, argument) => { - arguments.push(argument); - type_id = function; - } - Type::KindApplication(function, _) => { - type_id = function; - } - _ => break, - } - } - - arguments.reverse(); - Some((type_id, arguments)) -} - -/// Checks if a type is stuck on a unification variable at its head. -fn is_stuck(state: &mut CheckState, type_id: TypeId) -> bool { - let type_id = state.normalize_type(type_id); - matches!(state.storage[type_id], Type::Unification(_)) -} - -/// Extracts a symbol from a type, returning `None` if stuck on a unification variable. -fn extract_symbol_or_stuck(state: &mut CheckState, id: TypeId) -> Option { - let id = state.normalize_type(id); - - if matches!(state.storage[id], Type::Unification(_)) { - return None; - } - - extract_symbol(state, id).map(|s| s.to_string()) -} - -/// Extracts a symbol with its kind from a type, returning `None` if stuck. -fn extract_symbol_with_kind( - state: &mut CheckState, - id: TypeId, -) -> Option<(StringKind, SmolStr)> { - let id = state.normalize_type(id); - - if matches!(state.storage[id], Type::Unification(_)) { - return None; - } - - match &state.storage[id] { - Type::String(kind, value) => Some((*kind, SmolStr::clone(value))), - _ => None, - } -} - -/// Checks if a string is a valid PureScript label. -/// -/// Matches the lexer rules: starts with lowercase letter or `_`, -/// continues with alphanumeric, `_`, or `'`. -fn is_valid_label(s: &str) -> bool { - let mut chars = s.chars(); - match chars.next() { - Some(c) if c.is_lowercase() || c == '_' => {} - _ => return false, - } - chars.all(|c| c.is_alphanumeric() || c == '_' || c == '\'') -} - -/// Renders a label, quoting it if necessary. -/// -/// Preserves the original string kind (regular vs raw) when quoting. -fn render_label(kind: StringKind, s: &str) -> String { - if is_valid_label(s) { - s.to_string() - } else { - match kind { - StringKind::String => format!(r#""{s}""#), - StringKind::RawString => format!(r#""""{s}""""#), - } - } -} - -/// Renders a `Doc` type into a string for custom type error messages. -/// -/// Returns `None` if the doc is stuck on a unification variable. -fn render_doc( - state: &mut CheckState, - context: &CheckContext, - type_id: TypeId, -) -> Option -where - Q: ExternalQueries, -{ - let type_id = state.normalize_type(type_id); - - if matches!(state.storage[type_id], Type::Unification(_)) { - return None; - } - - let (constructor, arguments) = decompose_application(state, type_id)?; - let prim = &context.prim_type_error; - - if constructor == prim.text { - let &[symbol] = arguments.as_slice() else { return None }; - extract_symbol_or_stuck(state, symbol) - } else if constructor == prim.quote { - let &[t] = arguments.as_slice() else { return None }; - if is_stuck(state, t) { - return None; - } - Some(pretty::print_local(state, context, t)) - } else if constructor == prim.quote_label { - let &[symbol] = arguments.as_slice() else { return None }; - extract_symbol_with_kind(state, symbol).map(|(kind, s)| render_label(kind, &s)) - } else if constructor == prim.beside { - let &[left, right] = arguments.as_slice() else { return None }; - let l = render_doc(state, context, left)?; - let r = render_doc(state, context, right)?; - Some(format!("{}{}", l, r)) - } else if constructor == prim.above { - let &[upper, lower] = arguments.as_slice() else { return None }; - let u = render_doc(state, context, upper)?; - let d = render_doc(state, context, lower)?; - Some(format!("{}\n{}", u, d)) - } else { - None - } -} - -/// Solver for `Prim.TypeError.Warn`. -/// -/// Emits a custom warning message and satisfies the constraint. -pub fn prim_warn( - state: &mut CheckState, - context: &CheckContext, - arguments: &[TypeId], -) -> Option -where - Q: ExternalQueries, -{ - let &[doc] = arguments else { return None }; - - let Some(message) = render_doc(state, context, doc) else { - return Some(MatchInstance::Stuck); - }; - - let message_id = state.checked.custom_messages.len() as u32; - state.checked.custom_messages.push(message); - state.insert_error(ErrorKind::CustomWarning { message_id }); - - Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) -} - -/// Solver for `Prim.TypeError.Fail`. -/// -/// Emits a custom error message and satisfies the constraint. -pub fn prim_fail( - state: &mut CheckState, - context: &CheckContext, - arguments: &[TypeId], -) -> Option -where - Q: ExternalQueries, -{ - let &[doc] = arguments else { return None }; - - let Some(message) = render_doc(state, context, doc) else { - return Some(MatchInstance::Stuck); - }; - - let message_id = state.checked.custom_messages.len() as u32; - state.checked.custom_messages.push(message); - state.insert_error(ErrorKind::CustomFailure { message_id }); - - Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) -} diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs new file mode 100644 index 00000000..9c6229a4 --- /dev/null +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs @@ -0,0 +1,436 @@ +use std::sync::Arc; + +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; +use itertools::izip; + +use crate::algorithm::constraint::{self, MatchInstance}; +use crate::algorithm::safety::safe_loop; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::{derive, kind, substitute}; +use crate::core::Role; +use crate::{ExternalQueries, Type, TypeId}; + +enum NewtypeCoercionResult { + Success(MatchInstance), + ConstructorNotInScope { file_id: FileId, item_id: TypeItemId }, + NotApplicable, +} + +pub fn prim_coercible( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[left, right] = arguments else { + return Ok(None); + }; + + let left = state.normalize_type(left); + let right = state.normalize_type(right); + + if left == right { + return Ok(Some(MatchInstance::Match { constraints: vec![], equalities: vec![] })); + } + + if is_unification_head(state, left) || is_unification_head(state, right) { + return Ok(Some(MatchInstance::Stuck)); + } + + let newtype_result = try_newtype_coercion(state, context, left, right)?; + if let NewtypeCoercionResult::Success(result) = newtype_result { + return Ok(Some(result)); + } + + if let Some(result) = try_application_coercion(state, context, left, right)? { + return Ok(Some(result)); + } + + if let Some(result) = try_higher_kinded_coercion(state, context, left, right)? { + return Ok(Some(result)); + } + + if let Some(result) = try_row_coercion(state, context, left, right) { + return Ok(Some(result)); + } + + if let NewtypeCoercionResult::ConstructorNotInScope { file_id, item_id } = newtype_result { + state.insert_error(crate::error::ErrorKind::CoercibleConstructorNotInScope { + file_id, + item_id, + }); + } + + Ok(Some(MatchInstance::Apart)) +} + +fn is_unification_head(state: &mut CheckState, mut type_id: TypeId) -> bool { + loop { + type_id = state.normalize_type(type_id); + match state.storage[type_id] { + Type::Unification(_) => return true, + Type::Application(function, _) | Type::KindApplication(function, _) => { + type_id = function; + } + _ => return false, + } + } +} + +fn try_newtype_coercion( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut hidden_newtype: Option<(FileId, TypeItemId)> = None; + + if has_type_kind(state, context, left)? { + if let Some((file_id, type_id)) = derive::extract_type_constructor(state, left) { + if is_newtype(context, file_id, type_id)? { + if is_constructor_in_scope(context, file_id, type_id)? { + let inner = derive::get_newtype_inner(state, context, file_id, type_id, left)?; + let constraint = make_coercible_constraint(state, context, inner, right); + return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { + constraints: vec![constraint], + equalities: vec![], + })); + } else { + hidden_newtype = Some((file_id, type_id)); + } + } + } + } + + if has_type_kind(state, context, right)? { + if let Some((file_id, type_id)) = derive::extract_type_constructor(state, right) { + if is_newtype(context, file_id, type_id)? { + if is_constructor_in_scope(context, file_id, type_id)? { + let inner = derive::get_newtype_inner(state, context, file_id, type_id, right)?; + let constraint = make_coercible_constraint(state, context, left, inner); + return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { + constraints: vec![constraint], + equalities: vec![], + })); + } else if hidden_newtype.is_none() { + hidden_newtype = Some((file_id, type_id)); + } + } + } + } + + if let Some((file_id, item_id)) = hidden_newtype { + return Ok(NewtypeCoercionResult::ConstructorNotInScope { file_id, item_id }); + } + + Ok(NewtypeCoercionResult::NotApplicable) +} + +fn has_type_kind( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let kind = kind::elaborate_kind(state, context, type_id)?; + let kind = state.normalize_type(kind); + Ok(kind == context.prim.t) +} + +fn is_newtype( + context: &CheckContext, + file_id: FileId, + type_id: TypeItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let is_newtype = if file_id == context.id { + matches!(context.indexed.items[type_id].kind, indexing::TypeItemKind::Newtype { .. }) + } else { + let indexed = context.queries.indexed(file_id)?; + matches!(indexed.items[type_id].kind, indexing::TypeItemKind::Newtype { .. }) + }; + Ok(is_newtype) +} + +fn is_constructor_in_scope( + context: &CheckContext, + file_id: FileId, + item_id: TypeItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let constructor_term_id = if file_id == context.id { + context.indexed.pairs.data_constructors(item_id).next() + } else { + let indexed = context.queries.indexed(file_id)?; + indexed.pairs.data_constructors(item_id).next() + }; + + let Some(constructor_term_id) = constructor_term_id else { + return Ok(false); + }; + + Ok(context.resolved.is_term_in_scope(&context.prim_resolved, file_id, constructor_term_id)) +} + +fn try_application_coercion( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let Some((left_file, left_id)) = derive::extract_type_constructor(state, left) else { + return Ok(None); + }; + let Some((right_file, right_id)) = derive::extract_type_constructor(state, right) else { + return Ok(None); + }; + + if left_file != right_file || left_id != right_id { + return Ok(None); + } + + let left = extract_type_arguments(state, left); + let right = extract_type_arguments(state, right); + + if left.len() != right.len() { + return Ok(Some(MatchInstance::Apart)); + } + + let Some(roles) = lookup_roles_for_type(state, context, left_file, left_id)? else { + return Ok(Some(MatchInstance::Stuck)); + }; + + debug_assert_eq!(roles.len(), left.len(), "critical failure: mismatched lengths"); + debug_assert_eq!(roles.len(), right.len(), "critical failure: mismatched lengths"); + + let mut constraints = vec![]; + let mut equalities = vec![]; + + for (role, &left, &right) in izip!(&*roles, &left, &right) { + match role { + Role::Phantom => (), + Role::Representational => { + let constraint = make_coercible_constraint(state, context, left, right); + constraints.push(constraint); + } + Role::Nominal => { + if left != right { + if constraint::can_unify(state, left, right).is_apart() { + return Ok(Some(MatchInstance::Apart)); + } + equalities.push((left, right)); + } + } + } + } + + Ok(Some(MatchInstance::Match { constraints, equalities })) +} + +fn lookup_roles_for_type( + state: &CheckState, + context: &CheckContext, + file_id: FileId, + type_id: TypeItemId, +) -> QueryResult>> +where + Q: ExternalQueries, +{ + if file_id == context.id { + Ok(state.checked.lookup_roles(type_id)) + } else { + let checked = context.queries.checked(file_id)?; + Ok(checked.lookup_roles(type_id)) + } +} + +fn extract_type_arguments(state: &mut CheckState, type_id: TypeId) -> Vec { + let mut arguments = vec![]; + let mut current_id = type_id; + + safe_loop! { + current_id = state.normalize_type(current_id); + match state.storage[current_id] { + Type::Application(function, argument) => { + arguments.push(argument); + current_id = function; + } + Type::KindApplication(function, _) => { + current_id = function; + } + _ => break, + } + } + + arguments.reverse(); + arguments +} + +fn try_row_coercion( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> Option +where + Q: ExternalQueries, +{ + let Type::Row(left_row) = &state.storage[left] else { return None }; + let Type::Row(right_row) = &state.storage[right] else { return None }; + + let left_row = left_row.clone(); + let right_row = right_row.clone(); + + if left_row.fields.len() != right_row.fields.len() { + return Some(MatchInstance::Apart); + } + + let mut constraints = vec![]; + + for (left_field, right_field) in izip!(&*left_row.fields, &*right_row.fields) { + if left_field.label != right_field.label { + return Some(MatchInstance::Apart); + } + let constraint = make_coercible_constraint(state, context, left_field.id, right_field.id); + constraints.push(constraint); + } + + match (left_row.tail, right_row.tail) { + (None, None) => (), + (Some(left_tail), Some(right_tail)) => { + let constraint = make_coercible_constraint(state, context, left_tail, right_tail); + constraints.push(constraint); + } + (None, Some(_)) | (Some(_), None) => { + return Some(MatchInstance::Apart); + } + } + + Some(MatchInstance::Match { constraints, equalities: vec![] }) +} + +fn try_higher_kinded_coercion( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + // Let's say we're attempting to coerce the following types: + // + // data Maybe :: forall k. k -> Type -> Type + // data Maybe n a = Just a | Nothing + // + // newtype MaybeAlias :: forall k. k -> Type -> Type + // newtype MaybeAlias n a = MaybeAlias (Maybe n a) + // + // solve[Coercible Maybe MaybeAlias] + // + // In order to solve coercion for higher-kinded types like + // this, we need to be able to solve the following coercion. + // + // solve[Coercible (Maybe ~a) (MaybeAlias ~a)] + // + // To begin, we get the kinds of these types + // + // left_kind := forall k. k -> Type -> Type + // right_kind := forall k. k -> Type -> Type + let left_kind = kind::elaborate_kind(state, context, left)?; + let right_kind = kind::elaborate_kind(state, context, right)?; + + // decompose_kind_for_coercion instantiates the variables into + // skolem variables, then returns the first argument, which in + // this case is the already-skolemized `~k` + // + // left_kind_applied := Maybe @~k + // left_domain := ~k + let Some((left_kind_applied, left_domain)) = + decompose_kind_for_coercion(state, left, left_kind) + else { + return Ok(None); + }; + + // right_kind_applied := MaybeAlias @~k + // right_domain := ~k + let Some((right_kind_applied, right_domain)) = + decompose_kind_for_coercion(state, right, right_kind) + else { + return Ok(None); + }; + + if constraint::can_unify(state, left_domain, right_domain).is_apart() { + return Ok(Some(MatchInstance::Apart)); + } + + // Given left_domain ~ right_domain, create a skolem kinded by `~k` + let argument = state.fresh_skolem_kinded(left_domain); + + // Finally, we can saturated left_kind_applied and right_kind_applied + // + // left := Maybe @~k (~a :: ~k) + // right := MaybeAlias @~k (~a :: ~k) + // + // Finally, we emit `left <~> right` as a constraint and rely on the + // remaining rules to solve this for us, particularly newtype coercion. + let left = state.storage.intern(Type::Application(left_kind_applied, argument)); + let right = state.storage.intern(Type::Application(right_kind_applied, argument)); + let constraint = make_coercible_constraint(state, context, left, right); + + Ok(Some(MatchInstance::Match { constraints: vec![constraint], equalities: vec![] })) +} + +fn decompose_kind_for_coercion( + state: &mut CheckState, + mut type_id: TypeId, + mut kind_id: TypeId, +) -> Option<(TypeId, TypeId)> { + safe_loop! { + kind_id = state.normalize_type(kind_id); + + let forall = match &state.storage[kind_id] { + Type::Forall(binder, inner) => Some((binder.kind, binder.level, *inner)), + Type::Function(domain, _) => return Some((type_id, *domain)), + _ => return None, + }; + + if let Some((binder_kind, binder_level, inner_kind)) = forall { + let fresh_kind = state.fresh_skolem_kinded(binder_kind); + type_id = state.storage.intern(Type::KindApplication(type_id, fresh_kind)); + kind_id = substitute::SubstituteBound::on(state, binder_level, fresh_kind, inner_kind); + } + } +} + +fn make_coercible_constraint( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> TypeId +where + Q: ExternalQueries, +{ + let coerce = &context.prim_coerce; + let coercible = state.storage.intern(Type::Constructor(coerce.file_id, coerce.coercible)); + + let coercible = state.storage.intern(Type::Application(coercible, left)); + state.storage.intern(Type::Application(coercible, right)) +} diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_int.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_int.rs new file mode 100644 index 00000000..7e34da4d --- /dev/null +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_int.rs @@ -0,0 +1,212 @@ +use std::cmp::Ordering; + +use lowering::StringKind; +use petgraph::algo::has_path_connecting; +use petgraph::graphmap::DiGraphMap; +use smol_str::SmolStr; + +use crate::algorithm::constraint::{self, MatchInstance}; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::{ExternalQueries, Type, TypeId}; + +use super::extract_integer; + +pub fn prim_int_add(state: &mut CheckState, arguments: &[TypeId]) -> Option { + let &[left, right, sum] = arguments else { + return None; + }; + + let left = state.normalize_type(left); + let right = state.normalize_type(right); + let sum = state.normalize_type(sum); + + let left_int = extract_integer(state, left); + let right_int = extract_integer(state, right); + let sum_int = extract_integer(state, sum); + + match (left_int, right_int, sum_int) { + (Some(left), Some(right), _) => { + let result = state.storage.intern(Type::Integer(left + right)); + if constraint::can_unify(state, sum, result).is_apart() { + return Some(MatchInstance::Apart); + } + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(sum, result)] }) + } + (Some(left), _, Some(sum)) => { + let result = state.storage.intern(Type::Integer(sum - left)); + if constraint::can_unify(state, right, result).is_apart() { + return Some(MatchInstance::Apart); + } + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(right, result)] }) + } + (_, Some(right), Some(sum)) => { + let result = state.storage.intern(Type::Integer(sum - right)); + if constraint::can_unify(state, left, result).is_apart() { + return Some(MatchInstance::Apart); + } + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(left, result)] }) + } + _ => Some(MatchInstance::Stuck), + } +} + +pub fn prim_int_mul(state: &mut CheckState, arguments: &[TypeId]) -> Option { + let &[left, right, product] = arguments else { + return None; + }; + + let left = state.normalize_type(left); + let right = state.normalize_type(right); + let product = state.normalize_type(product); + + let Some(left_int) = extract_integer(state, left) else { + return Some(MatchInstance::Stuck); + }; + + let Some(right_int) = extract_integer(state, right) else { + return Some(MatchInstance::Stuck); + }; + + let result = state.storage.intern(Type::Integer(left_int * right_int)); + + if constraint::can_unify(state, product, result).is_apart() { + return Some(MatchInstance::Apart); + } + + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(product, result)] }) +} + +pub fn prim_int_compare( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], + given: &[constraint::ConstraintApplication], +) -> Option +where + Q: ExternalQueries, +{ + let &[left, right, ordering] = arguments else { + return None; + }; + + let left = state.normalize_type(left); + let right = state.normalize_type(right); + let ordering = state.normalize_type(ordering); + + let left_int = extract_integer(state, left); + let right_int = extract_integer(state, right); + + if let (Some(left_int), Some(right_int)) = (left_int, right_int) { + let result = match left_int.cmp(&right_int) { + Ordering::Less => context.prim_ordering.lt, + Ordering::Equal => context.prim_ordering.eq, + Ordering::Greater => context.prim_ordering.gt, + }; + + if constraint::can_unify(state, ordering, result).is_apart() { + return Some(MatchInstance::Apart); + } + + return Some(MatchInstance::Match { + constraints: vec![], + equalities: vec![(ordering, result)], + }); + } + + prim_int_compare_transitive(state, context, left, right, ordering, given) +} + +/// Uses a graph-based approach to derive ordering relationships transitively. +/// +/// We build a directed graph where `a -> b` means `a < b`: +/// - `Compare a b LT`: add edge `a -> b` +/// - `Compare a b EQ`: add edges `a -> b` and `b -> a` +/// - `Compare a b GT`: add edge `b -> a` (since `a > b` means `b < a`) +/// +/// Then we compute reachability to determine the ordering: +/// - Path from `left` to `right` only: LT +/// - Path from `right` to `left` only: GT +/// - Paths in both directions: EQ +/// - No path: Unknown/Stuck +fn prim_int_compare_transitive( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, + ordering: TypeId, + given: &[constraint::ConstraintApplication], +) -> Option +where + Q: ExternalQueries, +{ + let prim_int = &context.prim_int; + let lt = context.prim_ordering.lt; + let eq = context.prim_ordering.eq; + let gt = context.prim_ordering.gt; + + let mut graph: DiGraphMap = DiGraphMap::new(); + + for constraint in given { + if constraint.file_id != prim_int.file_id || constraint.item_id != prim_int.compare { + continue; + } + + let &[a, b, ordering] = constraint.arguments.as_slice() else { continue }; + + let a = state.normalize_type(a); + let b = state.normalize_type(b); + + let ordering = state.normalize_type(ordering); + + if ordering == lt { + // a < b: add edge a -> b + graph.add_edge(a, b, ()); + } else if ordering == eq { + // a = b: add edges both ways + graph.add_edge(a, b, ()); + graph.add_edge(b, a, ()); + } else if ordering == gt { + // a > b means b < a: add edge b -> a + graph.add_edge(b, a, ()); + } + } + + // Check reachability in both directions + let left_reaches_right = has_path_connecting(&graph, left, right, None); + let right_reaches_left = has_path_connecting(&graph, right, left, None); + + let result = match (left_reaches_right, right_reaches_left) { + (true, true) => eq, + (true, false) => lt, + (false, true) => gt, + (false, false) => return Some(MatchInstance::Stuck), + }; + + if constraint::can_unify(state, ordering, result).is_apart() { + return Some(MatchInstance::Apart); + } + + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(ordering, result)] }) +} + +pub fn prim_int_to_string(state: &mut CheckState, arguments: &[TypeId]) -> Option { + let &[int, symbol] = arguments else { + return None; + }; + + let int = state.normalize_type(int); + let symbol = state.normalize_type(symbol); + + let Some(value) = extract_integer(state, int) else { + return Some(MatchInstance::Stuck); + }; + + let value: SmolStr = value.to_string().into(); + let result = state.storage.intern(Type::String(StringKind::String, value)); + + if constraint::can_unify(state, symbol, result).is_apart() { + return Some(MatchInstance::Apart); + } + + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(symbol, result)] }) +} diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_reflectable.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_reflectable.rs new file mode 100644 index 00000000..d36008af --- /dev/null +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_reflectable.rs @@ -0,0 +1,62 @@ +use crate::algorithm::constraint::{self, MatchInstance}; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::{ExternalQueries, Type, TypeId}; + +use super::{extract_integer, extract_symbol}; + +pub fn prim_reflectable( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> Option +where + Q: ExternalQueries, +{ + let &[v, t] = arguments else { return None }; + + let v = state.normalize_type(v); + let t = state.normalize_type(t); + + if extract_symbol(state, v).is_some() { + let expected = context.prim.string; + return check_reflectable_match(state, t, expected); + } + + if extract_integer(state, v).is_some() { + let expected = context.prim.int; + return check_reflectable_match(state, t, expected); + } + + if v == context.prim_boolean.true_ || v == context.prim_boolean.false_ { + let expected = context.prim.boolean; + return check_reflectable_match(state, t, expected); + } + + if v == context.prim_ordering.lt + || v == context.prim_ordering.eq + || v == context.prim_ordering.gt + { + let Some(expected) = context.known_reflectable.ordering else { + return Some(MatchInstance::Stuck); + }; + return check_reflectable_match(state, t, expected); + } + + if matches!(state.storage[v], Type::Unification(_)) { + return Some(MatchInstance::Stuck); + } + + Some(MatchInstance::Apart) +} + +fn check_reflectable_match( + state: &mut CheckState, + actual: TypeId, + expected: TypeId, +) -> Option { + if constraint::can_unify(state, actual, expected).is_apart() { + Some(MatchInstance::Apart) + } else { + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(actual, expected)] }) + } +} diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_row.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_row.rs new file mode 100644 index 00000000..7e3d15fe --- /dev/null +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_row.rs @@ -0,0 +1,250 @@ +use std::cmp::Ordering; + +use itertools::EitherOrBoth; +use rustc_hash::FxHashSet; + +use crate::algorithm::constraint::{self, MatchInstance}; +use crate::algorithm::state::CheckState; +use crate::core::{RowField, RowType}; +use crate::{Type, TypeId}; + +use super::extract_symbol; + +fn extract_closed_row(state: &CheckState, id: TypeId) -> Option { + let Type::Row(row) = &state.storage[id] else { return None }; + if row.tail.is_some() { + return None; + } + Some(row.clone()) +} + +fn extract_row(state: &CheckState, id: TypeId) -> Option { + let Type::Row(row) = &state.storage[id] else { return None }; + Some(row.clone()) +} + +fn merge_row_fields( + state: &mut CheckState, + left: &[RowField], + right: &[RowField], +) -> Option> { + let left = left.iter(); + let right = right.iter(); + + let merged_by_label = + itertools::merge_join_by(left, right, |left, right| left.label.cmp(&right.label)); + + let mut result = vec![]; + for field in merged_by_label { + match field { + EitherOrBoth::Left(left) => result.push(left.clone()), + EitherOrBoth::Right(right) => result.push(right.clone()), + EitherOrBoth::Both(left, right) => { + let left_type = state.normalize_type(left.id); + let right_type = state.normalize_type(right.id); + if constraint::can_unify(state, left_type, right_type).is_apart() { + return None; + } + result.push(left.clone()); + } + } + } + + Some(result) +} + +type SubtractResult = (Vec, Vec<(TypeId, TypeId)>); + +fn subtract_row_fields( + state: &mut CheckState, + source: &[RowField], + to_remove: &[RowField], +) -> Option { + let mut result = vec![]; + let mut equalities = vec![]; + let mut to_remove_iter = to_remove.iter().peekable(); + + for field in source { + if let Some(remove_field) = to_remove_iter.peek() { + match field.label.cmp(&remove_field.label) { + Ordering::Less => { + result.push(field.clone()); + } + Ordering::Equal => { + let field_ty = state.normalize_type(field.id); + let removed_ty = state.normalize_type(remove_field.id); + if constraint::can_unify(state, field_ty, removed_ty).is_apart() { + return None; + } + equalities.push((field.id, remove_field.id)); + to_remove_iter.next(); + } + Ordering::Greater => { + return None; + } + } + } else { + result.push(field.clone()); + } + } + + if to_remove_iter.next().is_some() { + return None; + } + + Some((result, equalities)) +} + +pub fn prim_row_union(state: &mut CheckState, arguments: &[TypeId]) -> Option { + let &[left, right, union] = arguments else { + return None; + }; + + let left = state.normalize_type(left); + let right = state.normalize_type(right); + let union = state.normalize_type(union); + + let left_row = extract_closed_row(state, left); + let right_row = extract_closed_row(state, right); + let union_row = extract_closed_row(state, union); + + match (left_row, right_row, union_row) { + (Some(left_row), Some(right_row), _) => { + if let Some(merged) = merge_row_fields(state, &left_row.fields, &right_row.fields) { + let result = state.storage.intern(Type::Row(RowType::closed(merged))); + Some(MatchInstance::Match { + constraints: vec![], + equalities: vec![(union, result)], + }) + } else { + Some(MatchInstance::Apart) + } + } + (_, Some(right_row), Some(union_row)) => { + if let Some((remaining, mut equalities)) = + subtract_row_fields(state, &union_row.fields, &right_row.fields) + { + let result = state.storage.intern(Type::Row(RowType::closed(remaining))); + equalities.push((left, result)); + Some(MatchInstance::Match { constraints: vec![], equalities }) + } else { + Some(MatchInstance::Apart) + } + } + (Some(left_row), _, Some(union_row)) => { + if let Some((remaining, mut equalities)) = + subtract_row_fields(state, &union_row.fields, &left_row.fields) + { + let result = state.storage.intern(Type::Row(RowType::closed(remaining))); + equalities.push((right, result)); + Some(MatchInstance::Match { constraints: vec![], equalities }) + } else { + Some(MatchInstance::Apart) + } + } + _ => Some(MatchInstance::Stuck), + } +} + +pub fn prim_row_cons(state: &mut CheckState, arguments: &[TypeId]) -> Option { + let &[label, a, tail, row] = arguments else { + return None; + }; + + let label = state.normalize_type(label); + let a = state.normalize_type(a); + let tail = state.normalize_type(tail); + let row = state.normalize_type(row); + + let label_symbol = extract_symbol(state, label); + let tail_row = extract_closed_row(state, tail); + let row_row = extract_closed_row(state, row); + + match (label_symbol, tail_row, row_row) { + (Some(label_value), Some(tail_row), _) => { + let mut fields = vec![RowField { label: label_value, id: a }]; + fields.extend(tail_row.fields.iter().cloned()); + + let result_row = RowType::from_unsorted(fields, None); + let result = state.storage.intern(Type::Row(result_row)); + + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(row, result)] }) + } + + (Some(label_value), _, Some(row_row)) => { + let mut remaining = vec![]; + let mut found_type = None; + + for field in row_row.fields.iter() { + if field.label == label_value && found_type.is_none() { + found_type = Some(field.id); + } else { + remaining.push(field.clone()); + } + } + + if let Some(field_type) = found_type { + let tail_result = state.storage.intern(Type::Row(RowType::closed(remaining))); + Some(MatchInstance::Match { + constraints: vec![], + equalities: vec![(a, field_type), (tail, tail_result)], + }) + } else { + Some(MatchInstance::Apart) + } + } + _ => Some(MatchInstance::Stuck), + } +} + +pub fn prim_row_lacks(state: &mut CheckState, arguments: &[TypeId]) -> Option { + let &[label, row] = arguments else { + return None; + }; + + let label = state.normalize_type(label); + let row = state.normalize_type(row); + + let Some(label_value) = extract_symbol(state, label) else { + return Some(MatchInstance::Stuck); + }; + + let Some(row_row) = extract_row(state, row) else { + return Some(MatchInstance::Stuck); + }; + + let has_label = row_row.fields.iter().any(|field| field.label == label_value); + + if has_label { + Some(MatchInstance::Apart) + } else if row_row.tail.is_some() { + Some(MatchInstance::Stuck) + } else { + Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) + } +} + +pub fn prim_row_nub(state: &mut CheckState, arguments: &[TypeId]) -> Option { + let &[original, nubbed] = arguments else { + return None; + }; + + let original = state.normalize_type(original); + let nubbed = state.normalize_type(nubbed); + + let Some(original_row) = extract_closed_row(state, original) else { + return Some(MatchInstance::Stuck); + }; + + let mut seen = FxHashSet::default(); + let mut fields = vec![]; + + for field in original_row.fields.iter() { + if seen.insert(field.label.clone()) { + fields.push(field.clone()); + } + } + + let result = state.storage.intern(Type::Row(RowType::closed(fields))); + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(nubbed, result)] }) +} diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_row_list.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_row_list.rs new file mode 100644 index 00000000..530b85eb --- /dev/null +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_row_list.rs @@ -0,0 +1,75 @@ +use lowering::StringKind; + +use crate::algorithm::constraint::MatchInstance; +use crate::algorithm::kind; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::core::RowType; +use crate::{ExternalQueries, Type, TypeId}; + +fn extract_closed_row(state: &CheckState, id: TypeId) -> Option { + let Type::Row(row) = &state.storage[id] else { return None }; + if row.tail.is_some() { + return None; + } + Some(row.clone()) +} + +fn extract_row_element_kind( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, +) -> TypeId +where + Q: ExternalQueries, +{ + let type_id = state.normalize_type(type_id); + + if let Type::Row(ref row_type) = state.storage[type_id] + && let Some(field) = row_type.fields.first() + && let Ok(kind) = kind::elaborate_kind(state, context, field.id) + { + return kind; + } + + state.fresh_unification_type(context) +} + +pub fn prim_rowlist_row_to_list( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> Option +where + Q: ExternalQueries, +{ + let &[row, list] = arguments else { + return None; + }; + + let row = state.normalize_type(row); + let list = state.normalize_type(list); + + let Some(row_row) = extract_closed_row(state, row) else { + return Some(MatchInstance::Stuck); + }; + + let element_kind = extract_row_element_kind(state, context, row); + + let mut result = + state.storage.intern(Type::KindApplication(context.prim_row_list.nil, element_kind)); + + let cons_kinded = + state.storage.intern(Type::KindApplication(context.prim_row_list.cons, element_kind)); + + for field in row_row.fields.iter().rev() { + let label_type = + state.storage.intern(Type::String(StringKind::String, field.label.clone())); + + let cons_label = state.storage.intern(Type::Application(cons_kinded, label_type)); + let cons_type = state.storage.intern(Type::Application(cons_label, field.id)); + + result = state.storage.intern(Type::Application(cons_type, result)); + } + + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(list, result)] }) +} diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_symbol.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_symbol.rs new file mode 100644 index 00000000..5555d4d7 --- /dev/null +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_symbol.rs @@ -0,0 +1,155 @@ +use std::cmp::Ordering; + +use lowering::StringKind; +use smol_str::SmolStr; + +use crate::algorithm::constraint::MatchInstance; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::{ExternalQueries, Type, TypeId}; + +use super::extract_symbol; + +pub fn prim_symbol_append(state: &mut CheckState, arguments: &[TypeId]) -> Option { + let &[left, right, appended] = arguments else { + return None; + }; + + let left = state.normalize_type(left); + let right = state.normalize_type(right); + let appended = state.normalize_type(appended); + + let left_symbol = extract_symbol(state, left); + let right_symbol = extract_symbol(state, right); + let appended_symbol = extract_symbol(state, appended); + + match (left_symbol, right_symbol, appended_symbol) { + (Some(left), Some(right), _) => { + let result: SmolStr = format!("{left}{right}").into(); + let result = state.storage.intern(Type::String(StringKind::String, result)); + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(appended, result)] }) + } + (_, Some(right), Some(appended)) => { + if let Some(left_value) = appended.strip_suffix(right.as_str()) { + let result: SmolStr = left_value.into(); + let result = state.storage.intern(Type::String(StringKind::String, result)); + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(left, result)] }) + } else { + Some(MatchInstance::Apart) + } + } + (Some(left), _, Some(appended)) => { + if let Some(right_value) = appended.strip_prefix(left.as_str()) { + let result: SmolStr = right_value.into(); + let result = state.storage.intern(Type::String(StringKind::String, result)); + Some(MatchInstance::Match { + constraints: vec![], + equalities: vec![(right, result)], + }) + } else { + Some(MatchInstance::Apart) + } + } + _ => Some(MatchInstance::Stuck), + } +} + +pub fn prim_symbol_compare( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> Option +where + Q: ExternalQueries, +{ + let &[left, right, ordering] = arguments else { + return None; + }; + + let left = state.normalize_type(left); + let right = state.normalize_type(right); + let ordering = state.normalize_type(ordering); + + let Some(left_symbol) = extract_symbol(state, left) else { + return Some(MatchInstance::Stuck); + }; + + let Some(right_symbol) = extract_symbol(state, right) else { + return Some(MatchInstance::Stuck); + }; + + let result = match left_symbol.cmp(&right_symbol) { + Ordering::Less => context.prim_ordering.lt, + Ordering::Equal => context.prim_ordering.eq, + Ordering::Greater => context.prim_ordering.gt, + }; + + Some(MatchInstance::Match { constraints: vec![], equalities: vec![(ordering, result)] }) +} + +pub fn prim_symbol_cons(state: &mut CheckState, arguments: &[TypeId]) -> Option { + let &[head, tail, symbol] = arguments else { + return None; + }; + + let head = state.normalize_type(head); + let tail = state.normalize_type(tail); + let symbol = state.normalize_type(symbol); + + let head_symbol = extract_symbol(state, head); + let tail_symbol = extract_symbol(state, tail); + let symbol_symbol = extract_symbol(state, symbol); + + match (&head_symbol, &tail_symbol, &symbol_symbol) { + (Some(head), Some(tail), _) => { + let mut chars = head.chars(); + if let (Some(c), None) = (chars.next(), chars.next()) { + let result: SmolStr = format!("{c}{tail}").into(); + let result = state.storage.intern(Type::String(StringKind::String, result)); + Some(MatchInstance::Match { + constraints: vec![], + equalities: vec![(symbol, result)], + }) + } else { + Some(MatchInstance::Apart) + } + } + (_, _, Some(symbol_value)) => { + let mut chars = symbol_value.chars(); + if let Some(c) = chars.next() { + if let Some(head_symbol) = head_symbol { + let mut head_chars = head_symbol.chars(); + if head_chars.next() != Some(c) || head_chars.next().is_some() { + return Some(MatchInstance::Apart); + } + } + + let head_result: SmolStr = c.to_string().into(); + let tail_result: SmolStr = chars.as_str().into(); + let head_result = + state.storage.intern(Type::String(StringKind::String, head_result)); + let tail_result = + state.storage.intern(Type::String(StringKind::String, tail_result)); + Some(MatchInstance::Match { + constraints: vec![], + equalities: vec![(head, head_result), (tail, tail_result)], + }) + } else { + Some(MatchInstance::Apart) + } + } + _ => Some(MatchInstance::Stuck), + } +} + +pub fn prim_is_symbol(state: &mut CheckState, arguments: &[TypeId]) -> Option { + let &[symbol] = arguments else { return None }; + let symbol = state.normalize_type(symbol); + + if extract_symbol(state, symbol).is_some() { + Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) + } else if matches!(state.storage[symbol], Type::Unification(_)) { + Some(MatchInstance::Stuck) + } else { + Some(MatchInstance::Apart) + } +} diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_type_error.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_type_error.rs new file mode 100644 index 00000000..a58ef074 --- /dev/null +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_type_error.rs @@ -0,0 +1,187 @@ +use lowering::StringKind; +use smol_str::SmolStr; + +use crate::algorithm::constraint::MatchInstance; +use crate::algorithm::safety::safe_loop; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::core::pretty; +use crate::error::ErrorKind; +use crate::{ExternalQueries, Type, TypeId}; + +/// Decomposes a type application into its constructor and arguments. +fn decompose_application( + state: &mut CheckState, + mut type_id: TypeId, +) -> Option<(TypeId, Vec)> { + let mut arguments = vec![]; + + safe_loop! { + type_id = state.normalize_type(type_id); + match state.storage[type_id] { + Type::Application(function, argument) => { + arguments.push(argument); + type_id = function; + } + Type::KindApplication(function, _) => { + type_id = function; + } + _ => break, + } + } + + arguments.reverse(); + Some((type_id, arguments)) +} + +/// Checks if a type is stuck on a unification variable at its head. +fn is_stuck(state: &mut CheckState, type_id: TypeId) -> bool { + let type_id = state.normalize_type(type_id); + matches!(state.storage[type_id], Type::Unification(_)) +} + +/// Extracts a symbol from a type, returning `None` if stuck on a unification variable. +fn extract_symbol_or_stuck(state: &mut CheckState, id: TypeId) -> Option { + let id = state.normalize_type(id); + + if matches!(state.storage[id], Type::Unification(_)) { + return None; + } + + if let Type::String(_, value) = &state.storage[id] { Some(value.to_string()) } else { None } +} + +/// Extracts a symbol with its kind from a type, returning `None` if stuck. +fn extract_symbol_with_kind(state: &mut CheckState, id: TypeId) -> Option<(StringKind, SmolStr)> { + let id = state.normalize_type(id); + + if matches!(state.storage[id], Type::Unification(_)) { + return None; + } + + match &state.storage[id] { + Type::String(kind, value) => Some((*kind, SmolStr::clone(value))), + _ => None, + } +} + +/// Checks if a string is a valid PureScript label. +/// +/// Matches the lexer rules: starts with lowercase letter or `_`, +/// continues with alphanumeric, `_`, or `'`. +fn is_valid_label(s: &str) -> bool { + let mut chars = s.chars(); + match chars.next() { + Some(c) if c.is_lowercase() || c == '_' => {} + _ => return false, + } + chars.all(|c| c.is_alphanumeric() || c == '_' || c == '\'') +} + +/// Renders a label, quoting it if necessary. +/// +/// Preserves the original string kind (regular vs raw) when quoting. +fn render_label(kind: StringKind, s: &str) -> String { + if is_valid_label(s) { + s.to_string() + } else { + match kind { + StringKind::String => format!(r#""{s}""#), + StringKind::RawString => format!(r#""""{s}""""#), + } + } +} + +/// Renders a `Doc` type into a string for custom type error messages. +/// +/// Returns `None` if the doc is stuck on a unification variable. +fn render_doc( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, +) -> Option +where + Q: ExternalQueries, +{ + let type_id = state.normalize_type(type_id); + + if matches!(state.storage[type_id], Type::Unification(_)) { + return None; + } + + let (constructor, arguments) = decompose_application(state, type_id)?; + let prim = &context.prim_type_error; + + if constructor == prim.text { + let &[symbol] = arguments.as_slice() else { return None }; + extract_symbol_or_stuck(state, symbol) + } else if constructor == prim.quote { + let &[t] = arguments.as_slice() else { return None }; + if is_stuck(state, t) { + return None; + } + Some(pretty::print_local(state, context, t)) + } else if constructor == prim.quote_label { + let &[symbol] = arguments.as_slice() else { return None }; + extract_symbol_with_kind(state, symbol).map(|(kind, s)| render_label(kind, &s)) + } else if constructor == prim.beside { + let &[left, right] = arguments.as_slice() else { return None }; + let l = render_doc(state, context, left)?; + let r = render_doc(state, context, right)?; + Some(format!("{}{}", l, r)) + } else if constructor == prim.above { + let &[upper, lower] = arguments.as_slice() else { return None }; + let u = render_doc(state, context, upper)?; + let d = render_doc(state, context, lower)?; + Some(format!("{}\n{}", u, d)) + } else { + None + } +} + +/// Solver for `Prim.TypeError.Warn`. +/// +/// Emits a custom warning message and satisfies the constraint. +pub fn prim_warn( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> Option +where + Q: ExternalQueries, +{ + let &[doc] = arguments else { return None }; + + let Some(message) = render_doc(state, context, doc) else { + return Some(MatchInstance::Stuck); + }; + + let message_id = state.checked.custom_messages.len() as u32; + state.checked.custom_messages.push(message); + state.insert_error(ErrorKind::CustomWarning { message_id }); + + Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) +} + +/// Solver for `Prim.TypeError.Fail`. +/// +/// Emits a custom error message and satisfies the constraint. +pub fn prim_fail( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> Option +where + Q: ExternalQueries, +{ + let &[doc] = arguments else { return None }; + + let Some(message) = render_doc(state, context, doc) else { + return Some(MatchInstance::Stuck); + }; + + let message_id = state.checked.custom_messages.len() as u32; + state.checked.custom_messages.push(message); + state.insert_error(ErrorKind::CustomFailure { message_id }); + + Some(MatchInstance::Match { constraints: vec![], equalities: vec![] }) +} From 3a1e53ddcaf22a114cd23a6b17a5df22bd71f5b5 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 16:58:12 +0800 Subject: [PATCH 56/62] Move shared utilities to toolkit --- compiler-core/checking/src/algorithm.rs | 3 + .../constraint/compiler_solved/prim_coerce.rs | 28 +--- .../compiler_solved/prim_type_error.rs | 29 +--- .../checking/src/algorithm/derive.rs | 78 ++------- .../checking/src/algorithm/derive/variance.rs | 6 +- .../checking/src/algorithm/kind/operator.rs | 4 +- .../checking/src/algorithm/operator.rs | 19 +-- .../checking/src/algorithm/toolkit.rs | 153 ++++++++++++++++++ 8 files changed, 177 insertions(+), 143 deletions(-) create mode 100644 compiler-core/checking/src/algorithm/toolkit.rs diff --git a/compiler-core/checking/src/algorithm.rs b/compiler-core/checking/src/algorithm.rs index c23f6330..351784b4 100644 --- a/compiler-core/checking/src/algorithm.rs +++ b/compiler-core/checking/src/algorithm.rs @@ -45,6 +45,9 @@ pub mod substitute; /// Implements type inference and checking for [`lowering::ExpressionKind`]. pub mod term; +/// Shared utilities for common type manipulation patterns. +pub mod toolkit; + /// Implements type inference and checking for [`lowering::TermItemIr`]. pub mod term_item; diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs index 9c6229a4..ba7b67b2 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs @@ -8,7 +8,7 @@ use itertools::izip; use crate::algorithm::constraint::{self, MatchInstance}; use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::{derive, kind, substitute}; +use crate::algorithm::{derive, kind, substitute, toolkit}; use crate::core::Role; use crate::{ExternalQueries, Type, TypeId}; @@ -205,8 +205,8 @@ where return Ok(None); } - let left = extract_type_arguments(state, left); - let right = extract_type_arguments(state, right); + let (_, left) = toolkit::extract_type_application(state, left); + let (_, right) = toolkit::extract_type_application(state, right); if left.len() != right.len() { return Ok(Some(MatchInstance::Apart)); @@ -260,28 +260,6 @@ where } } -fn extract_type_arguments(state: &mut CheckState, type_id: TypeId) -> Vec { - let mut arguments = vec![]; - let mut current_id = type_id; - - safe_loop! { - current_id = state.normalize_type(current_id); - match state.storage[current_id] { - Type::Application(function, argument) => { - arguments.push(argument); - current_id = function; - } - Type::KindApplication(function, _) => { - current_id = function; - } - _ => break, - } - } - - arguments.reverse(); - arguments -} - fn try_row_coercion( state: &mut CheckState, context: &CheckContext, diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_type_error.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_type_error.rs index a58ef074..5e0e8e5b 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_type_error.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_type_error.rs @@ -2,37 +2,12 @@ use lowering::StringKind; use smol_str::SmolStr; use crate::algorithm::constraint::MatchInstance; -use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::toolkit; use crate::core::pretty; use crate::error::ErrorKind; use crate::{ExternalQueries, Type, TypeId}; -/// Decomposes a type application into its constructor and arguments. -fn decompose_application( - state: &mut CheckState, - mut type_id: TypeId, -) -> Option<(TypeId, Vec)> { - let mut arguments = vec![]; - - safe_loop! { - type_id = state.normalize_type(type_id); - match state.storage[type_id] { - Type::Application(function, argument) => { - arguments.push(argument); - type_id = function; - } - Type::KindApplication(function, _) => { - type_id = function; - } - _ => break, - } - } - - arguments.reverse(); - Some((type_id, arguments)) -} - /// Checks if a type is stuck on a unification variable at its head. fn is_stuck(state: &mut CheckState, type_id: TypeId) -> bool { let type_id = state.normalize_type(type_id); @@ -108,7 +83,7 @@ where return None; } - let (constructor, arguments) = decompose_application(state, type_id)?; + let (constructor, arguments) = toolkit::extract_type_application(state, type_id); let prim = &context.prim_type_error; if constructor == prim.text { diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index d9c51d59..fa8f292f 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -18,8 +18,8 @@ use indexing::{DeriveId, TermItemId, TypeItemId}; use crate::ExternalQueries; use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::{kind, substitute, term_item, transfer}; -use crate::core::{Type, TypeId, Variable, debruijn}; +use crate::algorithm::{kind, term_item, toolkit, transfer}; +use crate::core::{Type, TypeId, debruijn}; use crate::error::{ErrorKind, ErrorStep}; /// Input fields for [`check_derive`]. @@ -256,34 +256,6 @@ where Ok(is_newtype) } -/// Extracts type and kind arguments from an application. -/// -/// This function returns both type and kind arguments as constructors -/// have both. These are used to instantiate the constructor type with -/// arguments from the instance head. -pub(crate) fn extract_type_arguments(state: &mut CheckState, applied_type: TypeId) -> Vec { - let mut arguments = vec![]; - let mut current_id = applied_type; - - safe_loop! { - current_id = state.normalize_type(current_id); - match state.storage[current_id] { - Type::Application(function, argument) => { - arguments.push(argument); - current_id = function; - } - Type::KindApplication(function, argument) => { - arguments.push(argument); - current_id = function; - } - _ => break, - } - } - - arguments.reverse(); - arguments -} - pub(crate) fn lookup_local_term_type( state: &mut CheckState, context: &CheckContext, @@ -372,9 +344,10 @@ where /// Extracts constructor fields from a constructor. /// -/// This function uses [`extract_type_arguments`] to deconstruct the instance -/// head, then uses [`substitute::SubstituteBound`] to effectively specialise -/// the constructor type for the instance head in particular. Consider the ff: +/// This function uses [`toolkit::extract_all_applications`] to deconstruct +/// the instance head, then uses [`toolkit::instantiate_with_arguments`] to +/// effectively specialise the constructor type for the instance head in +/// particular. Consider the ff: /// /// ```purescript /// data Either a b = Left a | Right b @@ -393,41 +366,8 @@ fn extract_constructor_fields( constructor_type: TypeId, derived_type: TypeId, ) -> Vec { - let type_arguments = extract_type_arguments(state, derived_type); - let mut arguments_iter = type_arguments.into_iter(); - let mut current_id = constructor_type; - - safe_loop! { - current_id = state.normalize_type(current_id); - match &state.storage[current_id] { - Type::Forall(binder, inner) => { - let binder_level = binder.level; - let binder_kind = binder.kind; - let inner = *inner; - - let argument_type = arguments_iter.next().unwrap_or_else(|| { - let skolem = Variable::Skolem(binder_level, binder_kind); - state.storage.intern(Type::Variable(skolem)) - }); - - current_id = substitute::SubstituteBound::on(state, binder_level, argument_type, inner); - } - _ => break, - } - } - - let mut fields = vec![]; - - safe_loop! { - current_id = state.normalize_type(current_id); - match state.storage[current_id] { - Type::Function(argument, result) => { - fields.push(argument); - current_id = result; - } - _ => break, - } - } - + let arguments = toolkit::extract_all_applications(state, derived_type); + let constructor = toolkit::instantiate_with_arguments(state, constructor_type, arguments); + let (fields, _) = toolkit::extract_function_arguments(state, constructor); fields } diff --git a/compiler-core/checking/src/algorithm/derive/variance.rs b/compiler-core/checking/src/algorithm/derive/variance.rs index 09a99592..f1a73357 100644 --- a/compiler-core/checking/src/algorithm/derive/variance.rs +++ b/compiler-core/checking/src/algorithm/derive/variance.rs @@ -8,10 +8,10 @@ use files::FileId; use indexing::TypeItemId; use crate::ExternalQueries; -use crate::algorithm::derive::{self, extract_type_arguments, tools}; +use crate::algorithm::derive::{self, tools}; use crate::algorithm::safety::safe_loop; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::{substitute, transfer}; +use crate::algorithm::{substitute, toolkit, transfer}; use crate::core::{RowType, Type, TypeId, Variable, debruijn}; use crate::error::ErrorKind; @@ -129,7 +129,7 @@ fn extract_fields_with_skolems( where Q: ExternalQueries, { - let type_arguments = extract_type_arguments(state, derived_type); + let type_arguments = toolkit::extract_all_applications(state, derived_type); let mut arguments_iter = type_arguments.into_iter(); let mut current_id = constructor_type; let mut levels = vec![]; diff --git a/compiler-core/checking/src/algorithm/kind/operator.rs b/compiler-core/checking/src/algorithm/kind/operator.rs index f1f7be59..4b2d2e58 100644 --- a/compiler-core/checking/src/algorithm/kind/operator.rs +++ b/compiler-core/checking/src/algorithm/kind/operator.rs @@ -6,7 +6,7 @@ use indexing::TypeItemId; use crate::ExternalQueries; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::{kind, operator}; +use crate::algorithm::{kind, operator, toolkit}; use crate::core::{Type, TypeId}; pub fn infer_operator_chain_kind( @@ -30,7 +30,7 @@ where Q: ExternalQueries, { let operator_kind = kind::lookup_file_type(state, context, file_id, type_id)?; - let operator_kind = operator::instantiate_forall(state, operator_kind); + let operator_kind = toolkit::instantiate_forall(state, operator_kind); let operator_kind = state.normalize_type(operator_kind); let Type::Function(_, operator_kind) = state.storage[operator_kind] else { diff --git a/compiler-core/checking/src/algorithm/operator.rs b/compiler-core/checking/src/algorithm/operator.rs index fb8f88eb..d68985ea 100644 --- a/compiler-core/checking/src/algorithm/operator.rs +++ b/compiler-core/checking/src/algorithm/operator.rs @@ -7,7 +7,7 @@ use sugar::bracketing::BracketingResult; use crate::ExternalQueries; use crate::algorithm::state::{CheckContext, CheckState}; -use crate::algorithm::{binder, kind, substitute, term, unification}; +use crate::algorithm::{binder, kind, term, toolkit, unification}; use crate::core::{Type, TypeId}; #[derive(Copy, Clone, Debug)] @@ -105,7 +105,7 @@ where OperatorKindMode::Check { expected_type } => (unknown_elaborated, expected_type), }; - let operator_type = instantiate_forall(state, operator_type); + let operator_type = toolkit::instantiate_forall(state, operator_type); let operator_type = state.normalize_type(operator_type); let Type::Function(left_type, operator_type) = state.storage[operator_type] else { @@ -140,21 +140,6 @@ where Ok(E::build(state, context, operator, (left, right), result_type)) } -pub fn instantiate_forall(state: &mut CheckState, mut kind_id: TypeId) -> TypeId { - loop { - kind_id = state.normalize_type(kind_id); - if let Type::Forall(ref binder, inner) = state.storage[kind_id] { - let binder_level = binder.level; - let binder_kind = binder.kind; - - let unification = state.fresh_unification_kinded(binder_kind); - kind_id = substitute::SubstituteBound::on(state, binder_level, unification, inner); - } else { - break kind_id; - } - } -} - pub trait IsOperator: IsElement { type ItemId: Copy; type Elaborated: Copy; diff --git a/compiler-core/checking/src/algorithm/toolkit.rs b/compiler-core/checking/src/algorithm/toolkit.rs new file mode 100644 index 00000000..520fe2b5 --- /dev/null +++ b/compiler-core/checking/src/algorithm/toolkit.rs @@ -0,0 +1,153 @@ +use crate::algorithm::safety::safe_loop; +use crate::algorithm::state::CheckState; +use crate::algorithm::substitute; +use crate::core::{Type, TypeId, Variable}; + +/// Extracts type and kind arguments from a type application. +/// +/// Peels off [`Type::Application`] and [`Type::KindApplication`] layers, +/// collecting both type and kind application arguments. +/// +/// # Example +/// +/// Given `Proxy @Type Int`, returns `[Type, Int]`. +pub fn extract_all_applications(state: &mut CheckState, applied_type: TypeId) -> Vec { + let mut arguments = vec![]; + let mut current_id = applied_type; + + safe_loop! { + current_id = state.normalize_type(current_id); + match state.storage[current_id] { + Type::Application(function, argument) => { + arguments.push(argument); + current_id = function; + } + Type::KindApplication(function, argument) => { + arguments.push(argument); + current_id = function; + } + _ => break, + } + } + + arguments.reverse(); + arguments +} + +/// Decomposes a type application into its head type and arguments. +/// +/// Peels off [`Type::Application`] and [`Type::KindApplication`], +/// collecting type application arguments only. +/// +/// # Example +/// +/// Given `Maybe Int`, returns `(Maybe, [Int])`. +pub fn extract_type_application( + state: &mut CheckState, + mut type_id: TypeId, +) -> (TypeId, Vec) { + let mut arguments = vec![]; + + safe_loop! { + type_id = state.normalize_type(type_id); + match state.storage[type_id] { + Type::Application(function, argument) => { + arguments.push(argument); + type_id = function; + } + Type::KindApplication(function, _) => { + type_id = function; + } + _ => break, + } + } + + arguments.reverse(); + (type_id, arguments) +} + +/// Extracts function arguments and the return type. +/// +/// Peels off `Function` layers, collecting argument types. +/// +/// # Example +/// +/// Given `Int -> String -> Bool`, returns `([Int, String], Bool)`. +pub fn extract_function_arguments( + state: &mut CheckState, + mut type_id: TypeId, +) -> (Vec, TypeId) { + let mut arguments = vec![]; + + safe_loop! { + type_id = state.normalize_type(type_id); + match state.storage[type_id] { + Type::Function(argument, result) => { + arguments.push(argument); + type_id = result; + } + _ => break, + } + } + + (arguments, type_id) +} + +/// Instantiates [`Type::Forall`] with fresh unification variables. +pub fn instantiate_forall(state: &mut CheckState, mut type_id: TypeId) -> TypeId { + safe_loop! { + type_id = state.normalize_type(type_id); + if let Type::Forall(ref binder, inner) = state.storage[type_id] { + let binder_level = binder.level; + let binder_kind = binder.kind; + + let unification = state.fresh_unification_kinded(binder_kind); + type_id = substitute::SubstituteBound::on(state, binder_level, unification, inner); + } else { + break type_id; + } + } +} + +/// Instantiates [`Type::Forall`] with the provided arguments. +/// +/// This function falls back to constructing skolem variables if there's +/// not enough arguments provided. This is primarily used to specialise +/// constructor types based on the [`Type::Application`] and [`Type::KindApplication`] +/// used in an instance head. For example: +/// +/// ```purescript +/// -- Proxy @Type Int +/// Proxy :: forall (k :: Type) (a :: k). Proxy @k a +/// +/// -- instantiate_with_arguments(Proxy, [Type, Int]) +/// Proxy :: Proxy Type Int +/// ``` +pub fn instantiate_with_arguments( + state: &mut CheckState, + mut type_id: TypeId, + arguments: impl IntoIterator, +) -> TypeId { + let mut arguments_iter = arguments.into_iter(); + + safe_loop! { + type_id = state.normalize_type(type_id); + match &state.storage[type_id] { + Type::Forall(binder, inner) => { + let binder_level = binder.level; + let binder_kind = binder.kind; + let inner = *inner; + + let argument_type = arguments_iter.next().unwrap_or_else(|| { + let skolem = Variable::Skolem(binder_level, binder_kind); + state.storage.intern(Type::Variable(skolem)) + }); + + type_id = substitute::SubstituteBound::on(state, binder_level, argument_type, inner); + } + _ => break, + } + } + + type_id +} From 0f179531a70903307caf88adb3b73c0462766de1 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 17:08:26 +0800 Subject: [PATCH 57/62] Reduce duplicated work for instantiate_constructor_fields --- .../checking/src/algorithm/derive.rs | 25 +++++++++++-------- .../checking/src/algorithm/derive/generic.rs | 12 +++++---- .../checking/src/algorithm/toolkit.rs | 4 +-- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index fa8f292f..0de6a0f3 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -299,7 +299,8 @@ where return Ok(context.prim.unknown); }; - let fields = extract_constructor_fields(state, constructor_type, newtype_type); + let arguments = toolkit::extract_all_applications(state, newtype_type); + let fields = instantiate_constructor_fields(state, constructor_type, &arguments); Ok(fields.into_iter().next().unwrap_or(context.prim.unknown)) } @@ -329,11 +330,13 @@ where None }; + let arguments = toolkit::extract_all_applications(state, derived_type); + for constructor_id in constructors { let constructor_type = lookup_local_term_type(state, context, data_file, constructor_id)?; let Some(constructor_type) = constructor_type else { continue }; - let field_types = extract_constructor_fields(state, constructor_type, derived_type); + let field_types = instantiate_constructor_fields(state, constructor_type, &arguments); for field_type in field_types { higher_kinded::generate_constraint(state, context, field_type, class, class1); } @@ -342,12 +345,11 @@ where Ok(()) } -/// Extracts constructor fields from a constructor. +/// Instantiates and extracts constructor fields from a constructor type. /// -/// This function uses [`toolkit::extract_all_applications`] to deconstruct -/// the instance head, then uses [`toolkit::instantiate_with_arguments`] to -/// effectively specialise the constructor type for the instance head in -/// particular. Consider the ff: +/// This function uses [`toolkit::instantiate_with_arguments`] to specialise +/// the constructor type with the given type arguments, then extracts the +/// function arguments. Consider the ff: /// /// ```purescript /// data Either a b = Left a | Right b @@ -361,12 +363,15 @@ where /// derive instance Eq (Proxy @Type Int) /// -- Proxy :: Proxy @Type Int /// ``` -fn extract_constructor_fields( +/// +/// The `arguments` parameter should be obtained by calling +/// [`toolkit::extract_all_applications`] on the derived type once, +/// then passed to this function for each constructor. +fn instantiate_constructor_fields( state: &mut CheckState, constructor_type: TypeId, - derived_type: TypeId, + arguments: &[TypeId], ) -> Vec { - let arguments = toolkit::extract_all_applications(state, derived_type); let constructor = toolkit::instantiate_with_arguments(state, constructor_type, arguments); let (fields, _) = toolkit::extract_function_arguments(state, constructor); fields diff --git a/compiler-core/checking/src/algorithm/derive/generic.rs b/compiler-core/checking/src/algorithm/derive/generic.rs index 68dc6cd3..d5ce8a76 100644 --- a/compiler-core/checking/src/algorithm/derive/generic.rs +++ b/compiler-core/checking/src/algorithm/derive/generic.rs @@ -17,7 +17,7 @@ use smol_str::SmolStr; use crate::ExternalQueries; use crate::algorithm::derive::{self, tools}; use crate::algorithm::state::{CheckContext, CheckState, KnownGeneric}; -use crate::algorithm::{transfer, unification}; +use crate::algorithm::{toolkit, transfer, unification}; use crate::core::{Type, TypeId}; use crate::error::ErrorKind; @@ -82,8 +82,10 @@ where return Ok(known_generic.no_constructors); }; + let arguments = toolkit::extract_all_applications(state, derived_type); + let last = - build_generic_constructor(state, context, known_generic, data_file, derived_type, last)?; + build_generic_constructor(state, context, known_generic, data_file, &arguments, last)?; rest.iter().rev().try_fold(last, |accumulator, &constructor_id| { let constructor = build_generic_constructor( @@ -91,7 +93,7 @@ where context, known_generic, data_file, - derived_type, + &arguments, constructor_id, )?; let applied = state.storage.intern(Type::Application(known_generic.sum, constructor)); @@ -105,7 +107,7 @@ fn build_generic_constructor( context: &CheckContext, known_generic: &KnownGeneric, data_file: FileId, - derived_type: TypeId, + arguments: &[TypeId], constructor_id: TermItemId, ) -> QueryResult where @@ -129,7 +131,7 @@ where derive::lookup_local_term_type(state, context, data_file, constructor_id)?; let field_types = if let Some(constructor_type) = constructor_type { - derive::extract_constructor_fields(state, constructor_type, derived_type) + derive::instantiate_constructor_fields(state, constructor_type, arguments) } else { vec![] }; diff --git a/compiler-core/checking/src/algorithm/toolkit.rs b/compiler-core/checking/src/algorithm/toolkit.rs index 520fe2b5..3b9ca383 100644 --- a/compiler-core/checking/src/algorithm/toolkit.rs +++ b/compiler-core/checking/src/algorithm/toolkit.rs @@ -126,9 +126,9 @@ pub fn instantiate_forall(state: &mut CheckState, mut type_id: TypeId) -> TypeId pub fn instantiate_with_arguments( state: &mut CheckState, mut type_id: TypeId, - arguments: impl IntoIterator, + arguments: impl AsRef<[TypeId]>, ) -> TypeId { - let mut arguments_iter = arguments.into_iter(); + let mut arguments_iter = arguments.as_ref().iter().copied(); safe_loop! { type_id = state.normalize_type(type_id); From 2bf4dde51f7d3a22b59ec3ebfb08596ea60d90b6 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 17:09:14 +0800 Subject: [PATCH 58/62] Clippy lints --- .../constraint/compiler_solved/prim_coerce.rs | 16 ++++++---------- .../checking/src/algorithm/type_item.rs | 4 +--- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs index ba7b67b2..56dee272 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs @@ -92,9 +92,9 @@ where { let mut hidden_newtype: Option<(FileId, TypeItemId)> = None; - if has_type_kind(state, context, left)? { - if let Some((file_id, type_id)) = derive::extract_type_constructor(state, left) { - if is_newtype(context, file_id, type_id)? { + if has_type_kind(state, context, left)? + && let Some((file_id, type_id)) = derive::extract_type_constructor(state, left) + && is_newtype(context, file_id, type_id)? { if is_constructor_in_scope(context, file_id, type_id)? { let inner = derive::get_newtype_inner(state, context, file_id, type_id, left)?; let constraint = make_coercible_constraint(state, context, inner, right); @@ -106,12 +106,10 @@ where hidden_newtype = Some((file_id, type_id)); } } - } - } - if has_type_kind(state, context, right)? { - if let Some((file_id, type_id)) = derive::extract_type_constructor(state, right) { - if is_newtype(context, file_id, type_id)? { + if has_type_kind(state, context, right)? + && let Some((file_id, type_id)) = derive::extract_type_constructor(state, right) + && is_newtype(context, file_id, type_id)? { if is_constructor_in_scope(context, file_id, type_id)? { let inner = derive::get_newtype_inner(state, context, file_id, type_id, right)?; let constraint = make_coercible_constraint(state, context, left, inner); @@ -123,8 +121,6 @@ where hidden_newtype = Some((file_id, type_id)); } } - } - } if let Some((file_id, item_id)) = hidden_newtype { return Ok(NewtypeCoercionResult::ConstructorNotInScope { file_id, item_id }); diff --git a/compiler-core/checking/src/algorithm/type_item.rs b/compiler-core/checking/src/algorithm/type_item.rs index 633a718b..de37fe3f 100644 --- a/compiler-core/checking/src/algorithm/type_item.rs +++ b/compiler-core/checking/src/algorithm/type_item.rs @@ -992,9 +992,7 @@ fn check_roles( lowering::Role::Unknown => continue, }; - if is_foreign { - *validated = declared; - } else if declared >= inferred { + if is_foreign || declared >= inferred { *validated = declared; } else { state.insert_error(ErrorKind::InvalidRoleDeclaration { From b33aecf63abdfa67e1d781ba0d43a09188a2e05b Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 17:10:00 +0800 Subject: [PATCH 59/62] Format files --- .../constraint/compiler_solved/prim_coerce.rs | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs index 56dee272..cf4ccf29 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs @@ -94,33 +94,35 @@ where if has_type_kind(state, context, left)? && let Some((file_id, type_id)) = derive::extract_type_constructor(state, left) - && is_newtype(context, file_id, type_id)? { - if is_constructor_in_scope(context, file_id, type_id)? { - let inner = derive::get_newtype_inner(state, context, file_id, type_id, left)?; - let constraint = make_coercible_constraint(state, context, inner, right); - return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { - constraints: vec![constraint], - equalities: vec![], - })); - } else { - hidden_newtype = Some((file_id, type_id)); - } - } + && is_newtype(context, file_id, type_id)? + { + if is_constructor_in_scope(context, file_id, type_id)? { + let inner = derive::get_newtype_inner(state, context, file_id, type_id, left)?; + let constraint = make_coercible_constraint(state, context, inner, right); + return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { + constraints: vec![constraint], + equalities: vec![], + })); + } else { + hidden_newtype = Some((file_id, type_id)); + } + } if has_type_kind(state, context, right)? && let Some((file_id, type_id)) = derive::extract_type_constructor(state, right) - && is_newtype(context, file_id, type_id)? { - if is_constructor_in_scope(context, file_id, type_id)? { - let inner = derive::get_newtype_inner(state, context, file_id, type_id, right)?; - let constraint = make_coercible_constraint(state, context, left, inner); - return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { - constraints: vec![constraint], - equalities: vec![], - })); - } else if hidden_newtype.is_none() { - hidden_newtype = Some((file_id, type_id)); - } - } + && is_newtype(context, file_id, type_id)? + { + if is_constructor_in_scope(context, file_id, type_id)? { + let inner = derive::get_newtype_inner(state, context, file_id, type_id, right)?; + let constraint = make_coercible_constraint(state, context, left, inner); + return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { + constraints: vec![constraint], + equalities: vec![], + })); + } else if hidden_newtype.is_none() { + hidden_newtype = Some((file_id, type_id)); + } + } if let Some((file_id, item_id)) = hidden_newtype { return Ok(NewtypeCoercionResult::ConstructorNotInScope { file_id, item_id }); From f68e65c51a32f222e4eae900d6a2b363a8f991eb Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 17:14:59 +0800 Subject: [PATCH 60/62] Macro for derive dispatch --- .../checking/src/algorithm/derive.rs | 54 +++++++++---------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/compiler-core/checking/src/algorithm/derive.rs b/compiler-core/checking/src/algorithm/derive.rs index 0de6a0f3..261963e5 100644 --- a/compiler-core/checking/src/algorithm/derive.rs +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -96,35 +96,31 @@ where let class_is = |known| Some((class_file, class_id)) == known; let known_types = &context.known_types; - if class_is(known_types.eq) || class_is(known_types.ord) { - check_derive_class(state, context, elaborated)?; - } else if class_is(known_types.functor) { - functor::check_derive_functor(state, context, elaborated)?; - } else if class_is(known_types.bifunctor) { - functor::check_derive_bifunctor(state, context, elaborated)?; - } else if class_is(known_types.contravariant) { - contravariant::check_derive_contravariant(state, context, elaborated)?; - } else if class_is(known_types.profunctor) { - contravariant::check_derive_profunctor(state, context, elaborated)?; - } else if class_is(known_types.foldable) { - foldable::check_derive_foldable(state, context, elaborated)?; - } else if class_is(known_types.bifoldable) { - foldable::check_derive_bifoldable(state, context, elaborated)?; - } else if class_is(known_types.traversable) { - traversable::check_derive_traversable(state, context, elaborated)?; - } else if class_is(known_types.bitraversable) { - traversable::check_derive_bitraversable(state, context, elaborated)?; - } else if class_is(known_types.eq1) { - eq1::check_derive_eq1(state, context, elaborated)?; - } else if class_is(known_types.ord1) { - eq1::check_derive_ord1(state, context, elaborated)?; - } else if class_is(known_types.newtype) { - newtype::check_derive_newtype(state, context, elaborated)?; - } else if class_is(known_types.generic) { - generic::check_derive_generic(state, context, elaborated)?; - } else { - state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); - }; + macro_rules! dispatch { + ($($($known:ident)|+ => $handler:path),+ $(,)?) => { + $(if $(class_is(known_types.$known))||+ { + $handler(state, context, elaborated)?; + } else)+ { + state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); + } + }; + } + + dispatch! { + eq | ord => check_derive_class, + functor => functor::check_derive_functor, + bifunctor => functor::check_derive_bifunctor, + contravariant => contravariant::check_derive_contravariant, + profunctor => contravariant::check_derive_profunctor, + foldable => foldable::check_derive_foldable, + bifoldable => foldable::check_derive_bifoldable, + traversable => traversable::check_derive_traversable, + bitraversable => traversable::check_derive_bitraversable, + eq1 => eq1::check_derive_eq1, + ord1 => eq1::check_derive_ord1, + newtype => newtype::check_derive_newtype, + generic => generic::check_derive_generic, + } } // Unbind type variables bound during elaboration. From 43629087358dc7516ba4eb3140b66638c3671f08 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 17:33:24 +0800 Subject: [PATCH 61/62] Define constraint_application from extract_type_application --- .../checking/src/algorithm/constraint.rs | 26 +++++-------------- .../checking/src/algorithm/quantify.rs | 10 +++---- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index 1d76d2a3..169cd1eb 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -20,7 +20,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use crate::algorithm::fold::{FoldAction, TypeFold, fold_type}; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::visit::CollectFileReferences; -use crate::algorithm::{transfer, unification}; +use crate::algorithm::{toolkit, transfer, unification}; use crate::core::{Class, Instance, InstanceKind, Variable, debruijn}; use crate::{CheckedModule, ExternalQueries, Type, TypeId}; @@ -120,25 +120,11 @@ pub(crate) fn constraint_application( state: &mut CheckState, id: TypeId, ) -> Option { - let mut arguments = vec![]; - let mut current_id = id; - loop { - match state.storage[current_id] { - Type::Application(function, argument) => { - arguments.push(argument); - current_id = state.normalize_type(function); - } - Type::KindApplication(function, _) => { - current_id = state.normalize_type(function); - } - Type::Constructor(file_id, item_id) => { - arguments.reverse(); - return Some(ConstraintApplication { file_id, item_id, arguments }); - } - _ => { - return None; - } - } + let (constructor, arguments) = toolkit::extract_type_application(state, id); + if let Type::Constructor(file_id, item_id) = state.storage[constructor] { + Some(ConstraintApplication { file_id, item_id, arguments }) + } else { + None } } diff --git a/compiler-core/checking/src/algorithm/quantify.rs b/compiler-core/checking/src/algorithm/quantify.rs index 52e01cea..f56f8e9c 100644 --- a/compiler-core/checking/src/algorithm/quantify.rs +++ b/compiler-core/checking/src/algorithm/quantify.rs @@ -10,9 +10,7 @@ use rustc_hash::FxHashSet; use smol_str::SmolStrBuilder; use crate::ExternalQueries; -use crate::algorithm::constraint::{ - ConstraintApplication, constraint_application, elaborate_superclasses, -}; +use crate::algorithm::constraint::{self, ConstraintApplication}; use crate::algorithm::fold::Zonk; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::substitute::{ShiftBound, SubstituteUnification, UniToLevel}; @@ -165,7 +163,7 @@ where // Remove constraints found in the superclasses, keeping the most specific. let minimized = constraints.into_iter().filter(|&constraint| { - constraint_application(state, constraint) + constraint::constraint_application(state, constraint) .is_none_or(|constraint| !superclasses.contains(&constraint)) }); @@ -181,10 +179,10 @@ where Q: ExternalQueries, { let mut superclasses = vec![]; - elaborate_superclasses(state, context, constraint, &mut superclasses)?; + constraint::elaborate_superclasses(state, context, constraint, &mut superclasses)?; Ok(superclasses .into_iter() - .filter_map(|constraint| constraint_application(state, constraint)) + .filter_map(|constraint| constraint::constraint_application(state, constraint)) .collect()) } From 7f57bc372640d01adb6df7917746f34689fbec87 Mon Sep 17 00:00:00 2001 From: Justin Garcia Date: Fri, 16 Jan 2026 17:54:49 +0800 Subject: [PATCH 62/62] Use macro for compiler-solved classes --- .../checking/src/algorithm/constraint.rs | 109 ++++++++---------- 1 file changed, 49 insertions(+), 60 deletions(-) diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index 169cd1eb..e366d5d5 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -809,66 +809,55 @@ where { let ConstraintApplication { file_id, item_id, ref arguments } = *wanted; - let match_instance = if file_id == context.prim_int.file_id { - if item_id == context.prim_int.add { - prim_int_add(state, arguments) - } else if item_id == context.prim_int.mul { - prim_int_mul(state, arguments) - } else if item_id == context.prim_int.compare { - prim_int_compare(state, context, arguments, given) - } else if item_id == context.prim_int.to_string { - prim_int_to_string(state, arguments) - } else { - None - } - } else if file_id == context.prim_symbol.file_id { - if item_id == context.prim_symbol.append { - prim_symbol_append(state, arguments) - } else if item_id == context.prim_symbol.compare { - prim_symbol_compare(state, context, arguments) - } else if item_id == context.prim_symbol.cons { - prim_symbol_cons(state, arguments) - } else { - None - } - } else if file_id == context.prim_row.file_id { - if item_id == context.prim_row.union { - prim_row_union(state, arguments) - } else if item_id == context.prim_row.cons { - prim_row_cons(state, arguments) - } else if item_id == context.prim_row.lacks { - prim_row_lacks(state, arguments) - } else if item_id == context.prim_row.nub { - prim_row_nub(state, arguments) - } else { - None - } - } else if file_id == context.prim_row_list.file_id { - if item_id == context.prim_row_list.row_to_list { - prim_rowlist_row_to_list(state, context, arguments) - } else { - None - } - } else if file_id == context.prim_coerce.file_id { - if item_id == context.prim_coerce.coercible { - return prim_coercible(state, context, arguments); - } else { - None - } - } else if file_id == context.prim_type_error.file_id { - if item_id == context.prim_type_error.warn { - prim_warn(state, context, arguments) - } else if item_id == context.prim_type_error.fail { - prim_fail(state, context, arguments) - } else { - None - } - } else if context.known_reflectable.is_symbol == Some((file_id, item_id)) { - prim_is_symbol(state, arguments) - } else if context.known_reflectable.reflectable == Some((file_id, item_id)) { - prim_reflectable(state, context, arguments) - } else { - None + macro_rules! dispatch { + ( + $($prim_ctx:expr => { + $($item:ident => $handler:expr),* $(,)? + }),* + $(, ? $optional:expr => $opt_handler:expr)* $(,)? + ) => { + $( + if file_id == $prim_ctx.file_id { + $(if item_id == $prim_ctx.$item { $handler } else)* + { None } + } else + )* + $(if $optional == Some((file_id, item_id)) { $opt_handler } else)* + { None } + }; + } + + let match_instance = dispatch! { + context.prim_int => { + add => prim_int_add(state, arguments), + mul => prim_int_mul(state, arguments), + compare => prim_int_compare(state, context, arguments, given), + to_string => prim_int_to_string(state, arguments), + }, + context.prim_symbol => { + append => prim_symbol_append(state, arguments), + compare => prim_symbol_compare(state, context, arguments), + cons => prim_symbol_cons(state, arguments), + }, + context.prim_row => { + union => prim_row_union(state, arguments), + cons => prim_row_cons(state, arguments), + lacks => prim_row_lacks(state, arguments), + nub => prim_row_nub(state, arguments), + }, + context.prim_row_list => { + row_to_list => prim_rowlist_row_to_list(state, context, arguments), + }, + context.prim_coerce => { + coercible => prim_coercible(state, context, arguments)?, + }, + context.prim_type_error => { + warn => prim_warn(state, context, arguments), + fail => prim_fail(state, context, arguments), + }, + // These classes are defined in `prelude` and may be optional. + ? context.known_reflectable.is_symbol => prim_is_symbol(state, arguments), + ? context.known_reflectable.reflectable => prim_reflectable(state, context, arguments), }; Ok(match_instance)