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-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 7a0b807c..351784b4 100644 --- a/compiler-core/checking/src/algorithm.rs +++ b/compiler-core/checking/src/algorithm.rs @@ -12,9 +12,15 @@ 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; +/// Implements type visiting for read-only traversals. +pub mod visit; + /// Implements type signature inspection. pub mod inspect; @@ -27,6 +33,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; @@ -36,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; @@ -49,14 +61,15 @@ pub mod type_item; pub mod unification; use std::slice; +use std::sync::Arc; 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::{Role, Type, TypeId}; use crate::{CheckedModule, ExternalQueries}; pub fn check_source(queries: &impl ExternalQueries, file_id: FileId) -> QueryResult { @@ -64,16 +77,35 @@ 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)?; + check_derive_heads(&mut state, &context)?; check_value_groups(&mut state, &context)?; check_instance_members(&mut state, &context)?; 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, @@ -81,168 +113,62 @@ fn check_type_signatures( 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)?; - } - } + 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, + }; + 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 { + for scc in &context.grouped.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]); - } - 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)?; + Scc::Base(id) => { + if let Some(item) = type_item::check_type_item(state, context, *id)? { + type_item::commit_type_item(state, context, *id, item)?; } - - build_data_constructor_types(state, context, &with_signature)?; - state.commit_binding_group(context)?; } - } - } - - 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, + Scc::Recursive(id) => { + 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)?; + } + Ok(()) + })?; } - }); - - 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); } } - Ok(()) } @@ -253,7 +179,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)?; @@ -276,9 +202,9 @@ fn check_instance_heads( 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(), + 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, }); for &item_id in items { @@ -304,9 +230,9 @@ fn check_instance_members( 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(), + 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, }); for &item_id in items { @@ -330,6 +256,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, @@ -338,6 +265,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)?; @@ -346,6 +274,49 @@ where Ok(()) } +fn check_derive_heads( + state: &mut state::CheckState, + context: &state::CheckContext, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + 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(), + }); + + for &item_id in items { + let Some(TermItemIr::Derive { newtype, constraints, arguments, resolution }) = + context.lowered.info.get_term_item(item_id) + else { + continue; + }; + + 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, + }; + + derive::check_derive(state, context, check_derive)?; + } + + Ok(()) +} + fn check_value_groups( state: &mut state::CheckState, context: &state::CheckContext, @@ -362,44 +333,54 @@ 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(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)?; } } } @@ -455,5 +436,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) } diff --git a/compiler-core/checking/src/algorithm/constraint.rs b/compiler-core/checking/src/algorithm/constraint.rs index 9c43d56a..e366d5d5 100644 --- a/compiler-core/checking/src/algorithm/constraint.rs +++ b/compiler-core/checking/src/algorithm/constraint.rs @@ -15,12 +15,13 @@ 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::{transfer, unification}; -use crate::core::{Class, Instance, Variable, debruijn}; +use crate::algorithm::visit::CollectFileReferences; +use crate::algorithm::{toolkit, transfer, unification}; +use crate::core::{Class, Instance, InstanceKind, Variable, debruijn}; use crate::{CheckedModule, ExternalQueries, Type, TypeId}; pub fn solve_constraints( @@ -58,27 +59,24 @@ 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, &given)? { + 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.file_id, application.item_id)?; + let instance_chains = collect_instance_chains(state, context, &application)?; for chain in instance_chains { 'chain: for instance in chain { @@ -122,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 } } @@ -221,44 +205,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 = if file_id == context.id { - state - .checked - .instances - .values() - .filter(|instance| instance.resolution.1 == item_id) - .cloned() - .collect_vec() - } else { - let checked = context.queries.checked(file_id)?; - checked - .instances - .values() - .filter(|instance| instance.resolution.1 == item_id) - .cloned() - .collect_vec() + 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 { - grouped.entry(instance.chain_id).or_default().push(instance); + 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 { + 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 { - chain.sort_by_key(|instance| instance.chain_position); + 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); } - Ok(result) + Ok(all_chains) } fn localize_class(state: &mut CheckState, context: &CheckContext, class: &Class) -> Class @@ -270,8 +277,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, } @@ -285,12 +301,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( @@ -457,23 +469,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 } } @@ -584,15 +592,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 produces them, Bound consumes them. - (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, } } @@ -600,9 +599,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 { @@ -803,55 +802,65 @@ fn match_compiler_instances( state: &mut CheckState, context: &CheckContext, wanted: &ConstraintApplication, -) -> Option + given: &[ConstraintApplication], +) -> QueryResult> where Q: ExternalQueries, { let ConstraintApplication { file_id, item_id, ref arguments } = *wanted; - 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) - } 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 { - 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) } struct ApplyBindings<'a> { @@ -871,7 +880,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/constraint/compiler_solved.rs b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs index ddc9f51b..4fc1ee24 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved.rs @@ -1,577 +1,30 @@ -use std::cmp::Ordering; +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 itertools::EitherOrBoth; -use lowering::StringKind; -use rustc_hash::FxHashSet; use smol_str::SmolStr; -use super::MatchInstance; -use crate::algorithm::kind; -use crate::algorithm::state::{CheckContext, CheckState}; -use crate::core::{RowField, RowType}; -use crate::{ExternalQueries, Type, TypeId}; +use crate::algorithm::state::CheckState; +use crate::{Type, TypeId}; -fn extract_integer(state: &CheckState, id: TypeId) -> Option { - let Type::Integer(n) = state.storage[id] else { return None }; - Some(n) +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 { - let Type::String(_, s) = &state.storage[id] else { return None }; - Some(s.clone()) -} - -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 super::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() { - 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() { - 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 super::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], -) -> 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_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 = 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); - } - - 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 super::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 super::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 super::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)] }) +pub(super) fn extract_symbol(state: &CheckState, id: TypeId) -> Option { + let Type::String(_, value) = &state.storage[id] else { return None }; + Some(SmolStr::clone(value)) } 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..cf4ccf29 --- /dev/null +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_coerce.rs @@ -0,0 +1,412 @@ +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, toolkit}; +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)? + && 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)); + } + } + + 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)); + } + } + + 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) = toolkit::extract_type_application(state, left); + let (_, right) = toolkit::extract_type_application(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 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..5e0e8e5b --- /dev/null +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_type_error.rs @@ -0,0 +1,162 @@ +use lowering::StringKind; +use smol_str::SmolStr; + +use crate::algorithm::constraint::MatchInstance; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::toolkit; +use crate::core::pretty; +use crate::error::ErrorKind; +use crate::{ExternalQueries, Type, TypeId}; + +/// 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) = toolkit::extract_type_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/derive.rs b/compiler-core/checking/src/algorithm/derive.rs new file mode 100644 index 00000000..261963e5 --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive.rs @@ -0,0 +1,374 @@ +//! Implements type class deriving for PureScript. + +mod contravariant; +mod eq1; +mod foldable; +mod functor; +mod generic; +mod higher_kinded; +mod newtype; +mod tools; +mod traversable; +mod variance; + +use building_types::QueryResult; +use files::FileId; +use indexing::{DeriveId, TermItemId, TypeItemId}; + +use crate::ExternalQueries; +use crate::algorithm::safety::safe_loop; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::{kind, term_item, toolkit, transfer}; +use crate::core::{Type, TypeId, 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, +} + +/// 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; + + 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 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, 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 elaborated = tools::ElaboratedDerive { + derive_id, + constraints: core_constraints, + arguments: core_arguments, + class_file, + class_id, + }; + + if is_newtype { + check_newtype_derive(state, context, elaborated)?; + } else { + let class_is = |known| Some((class_file, class_id)) == known; + let known_types = &context.known_types; + + 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. + state.type_scope.unbind(debruijn::Level(size.0)); + + Ok(()) + }) +} + +fn check_derive_class( + 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)) = 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 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)?; + + tools::solve_and_report_constraints(state, context) +} + +fn check_newtype_derive( + state: &mut CheckState, + context: &CheckContext, + input: tools::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)?; + + // 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)) + }; + + tools::push_given_constraints(state, &input.constraints); + tools::register_derived_instance(state, context, input); + + state.constraints.push_wanted(delegate_constraint); + + tools::solve_and_report_constraints(state, context) +} + +pub 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, + } + } +} + +/// 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) +} + +pub(crate) 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))) +} + +/// 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. +pub fn get_newtype_inner( + state: &mut CheckState, + context: &CheckContext, + newtype_file: FileId, + newtype_id: TypeItemId, + newtype_type: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let constructors = tools::lookup_data_constructors(context, newtype_file, newtype_id)?; + + 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 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)) +} + +/// 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 = tools::lookup_data_constructors(context, data_file, data_id)?; + + 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 + }; + + 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 = instantiate_constructor_fields(state, constructor_type, &arguments); + for field_type in field_types { + higher_kinded::generate_constraint(state, context, field_type, class, class1); + } + } + + Ok(()) +} + +/// Instantiates and extracts constructor fields from a constructor type. +/// +/// 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 +/// +/// 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 +/// ``` +/// +/// 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, + arguments: &[TypeId], +) -> Vec { + 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/contravariant.rs b/compiler-core/checking/src/algorithm/derive/contravariant.rs new file mode 100644 index 00000000..9e21cf17 --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/contravariant.rs @@ -0,0 +1,88 @@ +//! Implements derive for Contravariant and Profunctor. + +use building_types::QueryResult; + +use crate::ExternalQueries; +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; + +/// 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)) = 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 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)); + 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)) = 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(()); + }; + + // 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::emit_superclass_constraints(state, context, &input)?; + 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/eq1.rs b/compiler-core/checking/src/algorithm/derive/eq1.rs new file mode 100644 index 00000000..9915e73a --- /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::{self, 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 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(()); + }; + + 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/compiler-core/checking/src/algorithm/derive/foldable.rs b/compiler-core/checking/src/algorithm/derive/foldable.rs new file mode 100644 index 00000000..96b061b5 --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/foldable.rs @@ -0,0 +1,85 @@ +//! Implements derive for Foldable and Bifoldable. + +use building_types::QueryResult; + +use crate::ExternalQueries; +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; + +/// 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)) = 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 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)); + 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)) = 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(()); + }; + + // 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 = + 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 new file mode 100644 index 00000000..e7466672 --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/functor.rs @@ -0,0 +1,85 @@ +//! Implements derive for Functor and Bifunctor. + +use building_types::QueryResult; + +use crate::ExternalQueries; +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; + +/// Checks a derive instance for Functor. +pub fn check_derive_functor( + 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)) = 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 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)); + generate_variance_constraints(state, context, data_file, data_id, derived_type, config)?; + + tools::solve_and_report_constraints(state, context) +} + +/// Checks a derive instance for Bifunctor. +pub fn check_derive_bifunctor( + 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)) = 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(()); + }; + + // 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 = + VarianceConfig::Pair((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) +} 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..d5ce8a76 --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/generic.rs @@ -0,0 +1,162 @@ +//! 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::{toolkit, 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 arguments = toolkit::extract_all_applications(state, derived_type); + + let 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( + state, + context, + known_generic, + data_file, + &arguments, + 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, + arguments: &[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::instantiate_constructor_fields(state, constructor_type, arguments) + } 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/higher_kinded.rs b/compiler-core/checking/src/algorithm/derive/higher_kinded.rs new file mode 100644 index 00000000..7c09a2d4 --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/higher_kinded.rs @@ -0,0 +1,93 @@ +//! 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::derive::tools; +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` +/// +/// For a field type like `f (g Int)` +/// - Emits `Eq1 f`, `Eq (g Int)` +/// +/// For nominal types like `Array Int` +/// - Emits `Eq (Array Int)` +/// +/// For records like `{ a :: Int, b :: f Int }` +/// - Emits `Eq Int` and `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 { + tools::emit_constraint(state, class1, function); + } + tools::emit_constraint(state, class, argument); + } else { + tools::emit_constraint(state, class, type_id); + } + } + Type::Row(RowType { ref fields, .. }) => { + for field in fields.iter() { + generate_constraint(state, context, field.id, class, class1); + } + } + _ => { + tools::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) => state.type_scope.kinds.get(*level).copied(), + Variable::Free(_) => None, + } +} 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..3c74bb3a --- /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)) = 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 }); + 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/derive/tools.rs b/compiler-core/checking/src/algorithm/derive/tools.rs new file mode 100644 index 00000000..b2ad20c1 --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/tools.rs @@ -0,0 +1,152 @@ +//! Shared utilities for derive instance checking. + +use building_types::QueryResult; +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::{constraint, quantify, substitute, transfer}; +use crate::core::{Instance, InstanceKind, Type, TypeId, debruijn}; +use crate::error::ErrorKind; + +/// Elaborated derive instance after kind inference. +pub struct ElaboratedDerive { + pub derive_id: DeriveId, + pub constraints: Vec<(TypeId, TypeId)>, + 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); + } +} + +/// 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, + 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: vec![], + }; + + 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) +} 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..e32c4d2c --- /dev/null +++ b/compiler-core/checking/src/algorithm/derive/traversable.rs @@ -0,0 +1,87 @@ +//! Implements derive for Traversable and Bitraversable. + +use building_types::QueryResult; + +use crate::ExternalQueries; +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; + +/// 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)) = 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 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)); + 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)) = 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(()); + }; + + // 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( + (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/derive/variance.rs b/compiler-core/checking/src/algorithm/derive/variance.rs new file mode 100644 index 00000000..f1a73357 --- /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::{self, tools}; +use crate::algorithm::safety::safe_loop; +use crate::algorithm::state::{CheckContext, CheckState}; +use crate::algorithm::{substitute, toolkit, 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 = + derive::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 = 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![]; + + 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/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/kind.rs b/compiler-core/checking/src/algorithm/kind.rs index 7a7ee4c7..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 = @@ -274,7 +278,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 +508,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, @@ -565,7 +569,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/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/kind/synonym.rs b/compiler-core/checking/src/algorithm/kind/synonym.rs index 49092214..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) @@ -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/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/quantify.rs b/compiler-core/checking/src/algorithm/quantify.rs index 4c12f956..f56f8e9c 100644 --- a/compiler-core/checking/src/algorithm/quantify.rs +++ b/compiler-core/checking/src/algorithm/quantify.rs @@ -10,13 +10,11 @@ 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::{ShiftLevels, SubstituteUnification, UniToLevel}; -use crate::core::{ForallBinder, RowType, Type, TypeId, debruijn}; +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)> { let graph = collect_unification(state, id); @@ -33,12 +31,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); @@ -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()) } @@ -194,6 +192,125 @@ 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(()); + } + + 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 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 = 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) + }); + + instance.arguments = arguments.collect(); + + let constraints = instance.constraints.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) + }); + + instance.constraints = constraints.collect(); + + instance.kind_variables = kind_variables; + + Some(()) +} + /// Builds a topological sort of the [`UniGraph`]. /// /// This function uses the domain-based sorting of the unification variables @@ -235,12 +352,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 +423,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/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 3a6011e0..618f9874 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; @@ -9,15 +10,15 @@ use building_types::QueryResult; use files::FileId; use indexing::{IndexedModule, TermItemId, TypeItemId}; use lowering::{ - BinderId, GraphNodeId, ImplicitBindingId, LetBindingNameGroupId, LoweredModule, RecordPunId, - TypeVariableBindingId, + BinderId, GraphNodeId, GroupedModule, ImplicitBindingId, LetBindingNameGroupId, LoweredModule, + RecordPunId, TypeItemIr, TypeVariableBindingId, }; use resolving::ResolvedModule; 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::algorithm::{constraint, transfer}; +use crate::core::{Type, TypeId, TypeInterner, Variable, debruijn}; use crate::error::{CheckError, ErrorKind, ErrorStep}; use crate::{CheckedModule, ExternalQueries}; @@ -36,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) @@ -289,22 +297,13 @@ pub struct CheckedConstructor { pub arguments: Vec, } -#[derive(Clone)] -pub struct CheckedDataLike { - 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 residual: FxHashMap>, - pub data: FxHashMap, + pub types: FxHashMap, } impl BindingGroupContext { @@ -313,15 +312,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) } } @@ -340,18 +331,27 @@ 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 prim_type_error: PrimTypeErrorCore, + pub known_types: KnownTypesCore, + pub known_reflectable: KnownReflectableCore, + pub known_generic: Option, pub id: FileId, pub indexed: Arc, pub lowered: Arc, + 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> @@ -365,30 +365,48 @@ 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)?; 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 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)?; + 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, prim_int, + prim_boolean, prim_ordering, prim_symbol, prim_row, prim_row_list, + prim_coerce, + prim_type_error, + known_types, + known_reflectable, + known_generic, id, indexed, lowered, + grouped, bracketed, sectioned, + resolved, prim_indexed, + prim_resolved, }) } } @@ -431,6 +449,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, @@ -451,8 +470,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"), @@ -497,6 +520,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, @@ -606,31 +653,300 @@ 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 }) + } +} + +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, + 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))) +} + +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)>, + pub ord: Option<(FileId, TypeItemId)>, + 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)>, + pub foldable: Option<(FileId, TypeItemId)>, + 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 { + fn collect(queries: &impl ExternalQueries) -> QueryResult { + let eq = fetch_known_type(queries, "Data.Eq", "Eq")?; + 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")?; + let functor = fetch_known_type(queries, "Data.Functor", "Functor")?; + let bifunctor = fetch_known_type(queries, "Data.Bifunctor", "Bifunctor")?; + let contravariant = + fetch_known_type(queries, "Data.Functor.Contravariant", "Contravariant")?; + 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")?; + let newtype = fetch_known_type(queries, "Data.Newtype", "Newtype")?; + let generic = fetch_known_type(queries, "Data.Generic.Rep", "Generic")?; + Ok(KnownTypesCore { + eq, + eq1, + ord, + ord1, + functor, + bifunctor, + contravariant, + profunctor, + foldable, + bifoldable, + traversable, + bitraversable, + newtype, + generic, + }) + } +} + +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, + 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 { - 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); + } } + + let result = f(self); + self.binding_group.terms.clear(); + result } - pub fn type_binding_group( + /// Executes the given closure with a type binding group in scope. + /// + /// 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: impl IntoIterator, - ) where + group: impl AsRef<[TypeItemId]>, + 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.types.insert(item, t); + 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; + } + 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)); } + + let operators = group.as_ref().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.as_ref().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> @@ -641,57 +957,6 @@ impl CheckState { constraint::solve_constraints(self, context, wanted, given) } - pub fn commit_binding_group(&mut self, context: &CheckContext) -> QueryResult<()> - where - Q: ExternalQueries, - { - 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); - } - } - - 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) { - class.quantified_variables = quantified_count; - 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, 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(()) - } - /// Executes an action with an [`ErrorStep`] in scope. pub fn with_error_step(&mut self, step: ErrorStep, f: F) -> T where @@ -741,6 +1006,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/compiler-core/checking/src/algorithm/substitute.rs b/compiler-core/checking/src/algorithm/substitute.rs index e5a8262b..fac69b7a 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,18 @@ 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 } - FoldAction::Continue } fn transform_binder(&mut self, binder: &mut ForallBinder) { @@ -98,3 +97,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::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/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 882419f9..a7fdb104 100644 --- a/compiler-core/checking/src/algorithm/term_item.rs +++ b/compiler-core/checking/src/algorithm/term_item.rs @@ -12,9 +12,15 @@ 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, 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`], @@ -134,10 +140,6 @@ where 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); - core_arguments.push((inferred_type, inferred_kind)); } @@ -145,23 +147,36 @@ 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), + kind: InstanceKind::Chain { id: chain_id, position: chain_position }, + kind_variables: vec![], + }; + + 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.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)); @@ -179,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, @@ -225,13 +240,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, { @@ -246,13 +261,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, @@ -261,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. @@ -287,6 +341,7 @@ where class_id, instance_arguments, instance_constraints, + kind_variables, } = input; // Fetch implicit variables captured by check_instance. @@ -305,6 +360,7 @@ where class_id, instance_arguments, instance_constraints, + kind_variables, }, )?; } @@ -343,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. @@ -374,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); } @@ -424,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 }); } } @@ -441,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)?; @@ -502,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/toolkit.rs b/compiler-core/checking/src/algorithm/toolkit.rs new file mode 100644 index 00000000..3b9ca383 --- /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 AsRef<[TypeId]>, +) -> TypeId { + let mut arguments_iter = arguments.as_ref().iter().copied(); + + 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 +} diff --git a/compiler-core/checking/src/algorithm/type_item.rs b/compiler-core/checking/src/algorithm/type_item.rs index d311b045..de37fe3f 100644 --- a/compiler-core/checking/src/algorithm/type_item.rs +++ b/compiler-core/checking/src/algorithm/type_item.rs @@ -1,252 +1,159 @@ use std::sync::Arc; use building_types::QueryResult; -use indexing::TypeItemId; -use itertools::Itertools; +use indexing::{TermItemId, TypeItemId}; +use itertools::{Itertools, izip}; use lowering::{ ClassIr, DataIr, NewtypeIr, SynonymIr, TermItemIr, TypeItemIr, TypeVariableBinding, }; use smol_str::SmolStr; use crate::ExternalQueries; -use crate::algorithm::state::{CheckContext, CheckState, CheckedConstructor, CheckedDataLike}; -use crate::algorithm::{inspect, kind, transfer, unification}; -use crate::core::{Class, ForallBinder, Operator, Synonym, Type, TypeId, Variable, debruijn}; +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, Role, 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, + pub declared_roles: Arc<[lowering::Role]>, +} + +#[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, .. } => { + TypeItemIr::DataGroup { signature, data, roles } => { 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, roles) } - TypeItemIr::NewtypeGroup { signature, newtype, .. } => { + TypeItemIr::NewtypeGroup { signature, newtype, roles } => { 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, roles) } 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 { roles, .. } => { + check_foreign_definition(state, context, item_id, roles) + } 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<()> + declared_roles: &Arc<[lowering::Role]>, +) -> 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 +161,31 @@ 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 }; - 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, + declared_roles: declared_roles.clone(), + }))) } -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 +194,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,28 +240,18 @@ where context.prim.constraint })? else { - return Ok(()); + return Ok(None); }; - // Elaborate and globalize superclass constraints + // Check superclasses 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)) }); + 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); - Class { superclasses, 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| { @@ -356,15 +261,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, @@ -376,14 +283,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, @@ -391,10 +304,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) @@ -423,69 +338,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)) @@ -496,118 +391,660 @@ 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, declared_roles } = + checked; - for item_id in context.indexed.pairs.data_constructors(item_id) { - let Some(TermItemIr::Constructor { arguments }) = - context.lowered.info.get_term_item(item_id) + 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) 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) + }); + + 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 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 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(); - let reference_type = unsolved_kinds.iter().fold(reference_type, |reference, &(kind, _)| { - state.storage.intern(Type::KindApplication(reference, kind)) + 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 }) }); - 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![]; + + 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(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); + } - if let Some(pending_type) = state.binding_group.terms.get(&constructor.item_id) { - let _ = unification::unify(state, context, *pending_type, constructor_type)?; + 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 || declared >= inferred { + *validated = declared; } else { - state.binding_group.terms.insert(constructor.item_id, constructor_type); + state.insert_error(ErrorKind::InvalidRoleDeclaration { + type_id, + parameter_index: index, + declared, + inferred, + }); } } - Ok(()) + 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/algorithm/unification.rs b/compiler-core/checking/src/algorithm/unification.rs index 1e712af3..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), } } @@ -293,15 +298,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/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/compiler-core/checking/src/core.rs b/compiler-core/checking/src/core.rs index b413f6d9..1253d783 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), @@ -113,18 +112,49 @@ 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: Vec, } #[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, +} + +#[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, pub kind_variables: debruijn::Size, } 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/compiler-core/checking/src/error.rs b/compiler-core/checking/src/error.rs index a00ff197..6e5178cd 100644 --- a/compiler-core/checking/src/error.rs +++ b/compiler-core/checking/src/error.rs @@ -25,10 +25,30 @@ pub enum ErrorKind { AmbiguousConstraint { constraint: TypeId, }, + CannotDeriveClass { + class_file: files::FileId, + class_id: indexing::TypeItemId, + }, + CannotDeriveForType { + type_id: TypeId, + }, + ContravariantOccurrence { + type_id: TypeId, + }, + CovariantOccurrence { + type_id: TypeId, + }, CannotUnify { t1: TypeId, t2: TypeId, }, + DeriveInvalidArity { + class_file: files::FileId, + class_id: indexing::TypeItemId, + expected: usize, + actual: usize, + }, + DeriveMissingFunctor, EmptyAdoBlock, EmptyDoBlock, InstanceHeadMismatch { @@ -44,6 +64,9 @@ pub enum ErrorKind { InvalidTypeOperator { id: TypeId, }, + ExpectedNewtype { + type_id: TypeId, + }, NoInstanceFound { constraint: TypeId, }, @@ -64,6 +87,22 @@ pub enum ErrorKind { expected: u32, actual: u32, }, + InvalidRoleDeclaration { + type_id: indexing::TypeItemId, + parameter_index: usize, + declared: crate::core::Role, + inferred: crate::core::Role, + }, + CoercibleConstructorNotInScope { + 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 c20707ee..91731e7e 100644 --- a/compiler-core/checking/src/lib.rs +++ b/compiler-core/checking/src/lib.rs @@ -8,8 +8,8 @@ use std::sync::Arc; use building_types::{QueryProxy, QueryResult}; use files::FileId; -use indexing::{IndexedModule, InstanceId, TermItemId, TypeItemId}; -use lowering::LoweredModule; +use indexing::{DeriveId, IndexedModule, InstanceId, TermItemId, TypeItemId}; +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, @@ -38,9 +39,13 @@ pub struct CheckedModule { pub operators: FxHashMap, pub synonyms: FxHashMap, pub instances: FxHashMap, + pub derived: FxHashMap, pub classes: FxHashMap, + pub data: FxHashMap, + pub roles: FxHashMap>, pub errors: Vec, + pub custom_messages: Vec, } impl CheckedModule { @@ -59,6 +64,14 @@ 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 lookup_roles(&self, id: TypeItemId) -> Option> { + self.roles.get(&id).cloned() + } } pub fn check_module(queries: &impl ExternalQueries, file_id: FileId) -> QueryResult { diff --git a/compiler-core/lowering/src/algorithm.rs b/compiler-core/lowering/src/algorithm.rs index abae08d7..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)); } } @@ -270,20 +265,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 { @@ -365,6 +361,16 @@ 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 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(); @@ -384,7 +390,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, resolution, arguments }; state.info.term_item.insert(item_id, kind); } @@ -403,7 +409,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) = @@ -432,15 +438,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); } @@ -472,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/intermediate.rs b/compiler-core/lowering/src/intermediate.rs index 74ca93f9..21c73438 100644 --- a/compiler-core/lowering/src/intermediate.rs +++ b/compiler-core/lowering/src/intermediate.rs @@ -317,7 +317,9 @@ pub enum TermItemIr { arguments: Arc<[TypeId]>, }, Derive { + newtype: bool, constraints: Arc<[TypeId]>, + resolution: Option<(FileId, TypeItemId)>, arguments: Arc<[TypeId]>, }, Foreign { 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/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)) } 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 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..818b8c8e 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,28 @@ 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, + }; + + // 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) + .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() { 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/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/001_proxy_checking/Main.snap b/tests-integration/fixtures/checking/001_proxy_checking/Main.snap index 5fa3cb79..3b8f7595 100644 --- a/tests-integration/fixtures/checking/001_proxy_checking/Main.snap +++ b/tests-integration/fixtures/checking/001_proxy_checking/Main.snap @@ -7,3 +7,12 @@ Proxy :: forall (k :: Type) (a :: k). Proxy @k a Types Proxy :: forall (k :: Type). k -> Type + +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 119d140a..22188897 100644 --- a/tests-integration/fixtures/checking/002_proxy_inference/Main.snap +++ b/tests-integration/fixtures/checking/002_proxy_inference/Main.snap @@ -3,7 +3,16 @@ 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 + 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 f8616299..4016bb24 100644 --- a/tests-integration/fixtures/checking/003_data_recursive/Main.snap +++ b/tests-integration/fixtures/checking/003_data_recursive/Main.snap @@ -8,3 +8,12 @@ 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/004_data_mutual/Main.snap b/tests-integration/fixtures/checking/004_data_mutual/Main.snap index 876408b5..0b0411fe 100644 --- a/tests-integration/fixtures/checking/004_data_mutual/Main.snap +++ b/tests-integration/fixtures/checking/004_data_mutual/Main.snap @@ -10,3 +10,17 @@ 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 + + +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 e0f99dd7..518d0b78 100644 --- a/tests-integration/fixtures/checking/005_newtype_recursive/Main.snap +++ b/tests-integration/fixtures/checking/005_newtype_recursive/Main.snap @@ -7,3 +7,12 @@ In :: forall (f :: Type -> Type). f (Mu f) -> Mu f Types Mu :: (Type -> Type) -> Type + +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 2bdd114a..3fa86715 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 @@ -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 474e6fd6..f8833b48 100644 --- a/tests-integration/fixtures/checking/007_foreign_poly/Main.snap +++ b/tests-integration/fixtures/checking/007_foreign_poly/Main.snap @@ -6,10 +6,14 @@ 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 + + +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 02ebae37..6117a786 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 @@ -25,3 +25,7 @@ Test2 = Identity Digit Quantified = :0 Kind = :0 Type = :0 + + +Roles +Digit = [] 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..1614b466 100644 --- a/tests-integration/fixtures/checking/013_class_phantom/Main.snap +++ b/tests-integration/fixtures/checking/013_class_phantom/Main.snap @@ -3,7 +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/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/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/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/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/029_partial_synonym_transformers/Main.snap b/tests-integration/fixtures/checking/029_partial_synonym_transformers/Main.snap index d3fc39bf..6d3df352 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,28 @@ NestedReader = Apply (Apply (ReaderT Int) Identity) Boolean Quantified = :0 Kind = :0 Type = :0 + + +Data +Identity + Quantified = :0 + Kind = :0 + +ReaderT + Quantified = :0 + Kind = :0 + +StateT + Quantified = :0 + Kind = :0 + +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 930caeed..12517656 100644 --- a/tests-integration/fixtures/checking/031_partial_synonym_polykind/Main.snap +++ b/tests-integration/fixtures/checking/031_partial_synonym_polykind/Main.snap @@ -73,3 +73,13 @@ EndoFn = forall (a :: Type). Endo Function a Quantified = :0 Kind = :0 Type = :1 + + +Data +Function + Quantified = :0 + Kind = :0 + + +Roles +Function = [Phantom, Phantom] 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..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::(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::(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/044_binder_constructor/Main.snap b/tests-integration/fixtures/checking/044_binder_constructor/Main.snap index 6c9bbbb0..8a1f7a69 100644 --- a/tests-integration/fixtures/checking/044_binder_constructor/Main.snap +++ b/tests-integration/fixtures/checking/044_binder_constructor/Main.snap @@ -12,12 +12,31 @@ 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 Maybe :: Type -> Type List :: Type -> Type + +Data +Pair + Quantified = :0 + Kind = :0 + +Maybe + Quantified = :0 + Kind = :0 + +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 15d6bf18..17da3dd7 100644 --- a/tests-integration/fixtures/checking/045_do_discard/Main.snap +++ b/tests-integration/fixtures/checking/045_do_discard/Main.snap @@ -14,3 +14,13 @@ test' :: Effect Unit Types Effect :: Type -> Type Unit :: Type + +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 34547cf9..ba585cab 100644 --- a/tests-integration/fixtures/checking/046_do_bind/Main.snap +++ b/tests-integration/fixtures/checking/046_do_bind/Main.snap @@ -13,3 +13,13 @@ test' :: Effect (Tuple Int Int) Types Effect :: Type -> Type Tuple :: Type -> Type -> Type + +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 384b7954..e995bc82 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,12 @@ test' :: Tuple Int String Types Tuple :: Type -> Type -> Type + +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 536e9cb3..4a47a052 100644 --- a/tests-integration/fixtures/checking/049_ado_discard/Main.snap +++ b/tests-integration/fixtures/checking/049_ado_discard/Main.snap @@ -14,3 +14,13 @@ test' :: Effect Unit Types Effect :: Type -> Type Unit :: Type + +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 a2e3e8f5..fa650034 100644 --- a/tests-integration/fixtures/checking/050_ado_bind/Main.snap +++ b/tests-integration/fixtures/checking/050_ado_bind/Main.snap @@ -13,3 +13,13 @@ test' :: Effect (Tuple Int Int) Types Effect :: Type -> Type Tuple :: Type -> Type -> Type + +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 370e121a..6427d4d4 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,12 @@ test' :: Tuple Int String Types Tuple :: Type -> Type -> Type + +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 0ba97e7c..74fc7804 100644 --- a/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap +++ b/tests-integration/fixtures/checking/053_do_polymorphic/Main.snap @@ -8,7 +8,16 @@ 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 + +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 e90633b2..6fc9f5f5 100644 --- a/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap +++ b/tests-integration/fixtures/checking/054_ado_polymorphic/Main.snap @@ -8,7 +8,16 @@ 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 + +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 ab3ae6d3..4779edfd 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,12 @@ matchCons' :: List Int -> Int Types List :: Type -> Type + +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 6fe8f2a2..41dd6995 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 @@ -21,3 +21,22 @@ 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 + + +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 df8bb99d..90d55e2f 100644 --- a/tests-integration/fixtures/checking/068_expression_sections/Main.snap +++ b/tests-integration/fixtures/checking/068_expression_sections/Main.snap @@ -10,21 +10,30 @@ 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 Types Tuple :: Type -> Type -> Type + +Data +Tuple + Quantified = :0 + Kind = :0 + + +Roles +Tuple = [Representational, Representational] 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/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/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..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,8 +3,15 @@ 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) + +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..c3a35638 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,17 @@ Types Eq :: Type -> Constraint Foo :: Type +Data +Foo + Quantified = :0 + Kind = :0 + + +Roles +Foo = [] + +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..843c4db4 100644 --- a/tests-integration/fixtures/checking/090_instance_improve/Main.snap +++ b/tests-integration/fixtures/checking/090_instance_improve/Main.snap @@ -3,10 +3,31 @@ 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 + Quantified = :0 + Kind = :0 + +False + Quantified = :0 + Kind = :0 + + +Roles +True = [] +False = [] + +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..b5104d5a 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,16 @@ Types Eq :: Type -> Constraint Ord :: Type -> Constraint Ordering :: Type + +Data +Ordering + Quantified = :0 + Kind = :0 + + +Roles +Ordering = [] + +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..3657b111 100644 --- a/tests-integration/fixtures/checking/093_constraint_generalization/Main.snap +++ b/tests-integration/fixtures/checking/093_constraint_generalization/Main.snap @@ -4,10 +4,14 @@ 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 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..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 @@ -11,5 +11,17 @@ Types Foo :: Type Eq :: Type -> Constraint +Data +Foo + Quantified = :0 + Kind = :0 + + +Roles +Foo = [] + +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..58fcb5a0 100644 --- a/tests-integration/fixtures/checking/097_instance_chains/Main.snap +++ b/tests-integration/fixtures/checking/097_instance_chains/Main.snap @@ -3,11 +3,29 @@ 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 + Quantified = :1 + Kind = :0 + + +Roles +Proxy = [Phantom] + +Classes +class TypeEq (&3 :: &0) (&4 :: &1) (&5 :: &2) + +Instances +instance forall (&0 :: Type). TypeEq (&1 :: &0) (&1 :: &0) (True :: Boolean) + chain: 0 +instance forall (&0 :: Type) (&1 :: Type). 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..d66b0514 100644 --- a/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap +++ b/tests-integration/fixtures/checking/098_fundep_propagation/Main.snap @@ -3,15 +3,44 @@ 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 + Quantified = :1 + Kind = :0 + + +Roles +Proxy = [Phantom] +Z = [] +S = [Nominal] + +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/099_builtin_int/Main.snap b/tests-integration/fixtures/checking/099_builtin_int/Main.snap index 019df2bb..09185331 100644 --- a/tests-integration/fixtures/checking/099_builtin_int/Main.snap +++ b/tests-integration/fixtures/checking/099_builtin_int/Main.snap @@ -27,3 +27,17 @@ forceSolve :: Types Proxy :: forall (k :: Type). k -> Type Unit :: Type + +Data +Proxy + Quantified = :0 + Kind = :1 + +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 ade2c8c5..30feedcf 100644 --- a/tests-integration/fixtures/checking/100_builtin_given/Main.snap +++ b/tests-integration/fixtures/checking/100_builtin_given/Main.snap @@ -39,3 +39,23 @@ MaxN = 5 Quantified = :0 Kind = :0 Type = :0 + + +Data +Proxy + Quantified = :0 + Kind = :1 + +N + Quantified = :0 + Kind = :0 + +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 4f7e4ba5..f7cd5b11 100644 --- a/tests-integration/fixtures/checking/101_builtin_symbol/Main.snap +++ b/tests-integration/fixtures/checking/101_builtin_symbol/Main.snap @@ -37,3 +37,12 @@ forceSolve :: Types Proxy :: forall (k :: Type). k -> Type + +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 44a65daf..23687cf1 100644 --- a/tests-integration/fixtures/checking/102_builtin_row/Main.snap +++ b/tests-integration/fixtures/checking/102_builtin_row/Main.snap @@ -82,3 +82,12 @@ solveRowToList :: Types Proxy :: forall (k :: Type). k -> Type + +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 90ff5b7f..48d84aa2 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,12 @@ Bar :: Foo Types Foo :: Type + +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 4969c0ef..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 @@ -13,3 +13,12 @@ forceSolve :: Types Proxy :: forall (k :: Type). k -> Type + +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 c32afe46..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 @@ -9,3 +9,12 @@ forceSolve :: forall (t7 :: Symbol). Append "hello" t7 "xyz" => { invalid :: Pro Types Proxy :: forall (k :: Type). k -> Type + +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 436c3f0e..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 @@ -9,3 +9,12 @@ forceSolve :: forall (t7 :: Symbol). Cons "hello" t7 "helloworld" => { invalid : Types Proxy :: forall (k :: Type). k -> Type + +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 b443dffa..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 @@ -13,3 +13,12 @@ forceSolve :: Types Proxy :: forall (k :: Type). k -> Type + +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 e860160b..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 @@ -10,5 +10,14 @@ forceSolve :: { invalid :: Proxy @(Row Type) ( a :: Int, b :: String ) } Types Proxy :: forall (k :: Type). k -> Type +Data +Proxy + Quantified = :0 + 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 f2096801..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 @@ -10,5 +10,14 @@ forceSolve :: { invalid :: Proxy @Int 10 } Types Proxy :: forall (k :: Type). k -> Type +Data +Proxy + Quantified = :0 + 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 bd662a1b..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 @@ -10,5 +10,14 @@ forceSolve :: { invalid :: Proxy @Int 10 } Types Proxy :: forall (k :: Type). k -> Type +Data +Proxy + Quantified = :0 + 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 1c6dde5a..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 @@ -10,5 +10,14 @@ forceSolve :: { invalid :: Proxy @Ordering LT } Types Proxy :: forall (k :: Type). k -> Type +Data +Proxy + Quantified = :0 + 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 ba62400e..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 @@ -10,5 +10,14 @@ forceSolve :: { invalid :: Proxy @Symbol "999" } Types Proxy :: forall (k :: Type). k -> Type +Data +Proxy + Quantified = :0 + 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 16eb89be..f3297232 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 @@ -25,5 +25,22 @@ Discard :: (Type -> Type) -> Constraint Bind :: (Type -> Type) -> Constraint Monad :: (Type -> Type) -> Constraint +Data +Tuple + Quantified = :0 + Kind = :0 + + +Roles +Tuple = [Representational, Representational] + +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..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 @@ -8,7 +8,14 @@ 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))] -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/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..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 @@ -9,3 +9,19 @@ Box :: forall (a :: Type). a -> Box a Types Functor :: (Type -> Type) -> Constraint Box :: Type -> Type + +Data +Box + Quantified = :0 + Kind = :0 + + +Roles +Box = [Representational] + +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..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 @@ -15,3 +15,27 @@ Show :: Type -> Constraint Functor :: (Type -> Type) -> Constraint Box :: Type -> Type Maybe :: Type -> Type + +Data +Box + Quantified = :0 + Kind = :0 + +Maybe + Quantified = :0 + Kind = :0 + + +Roles +Box = [Representational] +Maybe = [Representational] + +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..48c1a0f4 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))] +InstanceHeadMismatch { class_file: Idx::(30), 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..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 @@ -12,5 +12,22 @@ Show :: Type -> Constraint Functor :: (Type -> Type) -> Constraint Box :: Type -> Type +Data +Box + Quantified = :0 + Kind = :0 + + +Roles +Box = [Representational] + +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..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 @@ -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))] +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.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..30689b33 --- /dev/null +++ b/tests-integration/fixtures/checking/126_instance_phantom/Main.snap @@ -0,0 +1,28 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +identity :: forall (a :: Type). Phantom a => a -> a +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 (t1 :: Type). t1 -> Type + +Data +Proxy + Quantified = :1 + Kind = :0 + + +Roles +Proxy = [Phantom] + +Classes +class Phantom (&0 :: Type) + +Instances +instance forall (&0 :: Type). Phantom (Proxy @&0 &1 :: Type) + chain: 0 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..a2d03a12 --- /dev/null +++ b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.purs @@ -0,0 +1,13 @@ +module Main where + +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 new file mode 100644 index 00000000..01ef762c --- /dev/null +++ b/tests-integration/fixtures/checking/127_derive_eq_simple/Main.snap @@ -0,0 +1,39 @@ +--- +source: tests-integration/tests/checking/generated.rs +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 + + +Roles +Proxy = [Phantom] +NoEq = [] +ContainsNoEq = [] + +Derived +derive forall (&0 :: Type). Eq (Proxy @&0 &1 :: Type) +derive Eq (ContainsNoEq :: Type) + +Errors +NoInstanceFound { Eq NoEq } at [TermDeclaration(Idx::(4))] 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..e69231b5 --- /dev/null +++ b/tests-integration/fixtures/checking/128_type_operator_mutual/Main.snap @@ -0,0 +1,19 @@ +--- +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 + + +Roles +Add = [Representational, Representational] 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..f1148a15 --- /dev/null +++ b/tests-integration/fixtures/checking/129_derive_eq_with_fields/Main.snap @@ -0,0 +1,29 @@ +--- +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 + + +Roles +Box = [] +Pair = [] + +Derived +derive Eq (Box :: Type) +derive Eq (Pair :: Type) 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..000c34be --- /dev/null +++ b/tests-integration/fixtures/checking/130_derive_eq_parameterized/Main.snap @@ -0,0 +1,22 @@ +--- +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] + +Derived +derive Eq &0 => Eq (Maybe &0 :: Type) 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..8aaea864 --- /dev/null +++ b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.purs @@ -0,0 +1,9 @@ +module Main where + +import Data.Eq (class Eq) + +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 new file mode 100644 index 00000000..72d81bda --- /dev/null +++ b/tests-integration/fixtures/checking/131_derive_eq_missing_instance/Main.snap @@ -0,0 +1,31 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +MkNoEq :: NoEq +MkBox :: NoEq -> Box + +Types +NoEq :: Type +Box :: Type + +Data +NoEq + Quantified = :0 + Kind = :0 + +Box + Quantified = :0 + Kind = :0 + + +Roles +NoEq = [] +Box = [] + +Derived +derive Eq (Box :: Type) + +Errors +NoInstanceFound { Eq NoEq } at [TermDeclaration(Idx::(2))] 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..fe7b919e --- /dev/null +++ b/tests-integration/fixtures/checking/132_derive_eq_1_higher_kinded/Main.snap @@ -0,0 +1,32 @@ +--- +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 + + +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) + +Errors +NoInstanceFound { Eq1 &0 } at [TermDeclaration(Idx::(3))] 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..65d7fda5 --- /dev/null +++ b/tests-integration/fixtures/checking/133_derive_eq_partial/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 + +Types +Either :: Type -> Type -> Type + +Data +Either + Quantified = :0 + Kind = :0 + + +Roles +Either = [Representational, Representational] + +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/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..ffce0587 --- /dev/null +++ b/tests-integration/fixtures/checking/134_derive_ord_simple/Main.snap @@ -0,0 +1,51 @@ +--- +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 + + +Roles +Box = [] +Pair = [] +NoOrd = [] +ContainsNoOrd = [] + +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 { Ord NoOrd } at [TermDeclaration(Idx::(10))] 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..069f554b --- /dev/null +++ b/tests-integration/fixtures/checking/135_derive_ord_1_higher_kinded/Main.snap @@ -0,0 +1,35 @@ +--- +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 + + +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) +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))] +NoInstanceFound { Eq1 &0 } at [TermDeclaration(Idx::(5))] 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..3bffb8dd --- /dev/null +++ b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap @@ -0,0 +1,56 @@ +--- +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 + + +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) +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 { 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))] 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..73271ea5 --- /dev/null +++ b/tests-integration/fixtures/checking/137_derive_newtype_simple/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Wrapper :: Int -> Wrapper + +Types +Wrapper :: Type + +Data +Wrapper + Quantified = :0 + Kind = :0 + + +Roles +Wrapper = [] + +Derived +derive Show (Wrapper :: Type) 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..0bf73312 --- /dev/null +++ b/tests-integration/fixtures/checking/138_derive_newtype_parameterized/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 + + +Roles +Identity = [Representational] + +Derived +derive Show (Identity Int :: Type) 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..1af357a5 --- /dev/null +++ b/tests-integration/fixtures/checking/139_derive_newtype_with_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 + + +Roles +Identity = [Representational] + +Derived +derive Show &0 => Show (Identity &0 :: Type) 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..a43f2a32 --- /dev/null +++ b/tests-integration/fixtures/checking/140_derive_newtype_recursive/Main.snap @@ -0,0 +1,21 @@ +--- +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 + + +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.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..71582a9d --- /dev/null +++ b/tests-integration/fixtures/checking/141_derive_newtype_phantom/Main.snap @@ -0,0 +1,21 @@ +--- +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 + + +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.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..b57fee0c --- /dev/null +++ b/tests-integration/fixtures/checking/142_derive_newtype_not_newtype/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +Foo :: Int -> Foo + +Types +Foo :: Type + +Data +Foo + Quantified = :0 + 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.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..6fdb0231 --- /dev/null +++ b/tests-integration/fixtures/checking/143_derive_newtype_missing_instance/Main.snap @@ -0,0 +1,24 @@ +--- +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 + + +Roles +Identity = [Representational] + +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/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..1ce47204 --- /dev/null +++ b/tests-integration/fixtures/checking/144_derive_newtype_missing_given/Main.snap @@ -0,0 +1,24 @@ +--- +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 + + +Roles +Identity = [Representational] + +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/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..d0998052 --- /dev/null +++ b/tests-integration/fixtures/checking/145_derive_newtype_multi_param/Main.snap @@ -0,0 +1,21 @@ +--- +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 + + +Roles +Pair = [Representational, Phantom] + +Derived +derive Show (Pair @Type Int String :: Type) 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..4b99ee66 --- /dev/null +++ b/tests-integration/fixtures/checking/146_derive_functor_simple/Main.snap @@ -0,0 +1,38 @@ +--- +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 + + +Roles +Identity = [Representational] +Const = [Representational, Phantom] +Maybe = [Representational] + +Derived +derive Functor (Identity :: Type -> 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.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..c511ba0a --- /dev/null +++ b/tests-integration/fixtures/checking/147_derive_functor_higher_kinded/Main.snap @@ -0,0 +1,32 @@ +--- +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 + + +Roles +Wrap = [Representational, Nominal] +WrapNoFunctor = [Representational, Nominal] + +Derived +derive Functor &0 => Functor (Wrap @Type &0 :: Type -> 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.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..bda806e5 --- /dev/null +++ b/tests-integration/fixtures/checking/148_derive_functor_contravariant_error/Main.snap @@ -0,0 +1,40 @@ +--- +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 + + +Roles +Predicate = [Representational] +Reader = [Representational, Representational] +Cont = [Representational, Representational] + +Derived +derive Functor (Predicate :: Type -> Type) +derive Functor (Reader &0 :: Type -> Type) +derive Functor (Cont &0 :: Type -> Type) + +Errors +ContravariantOccurrence { type_id: Id(44) } at [TermDeclaration(Idx::(1))] 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..b659adfc --- /dev/null +++ b/tests-integration/fixtures/checking/149_derive_bifunctor_simple/Main.snap @@ -0,0 +1,39 @@ +--- +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 + + +Roles +Either = [Representational, Representational] +Pair = [Representational, Representational] +Const2 = [Representational, Phantom, Phantom] + +Derived +derive Bifunctor (Either :: Type -> Type -> Type) +derive Bifunctor (Pair :: Type -> Type -> Type) +derive Bifunctor (Const2 @Type @Type &0 :: Type -> Type -> Type) 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..2af3fdac --- /dev/null +++ b/tests-integration/fixtures/checking/150_derive_bifunctor_higher_kinded/Main.snap @@ -0,0 +1,38 @@ +--- +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 + + +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) + +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.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/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..3c19e666 --- /dev/null +++ b/tests-integration/fixtures/checking/151_derive_bifunctor_missing_functor/Main.snap @@ -0,0 +1,27 @@ +--- +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 + + +Roles +WrapBoth = [Representational, Representational, Nominal, Nominal] + +Derived +derive Bifunctor (WrapBoth @Type @Type &0 &1 :: Type -> Type -> Type) + +Errors +DeriveMissingFunctor at [TermDeclaration(Idx::(1))] +DeriveMissingFunctor at [TermDeclaration(Idx::(1))] 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..dff8c108 --- /dev/null +++ b/tests-integration/fixtures/checking/152_derive_contravariant_simple/Main.snap @@ -0,0 +1,37 @@ +--- +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 + + +Roles +Predicate = [Representational] +Comparison = [Representational] +Op = [Representational, Representational] + +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/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..261f65b8 --- /dev/null +++ b/tests-integration/fixtures/checking/153_derive_contravariant_error/Main.snap @@ -0,0 +1,33 @@ +--- +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 + + +Roles +Identity = [Representational] +Producer = [Representational] + +Derived +derive Contravariant (Identity :: Type -> Type) +derive Contravariant (Producer :: Type -> Type) + +Errors +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.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..bdf61386 --- /dev/null +++ b/tests-integration/fixtures/checking/154_derive_profunctor_simple/Main.snap @@ -0,0 +1,38 @@ +--- +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 + + +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) +derive Profunctor (Choice :: Type -> Type -> Type) 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..f123fd92 --- /dev/null +++ b/tests-integration/fixtures/checking/155_derive_profunctor_error/Main.snap @@ -0,0 +1,34 @@ +--- +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 + + +Roles +WrongFirst = [Representational, Representational] +WrongSecond = [Representational, Representational] + +Derived +derive Profunctor (WrongFirst :: Type -> Type -> Type) +derive Profunctor (WrongSecond :: Type -> Type -> Type) + +Errors +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.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..65f6d595 --- /dev/null +++ b/tests-integration/fixtures/checking/156_derive_bifunctor_insufficient_params/Main.snap @@ -0,0 +1,25 @@ +--- +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 + + +Roles +Triple = [Representational, Representational, Representational] + +Derived +derive Bifunctor (Triple Int String :: Type -> Type) + +Errors +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.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..eb192a78 --- /dev/null +++ b/tests-integration/fixtures/checking/157_derive_functor_insufficient_params/Main.snap @@ -0,0 +1,35 @@ +--- +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 + + +Roles +Pair = [Representational, Representational] +Unit = [] + +Derived +derive Functor (Pair Int String :: Type) +derive Functor (Unit :: Type) + +Errors +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))] 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..cdbb704d --- /dev/null +++ b/tests-integration/fixtures/checking/158_derive_foldable_simple/Main.snap @@ -0,0 +1,38 @@ +--- +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 + + +Roles +Identity = [Representational] +Maybe = [Representational] +Const = [Representational, Phantom] + +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/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..282a1b5b --- /dev/null +++ b/tests-integration/fixtures/checking/159_derive_foldable_higher_kinded/Main.snap @@ -0,0 +1,32 @@ +--- +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 + + +Roles +Wrap = [Representational, Nominal] +WrapNoFoldable = [Representational, Nominal] + +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/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..ef2d2efb --- /dev/null +++ b/tests-integration/fixtures/checking/160_derive_bifoldable_simple/Main.snap @@ -0,0 +1,39 @@ +--- +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 + + +Roles +Either = [Representational, Representational] +Pair = [Representational, Representational] +Const2 = [Representational, Phantom, Phantom] + +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/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..d9b8fc33 --- /dev/null +++ b/tests-integration/fixtures/checking/161_derive_bifoldable_higher_kinded/Main.snap @@ -0,0 +1,38 @@ +--- +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 + + +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) + +Errors +NoInstanceFound { Foldable &0 } at [TermDeclaration(Idx::(3))] +NoInstanceFound { Foldable &1 } at [TermDeclaration(Idx::(3))] 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..a374bd51 --- /dev/null +++ b/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.purs @@ -0,0 +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 new file mode 100644 index 00000000..eb4c896c --- /dev/null +++ b/tests-integration/fixtures/checking/162_derive_traversable_simple/Main.snap @@ -0,0 +1,44 @@ +--- +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 + + +Roles +Identity = [Representational] +Maybe = [Representational] +Const = [Representational, Phantom] + +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 new file mode 100644 index 00000000..d2402adf --- /dev/null +++ b/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.purs @@ -0,0 +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 new file mode 100644 index 00000000..8493bfee --- /dev/null +++ b/tests-integration/fixtures/checking/163_derive_traversable_higher_kinded/Main.snap @@ -0,0 +1,25 @@ +--- +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 + + +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) +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..9f0dc4ba --- /dev/null +++ b/tests-integration/fixtures/checking/164_derive_bitraversable_simple/Main.purs @@ -0,0 +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 new file mode 100644 index 00000000..922cf920 --- /dev/null +++ b/tests-integration/fixtures/checking/164_derive_bitraversable_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 + +Types +Either :: Type -> Type -> Type +Pair :: Type -> Type -> Type + +Data +Either + Quantified = :0 + Kind = :0 + +Pair + Quantified = :0 + Kind = :0 + + +Roles +Either = [Representational, Representational] +Pair = [Representational, Representational] + +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 new file mode 100644 index 00000000..00777010 --- /dev/null +++ b/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.purs @@ -0,0 +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 new file mode 100644 index 00000000..e5d4e8d8 --- /dev/null +++ b/tests-integration/fixtures/checking/165_derive_bitraversable_higher_kinded/Main.snap @@ -0,0 +1,25 @@ +--- +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 + + +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) +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..b734c3c8 --- /dev/null +++ b/tests-integration/fixtures/checking/166_derive_traversable_missing_superclass/Main.snap @@ -0,0 +1,27 @@ +--- +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 + + +Roles +Compose = [Representational, Representational, Nominal] + +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/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..4b36ecd6 --- /dev/null +++ b/tests-integration/fixtures/checking/167_derive_eq_1/Main.snap @@ -0,0 +1,91 @@ +--- +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 + + +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) +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..5e0d4172 --- /dev/null +++ b/tests-integration/fixtures/checking/168_derive_ord_1/Main.snap @@ -0,0 +1,63 @@ +--- +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 + + +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) +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/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..1ee274d7 --- /dev/null +++ b/tests-integration/fixtures/checking/169_derive_newtype_class_simple/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +UserId :: Int -> UserId + +Types +UserId :: Type + +Data +UserId + Quantified = :0 + Kind = :0 + + +Roles +UserId = [] + +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..d292637b --- /dev/null +++ b/tests-integration/fixtures/checking/170_derive_newtype_class_parameterized/Main.snap @@ -0,0 +1,21 @@ +--- +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 + + +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.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..80d37ac7 --- /dev/null +++ b/tests-integration/fixtures/checking/171_derive_newtype_class_not_newtype/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking/generated.rs +expression: report +--- +Terms +NotANewtype :: Int -> NotANewtype + +Types +NotANewtype :: Type + +Data +NotANewtype + Quantified = :0 + 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.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..57b576fa --- /dev/null +++ b/tests-integration/fixtures/checking/172_derive_generic_simple/Main.snap @@ -0,0 +1,85 @@ +--- +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 + + +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) +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..780692ee --- /dev/null +++ b/tests-integration/fixtures/checking/173_derive_newtype_class_coercible/Main.snap @@ -0,0 +1,33 @@ +--- +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 + + +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/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..4fa9f60e --- /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::(30), item_id: Idx::(0) } at [TermDeclaration(Idx::(0))] +NoInstanceFound { Coercible @Type Int HiddenAge } at [TermDeclaration(Idx::(0))] +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/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..cffd0690 --- /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::(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/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/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/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/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/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/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.Bifoldable.purs b/tests-integration/fixtures/checking/prelude/Data.Bifoldable.purs new file mode 100644 index 00000000..df7bf51e --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/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/prelude/Data.Bifunctor.purs b/tests-integration/fixtures/checking/prelude/Data.Bifunctor.purs new file mode 100644 index 00000000..54b5e06b --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/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/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.Eq.purs b/tests-integration/fixtures/checking/prelude/Data.Eq.purs new file mode 100644 index 00000000..9235fde9 --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/Data.Eq.purs @@ -0,0 +1,13 @@ +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 + +instance Eq Boolean where + eq _ _ = true diff --git a/tests-integration/fixtures/checking/prelude/Data.Foldable.purs b/tests-integration/fixtures/checking/prelude/Data.Foldable.purs new file mode 100644 index 00000000..103fe1eb --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/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/prelude/Data.Functor.Contravariant.purs b/tests-integration/fixtures/checking/prelude/Data.Functor.Contravariant.purs new file mode 100644 index 00000000..61246c10 --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/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/prelude/Data.Functor.purs b/tests-integration/fixtures/checking/prelude/Data.Functor.purs new file mode 100644 index 00000000..354b844e --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/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/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/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/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.Profunctor.purs b/tests-integration/fixtures/checking/prelude/Data.Profunctor.purs new file mode 100644 index 00000000..4eaf5dcf --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/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/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.Show.purs b/tests-integration/fixtures/checking/prelude/Data.Show.purs new file mode 100644 index 00000000..5c5866e8 --- /dev/null +++ b/tests-integration/fixtures/checking/prelude/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/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/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/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/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/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/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 }) diff --git a/tests-integration/src/generated/basic.rs b/tests-integration/src/generated/basic.rs index b6f7aa2f..6dbfa91c 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, 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(); + } } } @@ -310,44 +287,211 @@ 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.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(); + } + 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 { + 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(); + } + } + + 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_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(); + } + if !checked.errors.is_empty() { 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(); + } + AmbiguousConstraint { constraint } => { + writeln!(snapshot, "AmbiguousConstraint {{ {} }} at {step:?}", pp(constraint)) + .unwrap(); } - checking::error::ErrorKind::AmbiguousConstraint { constraint } => { - let constraint_pretty = pretty::print_global(engine, constraint); + InstanceMemberTypeMismatch { expected, actual } => { writeln!( snapshot, - "AmbiguousConstraint {{ {constraint_pretty} }} at {:?}", - &error.step + "InstanceMemberTypeMismatch {{ expected: {}, actual: {} }} at {step:?}", + pp(expected), + pp(actual) ) .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 {:?}", 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(" ")) +} 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); }); diff --git a/tests-integration/tests/checking/generated.rs b/tests-integration/tests/checking/generated.rs index 3a484515..80117684 100644 --- a/tests-integration/tests/checking/generated.rs +++ b/tests-integration/tests/checking/generated.rs @@ -261,3 +261,165 @@ 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"); } + +#[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"); } + +#[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"); } + +#[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"); } + +#[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"); } + +#[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"); } + +#[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"); } + +#[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"); } + +#[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"); } + +#[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"); } + +#[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"); } + +#[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"); } + +#[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"); } + +#[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/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/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..2cfe121b 100644 --- a/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap +++ b/tests-integration/tests/snapshots/checking__invalid_type_operator_ternary.snap @@ -7,10 +7,6 @@ expression: checked.errors kind: InvalidTypeOperator { id: Id(47), }, - 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..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,12 +5,8 @@ expression: checked.errors [ CheckError { kind: InvalidTypeOperator { - id: Id(29), + id: Id(35), }, - 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), - ), - ], - }, ] 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..dd100f5e 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,14 @@ 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 ); }