diff --git a/.claude/skills/type-checker-tests/SKILL.md b/.agents/skills/type-checker-tests/SKILL.md similarity index 81% rename from .claude/skills/type-checker-tests/SKILL.md rename to .agents/skills/type-checker-tests/SKILL.md index 0a42f19a..84e5d461 100644 --- a/.claude/skills/type-checker-tests/SKILL.md +++ b/.agents/skills/type-checker-tests/SKILL.md @@ -1,12 +1,12 @@ --- name: type-checker-tests -description: Add integration tests for type checker inference and checking functions +description: Add integration tests for type checker inference and checking2 functions allowed-tools: Bash(mkdir:*) --- # Type Checker Integration Tests -Use the command reference at `reference/compiler-scripts.md` for test runner syntax, snapshot workflows, filters, and trace debugging. The category is `checking`. +Use the command reference at `reference/compiler-scripts.md` for test runner syntax, snapshot workflows, filters, and trace debugging. The category is `checking2`. **Language:** Fixtures use PureScript syntax, not Haskell. @@ -15,7 +15,7 @@ Use the command reference at `reference/compiler-scripts.md` for test runner syn ### 1. Create fixture directory ```bash -just t checking --create "descriptive name" +just t checking2 --create "descriptive name" ``` The CLI picks the next fixture number and creates the folder. @@ -24,7 +24,7 @@ Tests are auto-discovered by `build.rs`. ### 2. Write Main.purs -**Standard pattern** - pair typed (checking) and untyped (inference) variants: +**Standard pattern** - pair typed (checking2) and untyped (inference) variants: ```purescript module Main where @@ -45,7 +45,16 @@ test' [x] = x ### 3. Run and review ```bash -just t checking NNN MMM +just t checking2 NNN MMM +``` + +### 4. Accept or reject snapshots + +```bash +just t checking2 NNN --diff # Inspect a fixture diff +just t checking2 NNN --accept # Accept a specific fixture +just t checking2 NNN --reject # Reject a specific fixture +just t checking2 --accept --confirm # Accept all pending snapshots ``` ## Multi-File Tests @@ -53,7 +62,7 @@ just t checking NNN MMM For imports, re-exports, or cross-module behavior: ``` -tests-integration/fixtures/checking/NNN_import_test/ +tests-integration/fixtures/checking2/NNN_import_test/ ├── Main.purs # Test file (snapshot generated) ├── Lib.purs # Supporting module └── Main.snap # Generated snapshot diff --git a/.claude/skills/type-checker-tests/reference/compiler-scripts.md b/.agents/skills/type-checker-tests/reference/compiler-scripts.md similarity index 85% rename from .claude/skills/type-checker-tests/reference/compiler-scripts.md rename to .agents/skills/type-checker-tests/reference/compiler-scripts.md index aac183a6..5e869f5f 100644 --- a/.claude/skills/type-checker-tests/reference/compiler-scripts.md +++ b/.agents/skills/type-checker-tests/reference/compiler-scripts.md @@ -28,11 +28,12 @@ just t --delete "name" # Dry-run fixture deletion (use --confirm) ### Snapshot commands ```bash -just t accept [--all] [filters...] # Accept pending snapshots -just t reject [--all] [filters...] # Reject pending snapshots +just t [filters...] --accept # Accept matching snapshots +just t [filters...] --reject # Reject matching snapshots +just t --accept --confirm # Accept all pending snapshots ``` -Requires `--all` flag when no filters provided (safety guardrail). +For safety, unfiltered `--accept` requires `--confirm`. ### Exclusion filters diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 00000000..2b7a412b --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8850f009..4d5e195f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,6 +225,7 @@ version = "0.1.0" dependencies = [ "building-types", "checking", + "checking2", "files", "indexing", "interner", @@ -237,6 +238,7 @@ dependencies = [ "prim-constants", "resolving", "rustc-hash 2.1.1", + "smol_str", "stabilizing", "sugar", "tempfile", @@ -325,6 +327,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "checking2" +version = "0.1.0" +dependencies = [ + "building-types", + "files", + "indexing", + "interner", + "itertools 0.14.0", + "lowering", + "parsing", + "petgraph", + "pretty", + "resolving", + "rustc-hash 2.1.1", + "smol_str", + "stabilizing", + "sugar", +] + [[package]] name = "chrono" version = "0.4.43" @@ -2616,6 +2638,7 @@ dependencies = [ "analyzer", "async-lsp", "checking", + "checking2", "diagnostics", "files", "glob", diff --git a/compiler-compatibility/command/src/compat.rs b/compiler-compatibility/command/src/compat.rs index 06975d09..f411b6c8 100644 --- a/compiler-compatibility/command/src/compat.rs +++ b/compiler-compatibility/command/src/compat.rs @@ -11,9 +11,8 @@ use petgraph::graphmap::DiGraphMap; use rayon::prelude::*; use url::Url; -use crate::loader; -use crate::resolver; use crate::types::ResolvedSet; +use crate::{loader, resolver}; /// Result of checking a single file. pub struct FileResult { @@ -415,10 +414,11 @@ fn find_package_dir(packages_dir: &Path, package_name: &str) -> Option().is_ok() && entry.file_type().ok()?.is_dir() { - return entry.path().canonicalize().ok(); - } + if let Some(suffix) = name_str.strip_prefix(&prefix) + && suffix.parse::().is_ok() + && entry.file_type().ok()?.is_dir() + { + return entry.path().canonicalize().ok(); } } None diff --git a/compiler-core/building-types/src/lib.rs b/compiler-core/building-types/src/lib.rs index 4332f1f4..ceaafe70 100644 --- a/compiler-core/building-types/src/lib.rs +++ b/compiler-core/building-types/src/lib.rs @@ -19,6 +19,7 @@ pub enum QueryKey { Bracketed(FileId), Sectioned(FileId), Checked(FileId), + Checked2(FileId), } #[derive(Error, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/compiler-core/building/Cargo.toml b/compiler-core/building/Cargo.toml index 1b118af3..586d9fc9 100644 --- a/compiler-core/building/Cargo.toml +++ b/compiler-core/building/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] building-types = { version = "0.1.0", path = "../building-types" } checking = { version = "0.1.0", path = "../checking" } +checking2 = { version = "0.1.0", path = "../checking2" } files = { version = "0.1.0", path = "../files" } indexing = { version = "0.1.0", path = "../indexing" } interner = { version = "0.1.0", path = "../interner" } @@ -19,6 +20,7 @@ resolving = { version = "0.1.0", path = "../resolving" } rustc-hash = "2.1.1" stabilizing = { version = "0.1.0", path = "../stabilizing" } sugar = { version = "0.1.0", path = "../sugar" } +smol_str = "0.3.2" tempfile = "3.24.0" thread_local = "1.1.9" url = "2.5.7" diff --git a/compiler-core/building/src/engine.rs b/compiler-core/building/src/engine.rs index a26d0436..c3ea1a50 100644 --- a/compiler-core/building/src/engine.rs +++ b/compiler-core/building/src/engine.rs @@ -34,6 +34,7 @@ use building_types::{ ModuleNameId, ModuleNameInterner, QueryError, QueryKey, QueryProxy, QueryResult, }; use checking::{CheckedModule, TypeInterner}; +use checking2::CheckedModule as CheckedModule2; use files::FileId; use graph::SnapshotGraph; use indexing::IndexedModule; @@ -99,12 +100,14 @@ struct DerivedStorage { bracketed: FxHashMap>>, sectioned: FxHashMap>>, checked: FxHashMap>>, + checked2: FxHashMap>>, } #[derive(Default)] struct InternedStorage { module: ModuleNameInterner, types: TypeInterner, + checking2: checking2::CoreInterners, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -428,6 +431,7 @@ impl QueryEngine { QueryKey::Bracketed(k) => derived_changed!(bracketed, k), QueryKey::Sectioned(k) => derived_changed!(sectioned, k), QueryKey::Checked(k) => derived_changed!(checked, k), + QueryKey::Checked2(k) => derived_changed!(checked2, k), } } @@ -757,6 +761,18 @@ impl QueryEngine { }, ) } + + pub fn checked2(&self, id: FileId) -> QueryResult> { + self.query( + QueryKey::Checked2(id), + |storage| storage.derived.checked2.get(&id), + |storage| storage.derived.checked2.entry(id), + |this| { + let checked = checking2::check_module(this, id)?; + Ok(Arc::new(checked)) + }, + ) + } } impl QueryEngine { @@ -841,6 +857,68 @@ impl checking::ExternalQueries for QueryEngine { } } +impl checking2::ExternalQueries for QueryEngine { + fn checked2(&self, id: FileId) -> QueryResult> { + QueryEngine::checked2(self, id) + } + + fn intern_type(&self, t: checking2::core::Type) -> checking2::core::TypeId { + let mut storage = self.storage.write(); + storage.interned.checking2.intern_type(t) + } + + fn lookup_type(&self, id: checking2::core::TypeId) -> checking2::core::Type { + let storage = self.storage.read(); + storage.interned.checking2.lookup_type(id) + } + + fn intern_forall_binder( + &self, + binder: checking2::core::ForallBinder, + ) -> checking2::core::ForallBinderId { + let mut storage = self.storage.write(); + storage.interned.checking2.intern_forall_binder(binder) + } + + fn lookup_forall_binder( + &self, + id: checking2::core::ForallBinderId, + ) -> checking2::core::ForallBinder { + let storage = self.storage.read(); + storage.interned.checking2.lookup_forall_binder(id) + } + + fn intern_row_type(&self, row: checking2::core::RowType) -> checking2::core::RowTypeId { + let mut storage = self.storage.write(); + storage.interned.checking2.intern_row_type(row) + } + + fn lookup_row_type(&self, id: checking2::core::RowTypeId) -> checking2::core::RowType { + let storage = self.storage.read(); + storage.interned.checking2.lookup_row_type(id) + } + + fn intern_synonym(&self, synonym: checking2::core::Synonym) -> checking2::core::SynonymId { + let mut storage = self.storage.write(); + storage.interned.checking2.intern_synonym(synonym) + } + + fn lookup_synonym(&self, id: checking2::core::SynonymId) -> checking2::core::Synonym { + let storage = self.storage.read(); + storage.interned.checking2.lookup_synonym(id) + } + + fn intern_smol_str(&self, s: smol_str::SmolStr) -> checking2::core::SmolStrId { + let mut storage = self.storage.write(); + storage.interned.checking2.intern_smol_str(s) + } + + fn lookup_smol_str(&self, id: checking2::core::SmolStrId) -> smol_str::SmolStr { + let storage = self.storage.read(); + storage.interned.checking2.lookup_smol_str(id) + } +} + impl resolving::ExternalQueries for QueryEngine {} impl sugar::ExternalQueries for QueryEngine {} 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 index a6c1b310..77d40d0d 100644 --- a/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_row.rs +++ b/compiler-core/checking/src/algorithm/constraint/compiler_solved/prim_row.rs @@ -4,12 +4,11 @@ use std::iter; use building_types::QueryResult; use rustc_hash::FxHashSet; -use crate::ExternalQueries; use crate::algorithm::constraint::{self, MatchInstance}; use crate::algorithm::state::{CheckContext, CheckState}; use crate::algorithm::toolkit; use crate::core::{RowField, RowType}; -use crate::{Type, TypeId}; +use crate::{ExternalQueries, Type, TypeId}; use super::extract_symbol; @@ -127,7 +126,10 @@ where .storage .intern(Type::Row(RowType::from_unsorted(union_fields, right_row.tail))); - Ok(Some(MatchInstance::Match { constraints: vec![], equalities: vec![(union, result)] })) + Ok(Some(MatchInstance::Match { + constraints: vec![], + equalities: vec![(union, result)], + })) } (_, Some(right_row), Some(union_row)) => { if right_row.tail.is_some() { diff --git a/compiler-core/checking2/Cargo.toml b/compiler-core/checking2/Cargo.toml new file mode 100644 index 00000000..da9d7892 --- /dev/null +++ b/compiler-core/checking2/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "checking2" +version = "0.1.0" +edition = "2024" + +[dependencies] +building-types = { version = "0.1.0", path = "../building-types" } +files = { version = "0.1.0", path = "../files" } +indexing = { version = "0.1.0", path = "../indexing" } +interner = { version = "0.1.0", path = "../interner" } +itertools = "0.14.0" +lowering = { version = "0.1.0", path = "../lowering" } +petgraph = "0.8.3" +pretty = "0.12" +parsing = { version = "0.1.0", path = "../parsing" } +resolving = { version = "0.1.0", path = "../resolving" } +rustc-hash = "2.1.1" +smol_str = "0.3.5" +stabilizing = { version = "0.1.0", path = "../stabilizing" } +sugar = { version = "0.1.0", path = "../sugar" } diff --git a/compiler-core/checking2/src/context.rs b/compiler-core/checking2/src/context.rs new file mode 100644 index 00000000..1ed66916 --- /dev/null +++ b/compiler-core/checking2/src/context.rs @@ -0,0 +1,718 @@ +//! Read-only environment for the type checking algorithm. +//! +//! See documentation for [`CheckContext`] for more information. + +use std::sync::Arc; + +use building_types::QueryResult; +use files::FileId; +use indexing::{IndexedModule, TermItemId, TypeItemId}; +use lowering::{GroupedModule, LoweredModule}; +use resolving::ResolvedModule; +use smol_str::SmolStr; +use stabilizing::StabilizedModule; +use sugar::{Bracketed, Sectioned}; + +use crate::ExternalQueries; +use crate::core::{ + Depth, ForallBinder, ForallBinderId, Name, RowType, RowTypeId, Synonym, SynonymId, Type, TypeId, +}; + +/// The read-only environment threaded through the type checking algorithm. +/// +/// This structure holds a reference to [`ExternalQueries`] for interning and +/// making build system queries; Arc references to query results for the current +/// module; and cached lookups for modules and items 'known' by the compiler. +pub struct CheckContext<'q, Q> +where + Q: ExternalQueries, +{ + pub queries: &'q 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_terms: KnownTermsCore, + pub known_reflectable: KnownReflectableCore, + pub known_generic: Option, + + pub id: FileId, + pub stabilized: Arc, + 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<'q, Q> CheckContext<'q, Q> +where + Q: ExternalQueries, +{ + pub fn new(queries: &'q Q, id: FileId) -> QueryResult> { + let stabilized = queries.stabilized(id)?; + 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 resolved = queries.resolved(id)?; + + let prim = PrimCore::collect(queries)?; + let prim_int = PrimIntCore::collect(queries)?; + let prim_boolean = PrimBooleanCore::collect(queries)?; + let prim_ordering = PrimOrderingCore::collect(queries)?; + let prim_symbol = PrimSymbolCore::collect(queries)?; + let prim_row = PrimRowCore::collect(queries)?; + let prim_row_list = PrimRowListCore::collect(queries)?; + let prim_coerce = PrimCoerceCore::collect(queries)?; + let prim_type_error = PrimTypeErrorCore::collect(queries)?; + let known_types = KnownTypesCore::collect(queries)?; + let known_terms = KnownTermsCore::collect(queries)?; + let known_reflectable = KnownReflectableCore::collect(queries)?; + let known_generic = KnownGeneric::collect(queries)?; + + 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_terms, + known_reflectable, + known_generic, + id, + stabilized, + indexed, + lowered, + grouped, + bracketed, + sectioned, + resolved, + prim_indexed, + prim_resolved, + }) + } +} + +impl<'q, Q> CheckContext<'q, Q> +where + Q: ExternalQueries, +{ + /// Creates an [`Type::Unknown`] type with a descriptive label. + pub fn unknown(&self, label: &str) -> TypeId { + let label = self.queries.intern_smol_str(SmolStr::new(label)); + self.queries.intern_type(Type::Unknown(label)) + } + + /// Interns a [`Type::Application`] node. + pub fn intern_application(&self, function: TypeId, argument: TypeId) -> TypeId { + self.queries.intern_type(Type::Application(function, argument)) + } + + /// Interns a [`Type::KindApplication`] node. + pub fn intern_kind_application(&self, function: TypeId, argument: TypeId) -> TypeId { + self.queries.intern_type(Type::KindApplication(function, argument)) + } + + /// Interns a [`Type::SynonymApplication`] node. + pub fn intern_synonym_application(&self, synonym_id: SynonymId) -> TypeId { + self.queries.intern_type(Type::SynonymApplication(synonym_id)) + } + + /// Interns a [`Type::Forall`] node. + pub fn intern_forall(&self, binder_id: ForallBinderId, inner: TypeId) -> TypeId { + self.queries.intern_type(Type::Forall(binder_id, inner)) + } + + /// Interns a [`Type::Constrained`] node. + pub fn intern_constrained(&self, constraint: TypeId, inner: TypeId) -> TypeId { + self.queries.intern_type(Type::Constrained(constraint, inner)) + } + + /// Interns a [`Type::Function`] node. + pub fn intern_function(&self, argument: TypeId, result: TypeId) -> TypeId { + self.queries.intern_type(Type::Function(argument, result)) + } + + /// Interns a right-associated function chain from arguments to result. + pub fn intern_function_chain(&self, arguments: &[TypeId], result: TypeId) -> TypeId { + let arguments = arguments.iter().copied(); + self.intern_function_chain_iter(arguments, result) + } + + /// Interns a right-associated function chain from iterator arguments to result. + pub fn intern_function_chain_iter(&self, arguments: I, result: TypeId) -> TypeId + where + I: IntoIterator, + I::IntoIter: DoubleEndedIterator, + { + arguments + .into_iter() + .rfold(result, |result, argument| self.intern_function(argument, result)) + } + + /// Interns a [`Type::Kinded`] node. + pub fn intern_kinded(&self, inner: TypeId, kind: TypeId) -> TypeId { + self.queries.intern_type(Type::Kinded(inner, kind)) + } + + /// Interns a [`Type::Row`] node. + pub fn intern_row(&self, row_id: RowTypeId) -> TypeId { + self.queries.intern_type(Type::Row(row_id)) + } + + /// Interns a [`Type::Rigid`] node. + pub fn intern_rigid(&self, name: Name, depth: Depth, kind: TypeId) -> TypeId { + self.queries.intern_type(Type::Rigid(name, depth, kind)) + } + + /// Interns a [`Type::Application`]-based function. + /// + /// The types `Function a b` and `a -> b` are equivalent, represented by + /// [`Type::Application`] and [`Type::Function`] respectively. Normalising + /// into the application-based form is generally more useful, such as in + /// the following example: + /// + /// ```text + /// unify(?function_a b, a -> b) + /// unify(?function_a b, Function a b) = [ ?function_a := Function a ] + /// ``` + pub fn intern_function_application(&self, argument: TypeId, result: TypeId) -> TypeId { + let function_argument = self.intern_application(self.prim.function, argument); + self.intern_application(function_argument, result) + } + + /// Looks up the [`Type`] for the given [`TypeId`]. + pub fn lookup_type(&self, id: TypeId) -> Type { + self.queries.lookup_type(id) + } + + /// Looks up the [`ForallBinder`] for the given [`ForallBinderId`]. + pub fn lookup_forall_binder(&self, id: ForallBinderId) -> ForallBinder { + self.queries.lookup_forall_binder(id) + } + + /// Looks up the [`RowType`] for the given [`RowTypeId`]. + pub fn lookup_row_type(&self, id: RowTypeId) -> RowType { + self.queries.lookup_row_type(id) + } + + /// Looks up the [`Synonym`] for the given [`SynonymId`]. + pub fn lookup_synonym(&self, id: SynonymId) -> Synonym { + self.queries.lookup_synonym(id) + } + + /// Interns a [`ForallBinder`], returning its [`ForallBinderId`]. + pub fn intern_forall_binder(&self, binder: ForallBinder) -> ForallBinderId { + self.queries.intern_forall_binder(binder) + } + + /// Interns a [`RowType`], returning its [`RowTypeId`]. + pub fn intern_row_type(&self, row: RowType) -> RowTypeId { + self.queries.intern_row_type(row) + } + + /// Interns a [`Synonym`], returning its [`SynonymId`]. + pub fn intern_synonym(&self, synonym: Synonym) -> SynonymId { + self.queries.intern_synonym(synonym) + } +} + +struct PrimLookup<'r, 'q, Q> +where + Q: ExternalQueries, +{ + resolved: &'r ResolvedModule, + queries: &'q Q, + module_name: &'static str, +} + +impl<'r, 'q, Q: ExternalQueries> PrimLookup<'r, 'q, Q> { + fn new(resolved: &'r ResolvedModule, queries: &'q Q, module_name: &'static str) -> Self { + PrimLookup { resolved, queries, module_name } + } + + fn type_item(&self, name: &str) -> TypeItemId { + let (_, type_id) = self.resolved.exports.lookup_type(name).unwrap_or_else(|| { + unreachable!("invariant violated: {name} not in {}", self.module_name) + }); + type_id + } + + fn type_constructor(&self, name: &str) -> TypeId { + let (file_id, type_id) = self.resolved.exports.lookup_type(name).unwrap_or_else(|| { + unreachable!("invariant violated: {name} not in {}", self.module_name) + }); + self.queries.intern_type(Type::Constructor(file_id, type_id)) + } + + fn class_item(&self, name: &str) -> TypeItemId { + let (_, type_id) = self.resolved.exports.lookup_class(name).unwrap_or_else(|| { + unreachable!("invariant violated: {name} not in {}", self.module_name) + }); + type_id + } + + fn class_constructor(&self, name: &str) -> TypeId { + let (file_id, type_id) = self.resolved.exports.lookup_class(name).unwrap_or_else(|| { + unreachable!("invariant violated: {name} not in {}", self.module_name) + }); + self.queries.intern_type(Type::Constructor(file_id, type_id)) + } + + fn intern(&self, ty: Type) -> TypeId { + self.queries.intern_type(ty) + } +} + +pub struct PrimCore { + pub prim_id: FileId, + pub t: TypeId, + pub type_to_type: TypeId, + pub function: TypeId, + pub function_item: TypeItemId, + pub array: TypeId, + pub record: TypeId, + pub number: TypeId, + pub int: TypeId, + pub string: TypeId, + pub char: TypeId, + pub boolean: TypeId, + pub partial: TypeId, + pub constraint: TypeId, + pub symbol: TypeId, + pub row: TypeId, + pub row_type: TypeId, +} + +impl PrimCore { + fn collect(queries: &impl ExternalQueries) -> QueryResult { + let prim_id = queries.prim_id(); + let resolved = queries.resolved(prim_id)?; + let lookup = PrimLookup::new(&resolved, queries, "Prim"); + + let t = lookup.type_constructor("Type"); + let type_to_type = lookup.intern(Type::Function(t, t)); + + let row = lookup.type_constructor("Row"); + let row_type = lookup.intern(Type::Application(row, t)); + + let function = lookup.type_constructor("Function"); + let function_item = lookup.type_item("Function"); + + Ok(PrimCore { + prim_id, + t, + type_to_type, + function, + function_item, + array: lookup.type_constructor("Array"), + record: lookup.type_constructor("Record"), + number: lookup.type_constructor("Number"), + int: lookup.type_constructor("Int"), + string: lookup.type_constructor("String"), + char: lookup.type_constructor("Char"), + boolean: lookup.type_constructor("Boolean"), + partial: lookup.class_constructor("Partial"), + constraint: lookup.type_constructor("Constraint"), + symbol: lookup.type_constructor("Symbol"), + row, + row_type, + }) + } +} + +pub struct PrimIntCore { + pub file_id: FileId, + pub add: TypeItemId, + pub mul: TypeItemId, + pub compare: TypeItemId, + pub to_string: TypeItemId, +} + +impl PrimIntCore { + fn collect(queries: &impl ExternalQueries) -> QueryResult { + let file_id = queries + .module_file("Prim.Int") + .unwrap_or_else(|| unreachable!("invariant violated: Prim.Int not found")); + + let resolved = queries.resolved(file_id)?; + let lookup = PrimLookup::new(&resolved, queries, "Prim.Int"); + + Ok(PrimIntCore { + file_id, + add: lookup.class_item("Add"), + mul: lookup.class_item("Mul"), + compare: lookup.class_item("Compare"), + to_string: lookup.class_item("ToString"), + }) + } +} + +pub struct PrimBooleanCore { + pub true_: TypeId, + pub false_: TypeId, +} + +impl PrimBooleanCore { + fn collect(queries: &impl ExternalQueries) -> 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 lookup = PrimLookup::new(&resolved, queries, "Prim.Boolean"); + + Ok(PrimBooleanCore { + true_: lookup.type_constructor("True"), + false_: lookup.type_constructor("False"), + }) + } +} + +pub struct PrimOrderingCore { + pub lt: TypeId, + pub eq: TypeId, + pub gt: TypeId, +} + +impl PrimOrderingCore { + fn collect(queries: &impl ExternalQueries) -> QueryResult { + let file_id = queries + .module_file("Prim.Ordering") + .unwrap_or_else(|| unreachable!("invariant violated: Prim.Ordering not found")); + + let resolved = queries.resolved(file_id)?; + let lookup = PrimLookup::new(&resolved, queries, "Prim.Ordering"); + + Ok(PrimOrderingCore { + lt: lookup.type_constructor("LT"), + eq: lookup.type_constructor("EQ"), + gt: lookup.type_constructor("GT"), + }) + } +} + +pub struct PrimSymbolCore { + pub file_id: FileId, + pub append: TypeItemId, + pub compare: TypeItemId, + pub cons: TypeItemId, +} + +impl PrimSymbolCore { + fn collect(queries: &impl ExternalQueries) -> QueryResult { + let file_id = queries + .module_file("Prim.Symbol") + .unwrap_or_else(|| unreachable!("invariant violated: Prim.Symbol not found")); + + let resolved = queries.resolved(file_id)?; + let lookup = PrimLookup::new(&resolved, queries, "Prim.Symbol"); + + Ok(PrimSymbolCore { + file_id, + append: lookup.class_item("Append"), + compare: lookup.class_item("Compare"), + cons: lookup.class_item("Cons"), + }) + } +} + +pub struct PrimRowCore { + pub file_id: FileId, + pub union: TypeItemId, + pub cons: TypeItemId, + pub lacks: TypeItemId, + pub nub: TypeItemId, +} + +impl PrimRowCore { + fn collect(queries: &impl ExternalQueries) -> QueryResult { + let file_id = queries + .module_file("Prim.Row") + .unwrap_or_else(|| unreachable!("invariant violated: Prim.Row not found")); + + let resolved = queries.resolved(file_id)?; + let lookup = PrimLookup::new(&resolved, queries, "Prim.Row"); + + Ok(PrimRowCore { + file_id, + union: lookup.class_item("Union"), + cons: lookup.class_item("Cons"), + lacks: lookup.class_item("Lacks"), + nub: lookup.class_item("Nub"), + }) + } +} + +pub struct PrimRowListCore { + pub file_id: FileId, + pub row_to_list: TypeItemId, + pub cons: TypeId, + pub nil: TypeId, +} + +impl PrimRowListCore { + fn collect(queries: &impl ExternalQueries) -> QueryResult { + let file_id = queries + .module_file("Prim.RowList") + .unwrap_or_else(|| unreachable!("invariant violated: Prim.RowList not found")); + + let resolved = queries.resolved(file_id)?; + let lookup = PrimLookup::new(&resolved, queries, "Prim.RowList"); + + Ok(PrimRowListCore { + file_id, + row_to_list: lookup.class_item("RowToList"), + cons: lookup.type_constructor("Cons"), + nil: lookup.type_constructor("Nil"), + }) + } +} + +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_class("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) -> 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 lookup = PrimLookup::new(&resolved, queries, "Prim.TypeError"); + + Ok(PrimTypeErrorCore { + file_id, + warn: lookup.class_item("Warn"), + fail: lookup.class_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_term( + 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, term_id)) = resolved.exports.lookup_term(n) else { + return Ok(None); + }; + Ok(Some((file_id, term_id))) +} + +fn fetch_known_class( + 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_class(n) else { + return Ok(None); + }; + Ok(Some((file_id, type_id))) +} + +fn fetch_known_constructor( + 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(queries.intern_type(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_class(queries, "Data.Eq", "Eq")?; + let eq1 = fetch_known_class(queries, "Data.Eq", "Eq1")?; + let ord = fetch_known_class(queries, "Data.Ord", "Ord")?; + let ord1 = fetch_known_class(queries, "Data.Ord", "Ord1")?; + let functor = fetch_known_class(queries, "Data.Functor", "Functor")?; + let bifunctor = fetch_known_class(queries, "Data.Bifunctor", "Bifunctor")?; + let contravariant = + fetch_known_class(queries, "Data.Functor.Contravariant", "Contravariant")?; + let profunctor = fetch_known_class(queries, "Data.Profunctor", "Profunctor")?; + let foldable = fetch_known_class(queries, "Data.Foldable", "Foldable")?; + let bifoldable = fetch_known_class(queries, "Data.Bifoldable", "Bifoldable")?; + let traversable = fetch_known_class(queries, "Data.Traversable", "Traversable")?; + let bitraversable = fetch_known_class(queries, "Data.Bitraversable", "Bitraversable")?; + let newtype = fetch_known_class(queries, "Data.Newtype", "Newtype")?; + let generic = fetch_known_class(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) -> QueryResult { + let is_symbol = fetch_known_class(queries, "Data.Symbol", "IsSymbol")?; + let reflectable = fetch_known_class(queries, "Data.Reflectable", "Reflectable")?; + let ordering = fetch_known_constructor(queries, "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) -> QueryResult> { + let Some(no_constructors) = + fetch_known_constructor(queries, "Data.Generic.Rep", "NoConstructors")? + else { + return Ok(None); + }; + let Some(constructor) = + fetch_known_constructor(queries, "Data.Generic.Rep", "Constructor")? + else { + return Ok(None); + }; + let Some(sum) = fetch_known_constructor(queries, "Data.Generic.Rep", "Sum")? else { + return Ok(None); + }; + let Some(product) = fetch_known_constructor(queries, "Data.Generic.Rep", "Product")? else { + return Ok(None); + }; + let Some(no_arguments) = + fetch_known_constructor(queries, "Data.Generic.Rep", "NoArguments")? + else { + return Ok(None); + }; + let Some(argument) = fetch_known_constructor(queries, "Data.Generic.Rep", "Argument")? + else { + return Ok(None); + }; + Ok(Some(KnownGeneric { + no_constructors, + constructor, + sum, + product, + no_arguments, + argument, + })) + } +} + +pub struct KnownTermsCore { + pub otherwise: Option<(FileId, TermItemId)>, +} + +impl KnownTermsCore { + fn collect(queries: &impl ExternalQueries) -> QueryResult { + let otherwise = fetch_known_term(queries, "Data.Boolean", "otherwise")?; + Ok(KnownTermsCore { otherwise }) + } +} diff --git a/compiler-core/checking2/src/core.rs b/compiler-core/checking2/src/core.rs new file mode 100644 index 00000000..3d91df23 --- /dev/null +++ b/compiler-core/checking2/src/core.rs @@ -0,0 +1,207 @@ +//! Implements core type structures. + +pub mod constraint; +pub mod exhaustive; +pub mod fold; +pub mod generalise; +pub mod normalise; +pub mod pretty; +pub mod signature; +pub mod substitute; +pub mod toolkit; +pub mod unification; +pub mod walk; +pub mod zonk; + +use std::sync::Arc; + +use files::FileId; +use indexing::{TermItemId, TypeItemId}; +use itertools::Itertools; +use smol_str::{SmolStr, SmolStrBuilder}; + +/// A globally unique identity for a rigid type variable. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Name { + pub file: FileId, + pub unique: u32, +} + +impl Name { + /// Renders this name as a stable textual variable like `t42`. + pub fn as_text(self) -> SmolStr { + let mut text = SmolStrBuilder::new(); + text.push('t'); + text.push_str(&self.unique.to_string()); + text.finish() + } +} + +/// A marker used to represent binding levels of variables. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Depth(pub u32); + +impl Depth { + pub fn increment(self) -> Depth { + Depth(self.0 + 1) + } +} + +/// Carries information about a type variable under a forall. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ForallBinder { + /// Whether this binder is visible to type applications. + pub visible: bool, + /// The unique identity attached to the type variable. + pub name: Name, + /// The kind of the type variable. + pub kind: TypeId, +} + +/// Represents a row type. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RowType { + /// A stable-sorted list representing `Map>`. + pub fields: Arc<[RowField]>, + /// Closed row if [`None`]; Open row if [`Some`]. + pub tail: Option, +} + +impl RowType { + pub fn new(fields: impl IntoIterator, tail: Option) -> RowType { + let mut fields = fields.into_iter().collect_vec(); + fields.sort_by(|a, b| a.label.cmp(&b.label)); + RowType { fields: Arc::from(fields), tail } + } +} + +/// A field in a row type. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RowField { + /// The label of the row field. + pub label: SmolStr, + /// The [`Type`] of the row field. + pub id: TypeId, +} + +/// The saturation of a synonym application. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Saturation { + /// Fully applied synonym. + Full, + /// Partially applied synonym. + Partial, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum KindOrType { + Kind(TypeId), + Type(TypeId), +} + +/// Represents a type synonym. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Synonym { + /// Whether fully or partially applied. + pub saturation: Saturation, + /// The reference to the synonym type. + pub reference: (FileId, TypeItemId), + /// Kind and type arguments to the synonym constructor. + pub arguments: Arc<[KindOrType]>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CheckedSynonym { + pub kind: TypeId, + pub parameters: Vec, + pub synonym: TypeId, +} + +/// Represents a checked class declaration. +/// +/// Member types are stored in [`CheckedModule::terms`] quantified and +/// constrained with [`Type::Forall`] and [`Type::Constrained`]: +/// +/// ```purescript +/// eq :: forall a. Eq a => a -> a -> Boolean +/// ``` +/// +/// [`CheckedModule::terms`]: crate::CheckedModule::terms +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CheckedClass { + /// Post-generalisation kind variable binders. + pub kind_binders: Vec, + /// Post-generalisation type parameter binders. + pub type_parameters: Vec, + /// Canonical class head, e.g. `Eq a` or `Foo @k a`. + pub canonical: TypeId, + /// Superclass constraints expressed in terms of the class head's rigids. + pub superclasses: Vec, + /// Functional dependencies, carried from lowering. + pub functional_dependencies: Arc<[lowering::FunctionalDependency]>, + /// Class member term item IDs. + pub members: Vec, +} + +/// Represents a checked instance declaration head. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CheckedInstance { + /// Type class reference. + pub resolution: (FileId, TypeItemId), + /// Canonical instance type, e.g. `forall a. Eq a => Eq (Array a)`. + pub canonical: TypeId, +} + +/// The core type representation used by the checker after name resolution. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Type { + /// Type application, `Array Int`. + Application(TypeId, TypeId), + /// Kind application, `Proxy @Int`. + KindApplication(TypeId, TypeId), + /// Type synonym application, see [`Synonym`]. + SynonymApplication(SynonymId), + + /// A universally quantified type, `forall a. a -> a`. + Forall(ForallBinderId, TypeId), + /// A constrained type, `Constraint => Constrained`. + Constrained(TypeId, TypeId), + /// A function type, `a -> b`. + Function(TypeId, TypeId), + /// A type with an explicit kind, `T :: K`. + Kinded(TypeId, TypeId), + + /// A resolved type constructor reference. + Constructor(FileId, TypeItemId), + + /// A type-level integer literal, `42`. + Integer(i32), + /// A type-level string literal, `"life"`. + String(lowering::StringKind, SmolStrId), + /// A row type, see [`RowType`]. + Row(RowTypeId), + + /// A bound skolem variable that can only unify with itself. + Rigid(Name, Depth, TypeId), + /// A unification variable that can be solved to another [`Type`]. + Unification(u32), + + /// A type variable that did not resolve to a binder. + Free(SmolStrId), + /// Recovery marker for exceptional type checker paths. + Unknown(SmolStrId), +} + +/// The role of a type parameter for safe coercions. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Role { + Phantom, + Representational, + Nominal, +} + +pub type ForallBinderId = interner::Id; +pub type RowTypeId = interner::Id; +pub type SynonymId = interner::Id; +pub type SmolStrId = interner::Id; +pub type TypeId = interner::Id; diff --git a/compiler-core/checking2/src/core/constraint.rs b/compiler-core/checking2/src/core/constraint.rs new file mode 100644 index 00000000..e01cd975 --- /dev/null +++ b/compiler-core/checking2/src/core/constraint.rs @@ -0,0 +1,259 @@ +pub mod compiler; +pub mod fd; +pub mod given; +pub mod instances; + +use std::collections::{HashSet, VecDeque}; +use std::mem; + +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; +use itertools::Itertools; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::substitute::{NameToType, SubstituteName}; +use crate::core::{Type, TypeId, normalise, toolkit, unification}; +use crate::implication::ImplicationId; +use crate::state::CheckState; + +use compiler::match_compiler_instances; +use given::{elaborate_given, match_given_instances}; +use instances::{collect_instance_chains, match_instance}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ConstraintApplication { + pub file_id: FileId, + pub item_id: TypeItemId, + pub arguments: Vec, +} + +#[derive(Debug, Clone)] +pub enum MatchInstance { + Match { constraints: Vec, equalities: Vec<(TypeId, TypeId)> }, + Apart, + Stuck, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum MatchType { + Match, + Apart, + Stuck, +} + +impl MatchType { + fn and_then(self, f: impl FnOnce() -> QueryResult) -> QueryResult { + if let MatchType::Match = self { f() } else { Ok(self) } + } +} + +pub fn solve_implication( + state: &mut CheckState, + context: &CheckContext, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let implication = state.implications.current(); + solve_implication_id(state, context, implication, &[]) +} + +/// Recursively solves an implication and its children. +fn solve_implication_id( + state: &mut CheckState, + context: &CheckContext, + implication: ImplicationId, + inherited: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let (wanted, given, children) = { + let node = &mut state.implications[implication]; + (mem::take(&mut node.wanted), mem::take(&mut node.given), node.children.clone()) + }; + + let all_given = inherited.iter().copied().chain(given.iter().copied()).collect_vec(); + + // Solve this implication's children with all_given. + for child in &children { + let residual = solve_implication_id(state, context, *child, &all_given)?; + + // TODO: partition_by_skolem_escape once skolems are introduced. + state.implications[implication].wanted.extend(residual); + } + + // Solve this implication's wanted constraints with all_given. + let remaining = mem::take(&mut state.implications[implication].wanted); + let wanted: VecDeque<_> = wanted.into_iter().chain(remaining).collect(); + let residuals = solve_constraints(state, context, wanted, &all_given)?; + + let implication = &mut state.implications[implication]; + implication.given = given; + implication.wanted = residuals.iter().copied().collect(); + + Ok(residuals) +} + +fn solve_constraints( + state: &mut CheckState, + context: &CheckContext, + wanted: VecDeque, + given: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let given = elaborate_given(state, context, given)?; + let mut work_queue = wanted; + let mut residual = vec![]; + + loop { + let mut made_progress = false; + + 'work: while let Some(wanted) = work_queue.pop_front() { + let Some(application) = constraint_application(state, context, wanted)? else { + residual.push(wanted); + continue; + }; + + match match_given_instances(state, context, &application, &given)? { + Some(MatchInstance::Match { equalities, .. }) => { + for (t1, t2) in equalities { + if unification::unify(state, context, t1, t2)? { + made_progress = true; + } + } + continue 'work; + } + Some(MatchInstance::Apart | MatchInstance::Stuck) | None => {} + } + + 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; + } + Some(MatchInstance::Stuck) => { + residual.push(wanted); + continue 'work; + } + Some(MatchInstance::Apart) | None => {} + } + + let instance_chains = collect_instance_chains(state, context, &application)?; + + for chain in instance_chains { + 'chain: for instance in chain { + match match_instance(state, context, &application, &instance)? { + 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 => continue 'chain, + MatchInstance::Stuck => break 'chain, + } + } + } + + residual.push(wanted); + } + + if made_progress && !residual.is_empty() { + work_queue.extend(residual.drain(..)); + } else { + break; + } + } + + Ok(residual) +} + +pub fn constraint_application( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let (constructor, arguments) = toolkit::extract_type_application(state, context, id)?; + let constructor = normalise::expand(state, context, constructor)?; + Ok(match context.lookup_type(constructor) { + Type::Constructor(file_id, item_id) => { + Some(ConstraintApplication { file_id, item_id, arguments }) + } + _ => None, + }) +} + +/// Discovers superclass constraints for a given constraint. +pub fn elaborate_superclasses( + state: &mut CheckState, + context: &CheckContext, + constraint: TypeId, + constraints: &mut Vec, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + fn aux( + state: &mut CheckState, + context: &CheckContext, + constraint: TypeId, + constraints: &mut Vec, + seen: &mut HashSet<(FileId, TypeItemId)>, + ) -> QueryResult<()> + where + Q: ExternalQueries, + { + let Some(application) = constraint_application(state, context, constraint)? else { + return Ok(()); + }; + + if !seen.insert((application.file_id, application.item_id)) { + return Ok(()); + } + + let Some(class_info) = + toolkit::lookup_file_class(state, context, application.file_id, application.item_id)? + else { + return Ok(()); + }; + + if class_info.superclasses.is_empty() { + return Ok(()); + } + + let mut bindings = NameToType::default(); + for (binder_id, &argument) in + class_info.type_parameters.iter().zip(application.arguments.iter()) + { + let binder = context.lookup_forall_binder(*binder_id); + bindings.insert(binder.name, argument); + } + + for &superclass in &class_info.superclasses { + let substituted = SubstituteName::many(state, context, &bindings, superclass)?; + constraints.push(substituted); + aux(state, context, substituted, constraints, seen)?; + } + + Ok(()) + } + + let mut seen = HashSet::new(); + aux(state, context, constraint, constraints, &mut seen) +} diff --git a/compiler-core/checking2/src/core/constraint/compiler.rs b/compiler-core/checking2/src/core/constraint/compiler.rs new file mode 100644 index 00000000..ea27952c --- /dev/null +++ b/compiler-core/checking2/src/core/constraint/compiler.rs @@ -0,0 +1,173 @@ +mod prim_coerce; +mod prim_int; +mod prim_reflectable; +mod prim_row; +mod prim_row_list; +mod prim_symbol; +mod prim_type_error; + +use building_types::QueryResult; +use smol_str::SmolStr; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::unification::{CanUnify, can_unify}; +use crate::core::{RowType, Type, TypeId, normalise}; +use crate::state::CheckState; + +use super::{ConstraintApplication, MatchInstance}; + +pub fn extract_integer( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let id = normalise::expand(state, context, id)?; + match context.lookup_type(id) { + Type::Integer(value) => Ok(Some(value)), + _ => Ok(None), + } +} + +pub fn extract_symbol( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let id = normalise::expand(state, context, id)?; + if let Type::String(_, id) = context.lookup_type(id) { + Ok(Some(context.queries.lookup_smol_str(id))) + } else { + Ok(None) + } +} + +pub fn extract_row( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let id = normalise::expand(state, context, id)?; + if let Type::Row(id) = context.lookup_type(id) { + Ok(Some(context.lookup_row_type(id))) + } else { + Ok(None) + } +} + +pub fn intern_symbol(context: &CheckContext, value: &str) -> TypeId +where + Q: ExternalQueries, +{ + let smol_str_id = context.queries.intern_smol_str(SmolStr::new(value)); + context.queries.intern_type(Type::String(lowering::StringKind::String, smol_str_id)) +} + +pub fn intern_integer(context: &CheckContext, value: i32) -> TypeId +where + Q: ExternalQueries, +{ + context.queries.intern_type(Type::Integer(value)) +} + +fn match_equality( + state: &mut CheckState, + context: &CheckContext, + actual: TypeId, + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + Ok(match can_unify(state, context, actual, expected)? { + CanUnify::Apart => MatchInstance::Apart, + CanUnify::Equal | CanUnify::Unify => { + MatchInstance::Match { constraints: vec![], equalities: vec![(actual, expected)] } + } + }) +} + +pub fn match_compiler_instances( + state: &mut CheckState, + context: &CheckContext, + wanted: &ConstraintApplication, + given: &[ConstraintApplication], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let ConstraintApplication { file_id, item_id, arguments } = wanted; + + let match_instance = if *file_id == context.prim_int.file_id { + if *item_id == context.prim_int.add { + prim_int::match_add(state, context, arguments)? + } else if *item_id == context.prim_int.mul { + prim_int::match_mul(state, context, arguments)? + } else if *item_id == context.prim_int.compare { + prim_int::match_compare(state, context, arguments, given)? + } else if *item_id == context.prim_int.to_string { + prim_int::match_to_string(state, context, arguments)? + } else { + None + } + } else if *file_id == context.prim_symbol.file_id { + if *item_id == context.prim_symbol.append { + prim_symbol::match_append(state, context, arguments)? + } else if *item_id == context.prim_symbol.compare { + prim_symbol::match_compare(state, context, arguments)? + } else if *item_id == context.prim_symbol.cons { + prim_symbol::match_cons(state, context, arguments)? + } else { + None + } + } else if *file_id == context.prim_row.file_id { + if *item_id == context.prim_row.union { + prim_row::match_union(state, context, arguments)? + } else if *item_id == context.prim_row.cons { + prim_row::match_cons(state, context, arguments)? + } else if *item_id == context.prim_row.lacks { + prim_row::match_lacks(state, context, arguments)? + } else if *item_id == context.prim_row.nub { + prim_row::match_nub(state, context, arguments)? + } else { + None + } + } else if *file_id == context.prim_row_list.file_id { + if *item_id == context.prim_row_list.row_to_list { + prim_row_list::match_row_to_list(state, context, arguments)? + } else { + None + } + } else if *file_id == context.prim_coerce.file_id { + if *item_id == context.prim_coerce.coercible { + prim_coerce::match_coercible(state, context, arguments)? + } else { + None + } + } else if *file_id == context.prim_type_error.file_id { + if *item_id == context.prim_type_error.warn { + prim_type_error::match_warn(state, context, arguments)? + } else if *item_id == context.prim_type_error.fail { + prim_type_error::match_fail(state, context, arguments)? + } else { + None + } + } else if context.known_reflectable.is_symbol == Some((*file_id, *item_id)) { + prim_symbol::match_is_symbol(state, context, arguments)? + } else if context.known_reflectable.reflectable == Some((*file_id, *item_id)) { + prim_reflectable::match_reflectable(state, context, arguments)? + } else { + None + }; + + Ok(match_instance) +} diff --git a/compiler-core/checking2/src/core/constraint/compiler/prim_coerce.rs b/compiler-core/checking2/src/core/constraint/compiler/prim_coerce.rs new file mode 100644 index 00000000..fb0ee8be --- /dev/null +++ b/compiler-core/checking2/src/core/constraint/compiler/prim_coerce.rs @@ -0,0 +1,497 @@ +use std::iter; +use std::sync::Arc; + +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; +use itertools::izip; + +use crate::context::CheckContext; +use crate::core::constraint::MatchInstance; +use crate::core::substitute::SubstituteName; +use crate::core::unification::{CanUnify, can_unify}; +use crate::core::{KindOrType, Role, Type, TypeId, normalise, toolkit}; +use crate::error::ErrorKind; +use crate::source::types; +use crate::state::CheckState; +use crate::{ExternalQueries, safe_loop}; + +enum NewtypeCoercionResult { + Success(MatchInstance), + ConstructorNotInScope { file_id: FileId, item_id: TypeItemId }, + NotApplicable, +} + +pub fn match_coercible( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[left, right] = arguments else { + return Ok(None); + }; + + let left = normalise::expand(state, context, left)?; + let right = normalise::expand(state, context, right)?; + + if left == right { + return Ok(Some(MatchInstance::Match { constraints: vec![], equalities: vec![] })); + } + + if is_unification_head(state, context, left)? || is_unification_head(state, context, right)? { + return Ok(Some(MatchInstance::Stuck)); + } + + if try_refl(state, context, left, right)? { + return Ok(Some(MatchInstance::Match { constraints: vec![], equalities: vec![] })); + } + + 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_function_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(ErrorKind::CoercibleConstructorNotInScope { file_id, item_id }); + } + + Ok(Some(MatchInstance::Apart)) +} + +fn is_unification_head( + state: &mut CheckState, + context: &CheckContext, + mut id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + safe_loop! { + id = normalise::expand(state, context, id)?; + match context.lookup_type(id) { + Type::Unification(_) => return Ok(true), + Type::Application(function, _) | Type::KindApplication(function, _) => { + id = function; + } + _ => return Ok(false), + } + } +} + +fn has_type_kind( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let kind = types::elaborate_kind(state, context, id)?; + let kind = normalise::expand(state, context, kind)?; + Ok(kind == context.prim.t) +} + +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, item_id)) = toolkit::extract_type_constructor(state, context, left)? + && toolkit::is_newtype(context, file_id, item_id)? + { + if toolkit::is_constructor_in_scope(context, file_id, item_id)? { + if let Some(toolkit::NewtypeInner { inner, .. }) = + toolkit::get_newtype_inner(state, context, file_id, item_id, left)? + { + let constraint = make_coercible_constraint(context, inner, right); + return Ok(NewtypeCoercionResult::Success(MatchInstance::Match { + constraints: vec![constraint], + equalities: vec![], + })); + } + } else { + hidden_newtype = Some((file_id, item_id)); + } + } + + if has_type_kind(state, context, right)? + && let Some((file_id, item_id)) = toolkit::extract_type_constructor(state, context, right)? + && toolkit::is_newtype(context, file_id, item_id)? + { + if toolkit::is_constructor_in_scope(context, file_id, item_id)? { + if let Some(toolkit::NewtypeInner { inner, .. }) = + toolkit::get_newtype_inner(state, context, file_id, item_id, right)? + { + let constraint = make_coercible_constraint(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, item_id)); + } + } + + if let Some((file_id, item_id)) = hidden_newtype { + return Ok(NewtypeCoercionResult::ConstructorNotInScope { file_id, item_id }); + } + + Ok(NewtypeCoercionResult::NotApplicable) +} + +fn try_application_coercion( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let Some((left_file, left_id)) = toolkit::extract_type_constructor(state, context, left)? + else { + return Ok(None); + }; + let Some((right_file, right_id)) = toolkit::extract_type_constructor(state, context, right)? + else { + return Ok(None); + }; + + if left_file != right_file || left_id != right_id { + return Ok(None); + } + + let (_, left_arguments) = toolkit::extract_type_application(state, context, left)?; + let (_, right_arguments) = toolkit::extract_type_application(state, context, right)?; + + if left_arguments.len() != right_arguments.len() { + return Ok(Some(MatchInstance::Apart)); + } + + let Some(roles) = toolkit::lookup_file_roles(state, context, left_file, left_id)? else { + return Ok(Some(MatchInstance::Stuck)); + }; + + debug_assert_eq!(roles.len(), left_arguments.len(), "critical failure: mismatched lengths"); + debug_assert_eq!(roles.len(), right_arguments.len(), "critical failure: mismatched lengths"); + + let mut constraints = vec![]; + let mut equalities = vec![]; + + for (role, &left_argument, &right_argument) in izip!(&*roles, &left_arguments, &right_arguments) + { + match role { + Role::Phantom => (), + Role::Representational => { + let constraint = make_coercible_constraint(context, left_argument, right_argument); + constraints.push(constraint); + } + Role::Nominal => { + if left_argument != right_argument { + if can_unify(state, context, left_argument, right_argument)? == CanUnify::Apart + { + return Ok(Some(MatchInstance::Apart)); + } + equalities.push((left_argument, right_argument)); + } + } + } + } + + Ok(Some(MatchInstance::Match { constraints, equalities })) +} + +fn try_function_coercion( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let left_function = decompose_function_simple(state, context, left)?; + let right_function = decompose_function_simple(state, context, right)?; + + let (Some((left_argument, left_result)), Some((right_argument, right_result))) = + (left_function, right_function) + else { + return Ok(None); + }; + + let left_constraint = make_coercible_constraint(context, left_argument, right_argument); + let right_constraint = make_coercible_constraint(context, left_result, right_result); + + Ok(Some(MatchInstance::Match { + constraints: vec![left_constraint, right_constraint], + equalities: vec![], + })) +} + +fn decompose_function_simple( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let id = normalise::expand(state, context, id)?; + match context.lookup_type(id) { + Type::Function(argument, result) => Ok(Some((argument, result))), + Type::Application(partial, result) => { + let partial = normalise::expand(state, context, partial)?; + if let Type::Application(constructor, argument) = context.lookup_type(partial) { + let constructor = normalise::expand(state, context, constructor)?; + if constructor == context.prim.function { + return Ok(Some((argument, result))); + } + } + Ok(None) + } + _ => Ok(None), + } +} + +fn try_row_coercion( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let left = normalise::expand(state, context, left)?; + let right = normalise::expand(state, context, right)?; + + let Type::Row(left_row_id) = context.lookup_type(left) else { return Ok(None) }; + let Type::Row(right_row_id) = context.lookup_type(right) else { return Ok(None) }; + + let left_row = context.lookup_row_type(left_row_id); + let right_row = context.lookup_row_type(right_row_id); + + if left_row.fields.len() != right_row.fields.len() { + return Ok(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 Ok(Some(MatchInstance::Apart)); + } + let constraint = make_coercible_constraint(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(context, left_tail, right_tail); + constraints.push(constraint); + } + (None, Some(_)) | (Some(_), None) => { + return Ok(Some(MatchInstance::Apart)); + } + } + + Ok(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 left_kind = types::elaborate_kind(state, context, left)?; + let right_kind = types::elaborate_kind(state, context, right)?; + + let Some((left_applied, left_domain)) = + decompose_kind_for_coercion(state, context, left, left_kind)? + else { + return Ok(None); + }; + + let Some((right_applied, right_domain)) = + decompose_kind_for_coercion(state, context, right, right_kind)? + else { + return Ok(None); + }; + + if can_unify(state, context, left_domain, right_domain)? == CanUnify::Apart { + return Ok(Some(MatchInstance::Apart)); + } + + let argument = state.fresh_rigid(context.queries, left_domain); + let left_saturated = context.intern_application(left_applied, argument); + let right_saturated = context.intern_application(right_applied, argument); + let constraint = make_coercible_constraint(context, left_saturated, right_saturated); + + Ok(Some(MatchInstance::Match { constraints: vec![constraint], equalities: vec![] })) +} + +fn decompose_kind_for_coercion( + state: &mut CheckState, + context: &CheckContext, + mut type_id: TypeId, + mut kind_id: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + safe_loop! { + kind_id = normalise::expand(state, context, kind_id)?; + match context.lookup_type(kind_id) { + Type::Forall(binder_id, inner_kind) => { + let binder = context.lookup_forall_binder(binder_id); + let fresh = state.fresh_rigid(context.queries, binder.kind); + type_id = context.intern_kind_application(type_id, fresh); + kind_id = SubstituteName::one(state, context, binder.name, fresh, inner_kind)?; + } + Type::Function(domain, _) => return Ok(Some((type_id, domain))), + _ => return Ok(None), + } + } +} + +fn try_refl( + state: &mut CheckState, + context: &CheckContext, + t1_type: TypeId, + t2_type: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let t1_type = normalise::expand(state, context, t1_type)?; + let t2_type = normalise::expand(state, context, t2_type)?; + + if t1_type == t2_type { + return Ok(true); + } + + match (context.lookup_type(t1_type), context.lookup_type(t2_type)) { + ( + Type::Application(t1_function, t1_argument), + Type::Application(t2_function, t2_argument), + ) + | ( + Type::Constrained(t1_function, t1_argument), + Type::Constrained(t2_function, t2_argument), + ) + | ( + Type::KindApplication(t1_function, t1_argument), + Type::KindApplication(t2_function, t2_argument), + ) + | (Type::Kinded(t1_function, t1_argument), Type::Kinded(t2_function, t2_argument)) => { + Ok(try_refl(state, context, t1_function, t2_function)? + && try_refl(state, context, t1_argument, t2_argument)?) + } + + (Type::Function(t1_argument, t1_result), Type::Function(t2_argument, t2_result)) => { + Ok(try_refl(state, context, t1_argument, t2_argument)? + && try_refl(state, context, t1_result, t2_result)?) + } + + (Type::Forall(t1_binder_id, t1_inner), Type::Forall(t2_binder_id, t2_inner)) => { + let t1_binder = context.lookup_forall_binder(t1_binder_id); + let t2_binder = context.lookup_forall_binder(t2_binder_id); + Ok(try_refl(state, context, t1_binder.kind, t2_binder.kind)? + && try_refl(state, context, t1_inner, t2_inner)?) + } + + (Type::SynonymApplication(t1_synonym_id), Type::SynonymApplication(t2_synonym_id)) => { + let t1_synonym = context.lookup_synonym(t1_synonym_id); + let t2_synonym = context.lookup_synonym(t2_synonym_id); + if t1_synonym.reference != t2_synonym.reference + || t1_synonym.arguments.len() != t2_synonym.arguments.len() + { + return Ok(false); + } + let t1_arguments = Arc::clone(&t1_synonym.arguments); + let t2_arguments = Arc::clone(&t2_synonym.arguments); + for (t1_argument, t2_argument) in iter::zip(t1_arguments.iter(), t2_arguments.iter()) { + let equivalent = match (t1_argument, t2_argument) { + (KindOrType::Kind(t1_kind), KindOrType::Kind(t2_kind)) + | (KindOrType::Type(t1_kind), KindOrType::Type(t2_kind)) => { + try_refl(state, context, *t1_kind, *t2_kind)? + } + _ => false, + }; + if !equivalent { + return Ok(false); + } + } + Ok(true) + } + + (Type::Row(t1_row_id), Type::Row(t2_row_id)) => { + let t1_row = context.lookup_row_type(t1_row_id); + let t2_row = context.lookup_row_type(t2_row_id); + if t1_row.fields.len() != t2_row.fields.len() { + return Ok(false); + } + for (t1_field, t2_field) in iter::zip(t1_row.fields.iter(), t2_row.fields.iter()) { + if t1_field.label != t2_field.label + || !try_refl(state, context, t1_field.id, t2_field.id)? + { + return Ok(false); + } + } + match (t1_row.tail, t2_row.tail) { + (Some(t1_tail), Some(t2_tail)) => try_refl(state, context, t1_tail, t2_tail), + (None, None) => Ok(true), + _ => Ok(false), + } + } + + (Type::Rigid(t1_name, _, t1_kind), Type::Rigid(t2_name, _, t2_kind)) => { + Ok(t1_name == t2_name && try_refl(state, context, t1_kind, t2_kind)?) + } + + _ => Ok(false), + } +} + +fn make_coercible_constraint(context: &CheckContext, left: TypeId, right: TypeId) -> TypeId +where + Q: ExternalQueries, +{ + let prim_coerce = &context.prim_coerce; + let coercible = Type::Constructor(prim_coerce.file_id, prim_coerce.coercible); + let coercible = context.queries.intern_type(coercible); + let coercible = context.intern_application(coercible, left); + context.intern_application(coercible, right) +} diff --git a/compiler-core/checking2/src/core/constraint/compiler/prim_int.rs b/compiler-core/checking2/src/core/constraint/compiler/prim_int.rs new file mode 100644 index 00000000..e1bc48c5 --- /dev/null +++ b/compiler-core/checking2/src/core/constraint/compiler/prim_int.rs @@ -0,0 +1,176 @@ +use std::cmp::Ordering; + +use building_types::QueryResult; +use petgraph::algo::has_path_connecting; +use petgraph::prelude::DiGraphMap; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::constraint::{ConstraintApplication, MatchInstance}; +use crate::core::{TypeId, normalise}; +use crate::state::CheckState; + +use super::{extract_integer, intern_symbol, match_equality}; + +pub fn match_add( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[left, right, sum] = arguments else { + return Ok(None); + }; + + let left_int = extract_integer(state, context, left)?; + let right_int = extract_integer(state, context, right)?; + let sum_int = extract_integer(state, context, sum)?; + + let matched = match (left_int, right_int, sum_int) { + (Some(left), Some(right), _) => { + let result = super::intern_integer(context, left + right); + match_equality(state, context, sum, result)? + } + (Some(left), _, Some(sum_value)) => { + let result = super::intern_integer(context, sum_value - left); + match_equality(state, context, right, result)? + } + (_, Some(right), Some(sum_value)) => { + let result = super::intern_integer(context, sum_value - right); + match_equality(state, context, left, result)? + } + _ => MatchInstance::Stuck, + }; + + Ok(Some(matched)) +} + +pub fn match_mul( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[left, right, product] = arguments else { + return Ok(None); + }; + + let Some(left_int) = extract_integer(state, context, left)? else { + return Ok(Some(MatchInstance::Stuck)); + }; + let Some(right_int) = extract_integer(state, context, right)? else { + return Ok(Some(MatchInstance::Stuck)); + }; + + let result = super::intern_integer(context, left_int * right_int); + Ok(Some(match_equality(state, context, product, result)?)) +} + +pub fn match_compare( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], + given: &[ConstraintApplication], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[left, right, ordering] = arguments else { + return Ok(None); + }; + + let left = normalise::expand(state, context, left)?; + let right = normalise::expand(state, context, right)?; + let ordering = normalise::expand(state, context, ordering)?; + + let left_int = extract_integer(state, context, left)?; + let right_int = extract_integer(state, context, 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, + }; + + return Ok(Some(match_equality(state, context, ordering, result)?)); + } + + Ok(Some(match_compare_transitive(state, context, left, right, ordering, given)?)) +} + +// Compare givens induce a directed graph where an edge `a -> b` means `a < b`. +fn match_compare_transitive( + state: &mut CheckState, + context: &CheckContext, + left: TypeId, + right: TypeId, + ordering: TypeId, + given: &[ConstraintApplication], +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut graph: DiGraphMap = DiGraphMap::new(); + + for constraint in given { + if constraint.file_id != context.prim_int.file_id + || constraint.item_id != context.prim_int.compare + { + continue; + } + + let &[a, b, relation] = constraint.arguments.as_slice() else { + continue; + }; + + let a = normalise::expand(state, context, a)?; + let b = normalise::expand(state, context, b)?; + let relation = normalise::expand(state, context, relation)?; + + if relation == context.prim_ordering.lt { + graph.add_edge(a, b, ()); + } else if relation == context.prim_ordering.eq { + graph.add_edge(a, b, ()); + graph.add_edge(b, a, ()); + } else if relation == context.prim_ordering.gt { + graph.add_edge(b, a, ()); + } + } + + 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) => context.prim_ordering.eq, + (true, false) => context.prim_ordering.lt, + (false, true) => context.prim_ordering.gt, + (false, false) => return Ok(MatchInstance::Stuck), + }; + + match_equality(state, context, ordering, result) +} + +pub fn match_to_string( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[int, symbol] = arguments else { + return Ok(None); + }; + + let Some(value) = extract_integer(state, context, int)? else { + return Ok(Some(MatchInstance::Stuck)); + }; + + let result = intern_symbol(context, &value.to_string()); + Ok(Some(match_equality(state, context, symbol, result)?)) +} diff --git a/compiler-core/checking2/src/core/constraint/compiler/prim_reflectable.rs b/compiler-core/checking2/src/core/constraint/compiler/prim_reflectable.rs new file mode 100644 index 00000000..4ac68097 --- /dev/null +++ b/compiler-core/checking2/src/core/constraint/compiler/prim_reflectable.rs @@ -0,0 +1,68 @@ +use building_types::QueryResult; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::constraint::MatchInstance; +use crate::core::unification::{CanUnify, can_unify}; +use crate::core::{Type, TypeId, normalise}; +use crate::state::CheckState; + +use super::{extract_integer, extract_symbol}; + +pub fn match_reflectable( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[v, t] = arguments else { return Ok(None) }; + + let v = normalise::expand(state, context, v)?; + + if extract_symbol(state, context, v)?.is_some() { + return Ok(Some(match_expected(state, context, t, context.prim.string)?)); + } + + if extract_integer(state, context, v)?.is_some() { + return Ok(Some(match_expected(state, context, t, context.prim.int)?)); + } + + if v == context.prim_boolean.true_ || v == context.prim_boolean.false_ { + return Ok(Some(match_expected(state, context, t, context.prim.boolean)?)); + } + + 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 Ok(Some(MatchInstance::Stuck)); + }; + return Ok(Some(match_expected(state, context, t, expected)?)); + } + + if matches!(context.lookup_type(v), Type::Unification(_)) { + return Ok(Some(MatchInstance::Stuck)); + } + + Ok(Some(MatchInstance::Apart)) +} + +fn match_expected( + state: &mut CheckState, + context: &CheckContext, + actual: TypeId, + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + Ok(match can_unify(state, context, actual, expected)? { + CanUnify::Apart => MatchInstance::Apart, + CanUnify::Equal | CanUnify::Unify => { + MatchInstance::Match { constraints: vec![], equalities: vec![(actual, expected)] } + } + }) +} diff --git a/compiler-core/checking2/src/core/constraint/compiler/prim_row.rs b/compiler-core/checking2/src/core/constraint/compiler/prim_row.rs new file mode 100644 index 00000000..0ce90966 --- /dev/null +++ b/compiler-core/checking2/src/core/constraint/compiler/prim_row.rs @@ -0,0 +1,289 @@ +use std::cmp::Ordering; + +use building_types::QueryResult; +use itertools::Itertools; +use rustc_hash::FxHashSet; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::constraint::MatchInstance; +use crate::core::unification::{CanUnify, can_unify}; +use crate::core::{RowField, RowType, Type, TypeId}; +use crate::state::CheckState; + +use super::{extract_row, extract_symbol, match_equality}; + +fn intern_row_value(context: &CheckContext, row: RowType) -> TypeId +where + Q: ExternalQueries, +{ + let row_id = context.intern_row_type(row); + context.intern_row(row_id) +} + +fn extract_closed_row( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let Some(row) = extract_row(state, context, id)? else { return Ok(None) }; + if row.tail.is_some() { + return Ok(None); + } + Ok(Some(row)) +} + +type SubtractResult = (Vec, Vec<(TypeId, TypeId)>); + +fn subtract_row_fields( + state: &mut CheckState, + context: &CheckContext, + source: &[RowField], + to_remove: &[RowField], +) -> QueryResult> +where + Q: ExternalQueries, +{ + 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 => { + if let CanUnify::Apart = can_unify(state, context, field.id, remove_field.id)? { + return Ok(None); + } + equalities.push((field.id, remove_field.id)); + to_remove_iter.next(); + } + Ordering::Greater => { + return Ok(None); + } + } + } else { + result.push(field.clone()); + } + } + + if to_remove_iter.next().is_some() { + return Ok(None); + } + + Ok(Some((result, equalities))) +} + +pub fn match_union( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[left, right, union] = arguments else { + return Ok(None); + }; + + let left_row = extract_row(state, context, left)?; + let right_row = extract_row(state, context, right)?; + let union_row = extract_row(state, context, union)?; + + match (left_row, right_row, union_row) { + (Some(left_row), Some(right_row), _) => { + if let Some(rest) = left_row.tail { + if left_row.fields.is_empty() { + return Ok(Some(MatchInstance::Stuck)); + } + + let fresh_tail = state.fresh_unification(context.queries, context.prim.row_type); + + let result = intern_row_value( + context, + RowType::new(left_row.fields.iter().cloned(), Some(fresh_tail)), + ); + + let prim_row = &context.prim_row; + + let constraint = context + .queries + .intern_type(Type::Constructor(prim_row.file_id, prim_row.union)); + let constraint = context.intern_application(constraint, rest); + let constraint = context.intern_application(constraint, right); + let constraint = context.intern_application(constraint, fresh_tail); + + return Ok(Some(MatchInstance::Match { + constraints: vec![constraint], + equalities: vec![(union, result)], + })); + } + + let union_fields = + left_row.fields.iter().chain(right_row.fields.iter()).cloned().collect_vec(); + + let result = intern_row_value(context, RowType::new(union_fields, right_row.tail)); + + Ok(Some(MatchInstance::Match { + constraints: vec![], + equalities: vec![(union, result)], + })) + } + (_, Some(right_row), Some(union_row)) => { + if right_row.tail.is_some() { + return Ok(Some(MatchInstance::Stuck)); + } + if let Some((remaining, mut equalities)) = + subtract_row_fields(state, context, &union_row.fields, &right_row.fields)? + { + let result = intern_row_value(context, RowType::new(remaining, union_row.tail)); + equalities.push((left, result)); + Ok(Some(MatchInstance::Match { constraints: vec![], equalities })) + } else { + Ok(Some(MatchInstance::Apart)) + } + } + (Some(left_row), _, Some(union_row)) => { + if left_row.tail.is_some() { + return Ok(Some(MatchInstance::Stuck)); + } + if let Some((remaining, mut equalities)) = + subtract_row_fields(state, context, &union_row.fields, &left_row.fields)? + { + let result = intern_row_value(context, RowType::new(remaining, union_row.tail)); + equalities.push((right, result)); + Ok(Some(MatchInstance::Match { constraints: vec![], equalities })) + } else { + Ok(Some(MatchInstance::Apart)) + } + } + _ => Ok(Some(MatchInstance::Stuck)), + } +} + +pub fn match_cons( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[label, a, tail, row] = arguments else { + return Ok(None); + }; + + let label_symbol = extract_symbol(state, context, label)?; + let tail_row = extract_row(state, context, tail)?; + let row_row = extract_row(state, context, 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 = intern_row_value(context, RowType::new(fields, tail_row.tail)); + + Ok(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 = intern_row_value(context, RowType::new(remaining, row_row.tail)); + Ok(Some(MatchInstance::Match { + constraints: vec![], + equalities: vec![(a, field_type), (tail, tail_result)], + })) + } else { + Ok(Some(MatchInstance::Apart)) + } + } + _ => Ok(Some(MatchInstance::Stuck)), + } +} + +pub fn match_lacks( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[label, row] = arguments else { + return Ok(None); + }; + + let Some(label_value) = extract_symbol(state, context, label)? else { + return Ok(Some(MatchInstance::Stuck)); + }; + + let Some(row_row) = extract_row(state, context, row)? else { + return Ok(Some(MatchInstance::Stuck)); + }; + + let has_label = row_row.fields.iter().any(|field| field.label == label_value); + + if has_label { + Ok(Some(MatchInstance::Apart)) + } else if let Some(tail) = row_row.tail { + if row_row.fields.is_empty() { + return Ok(Some(MatchInstance::Stuck)); + } + + let constraint = context + .queries + .intern_type(Type::Constructor(context.prim_row.file_id, context.prim_row.lacks)); + let constraint = context.intern_application(constraint, label); + let constraint = context.intern_application(constraint, tail); + + Ok(Some(MatchInstance::Match { constraints: vec![constraint], equalities: vec![] })) + } else { + Ok(Some(MatchInstance::Match { constraints: vec![], equalities: vec![] })) + } +} + +pub fn match_nub( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[original, nubbed] = arguments else { + return Ok(None); + }; + + let Some(original_row) = extract_closed_row(state, context, original)? else { + return Ok(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 = intern_row_value(context, RowType::new(fields, None)); + Ok(Some(match_equality(state, context, nubbed, result)?)) +} diff --git a/compiler-core/checking2/src/core/constraint/compiler/prim_row_list.rs b/compiler-core/checking2/src/core/constraint/compiler/prim_row_list.rs new file mode 100644 index 00000000..3a780f21 --- /dev/null +++ b/compiler-core/checking2/src/core/constraint/compiler/prim_row_list.rs @@ -0,0 +1,72 @@ +use std::sync::Arc; + +use building_types::QueryResult; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::constraint::MatchInstance; +use crate::core::{RowField, RowType, Type, TypeId, normalise}; +use crate::source::types; +use crate::state::CheckState; + +use super::{extract_row, intern_symbol, match_equality}; + +pub fn match_row_to_list( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[row, list] = arguments else { + return Ok(None); + }; + + let Some(row_value) = extract_row(state, context, row)? else { + return Ok(Some(MatchInstance::Stuck)); + }; + if row_value.tail.is_some() { + return Ok(Some(MatchInstance::Stuck)); + } + + let element_kind = row_element_kind(state, context, &row_value)?; + + let nil = context.intern_kind_application(context.prim_row_list.nil, element_kind); + let cons = context.intern_kind_application(context.prim_row_list.cons, element_kind); + + let result = row_value.fields.iter().rev().fold(nil, |rest, field| { + let label = intern_symbol(context, field.label.as_str()); + let cons_label = context.intern_application(cons, label); + let cons_type = context.intern_application(cons_label, field.id); + context.intern_application(cons_type, rest) + }); + + Ok(Some(match_equality(state, context, list, result)?)) +} + +fn row_element_kind( + state: &mut CheckState, + context: &CheckContext, + row: &RowType, +) -> QueryResult +where + Q: ExternalQueries, +{ + let Some(RowField { id, .. }) = row.fields.first() else { + return Ok(state.fresh_unification(context.queries, context.prim.t)); + }; + + let fields = Arc::from([RowField { label: row.fields[0].label.clone(), id: *id }]); + let singleton_row = RowType { fields, tail: None }; + let singleton_row_id = context.intern_row_type(singleton_row); + let singleton_row_type = context.intern_row(singleton_row_id); + let row_kind = types::elaborate_kind(state, context, singleton_row_type)?; + + let row_kind = normalise::expand(state, context, row_kind)?; + let Type::Application(_, element_kind) = context.lookup_type(row_kind) else { + return Ok(state.fresh_unification(context.queries, context.prim.t)); + }; + + Ok(element_kind) +} diff --git a/compiler-core/checking2/src/core/constraint/compiler/prim_symbol.rs b/compiler-core/checking2/src/core/constraint/compiler/prim_symbol.rs new file mode 100644 index 00000000..90e6d0ef --- /dev/null +++ b/compiler-core/checking2/src/core/constraint/compiler/prim_symbol.rs @@ -0,0 +1,159 @@ +use std::cmp::Ordering; + +use building_types::QueryResult; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::constraint::MatchInstance; +use crate::core::{Type, TypeId, normalise}; +use crate::state::CheckState; + +use super::{extract_symbol, intern_symbol, match_equality}; + +pub fn match_append( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[left, right, appended] = arguments else { + return Ok(None); + }; + + let left_symbol = extract_symbol(state, context, left)?; + let right_symbol = extract_symbol(state, context, right)?; + let appended_symbol = extract_symbol(state, context, appended)?; + + let matched = match (left_symbol, right_symbol, appended_symbol) { + (Some(left_value), Some(right_value), _) => { + let result = intern_symbol(context, &format!("{left_value}{right_value}")); + match_equality(state, context, appended, result)? + } + (_, Some(right_value), Some(appended_value)) => { + let Some(left_value) = appended_value.strip_suffix(right_value.as_str()) else { + return Ok(Some(MatchInstance::Apart)); + }; + + let result = intern_symbol(context, left_value); + match_equality(state, context, left, result)? + } + (Some(left_value), _, Some(appended_value)) => { + let Some(right_value) = appended_value.strip_prefix(left_value.as_str()) else { + return Ok(Some(MatchInstance::Apart)); + }; + + let result = intern_symbol(context, right_value); + match_equality(state, context, right, result)? + } + _ => MatchInstance::Stuck, + }; + + Ok(Some(matched)) +} + +pub fn match_compare( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[left, right, ordering] = arguments else { + return Ok(None); + }; + + let Some(left_symbol) = extract_symbol(state, context, left)? else { + return Ok(Some(MatchInstance::Stuck)); + }; + let Some(right_symbol) = extract_symbol(state, context, right)? else { + return Ok(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, + }; + + Ok(Some(match_equality(state, context, ordering, result)?)) +} + +pub fn match_cons( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[head, tail, symbol] = arguments else { + return Ok(None); + }; + + let head_symbol = extract_symbol(state, context, head)?; + let tail_symbol = extract_symbol(state, context, tail)?; + let symbol_symbol = extract_symbol(state, context, symbol)?; + + let matched = match (&head_symbol, &tail_symbol, &symbol_symbol) { + (Some(head_value), Some(tail_value), _) => { + let mut chars = head_value.chars(); + let (Some(ch), None) = (chars.next(), chars.next()) else { + return Ok(Some(MatchInstance::Apart)); + }; + + let result = intern_symbol(context, &format!("{ch}{tail_value}")); + match_equality(state, context, symbol, result)? + } + (_, _, Some(symbol_value)) => { + let mut chars = symbol_value.chars(); + let Some(head_char) = chars.next() else { + return Ok(Some(MatchInstance::Apart)); + }; + + if let Some(head_value) = head_symbol { + let mut head_chars = head_value.chars(); + if head_chars.next() != Some(head_char) || head_chars.next().is_some() { + return Ok(Some(MatchInstance::Apart)); + } + } + + let head_result = intern_symbol(context, &head_char.to_string()); + let tail_result = intern_symbol(context, chars.as_str()); + MatchInstance::Match { + constraints: vec![], + equalities: vec![(head, head_result), (tail, tail_result)], + } + } + _ => MatchInstance::Stuck, + }; + + Ok(Some(matched)) +} + +pub fn match_is_symbol( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[symbol] = arguments else { + return Ok(None); + }; + + let symbol = normalise::expand(state, context, symbol)?; + + let matched = if extract_symbol(state, context, symbol)?.is_some() { + MatchInstance::Match { constraints: vec![], equalities: vec![] } + } else if matches!(context.lookup_type(symbol), Type::Unification(_)) { + MatchInstance::Stuck + } else { + MatchInstance::Apart + }; + + Ok(Some(matched)) +} diff --git a/compiler-core/checking2/src/core/constraint/compiler/prim_type_error.rs b/compiler-core/checking2/src/core/constraint/compiler/prim_type_error.rs new file mode 100644 index 00000000..b42c87ea --- /dev/null +++ b/compiler-core/checking2/src/core/constraint/compiler/prim_type_error.rs @@ -0,0 +1,176 @@ +use building_types::QueryResult; +use lowering::StringKind; +use smol_str::{SmolStr, format_smolstr}; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::constraint::MatchInstance; +use crate::core::pretty::Pretty; +use crate::core::{Type, TypeId, normalise, toolkit, zonk}; +use crate::error::ErrorKind; +use crate::state::CheckState; + +fn is_stuck(state: &mut CheckState, context: &CheckContext, id: TypeId) -> QueryResult +where + Q: ExternalQueries, +{ + let id = normalise::expand(state, context, id)?; + Ok(matches!(context.lookup_type(id), Type::Unification(_))) +} + +fn extract_symbol_text( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let id = normalise::expand(state, context, id)?; + if let Type::String(_, smol_str_id) = context.lookup_type(id) { + Ok(Some(context.queries.lookup_smol_str(smol_str_id))) + } else { + Ok(None) + } +} + +fn extract_symbol_with_kind( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let id = normalise::expand(state, context, id)?; + if let Type::String(kind, smol_id) = context.lookup_type(id) { + Ok(Some((kind, context.queries.lookup_smol_str(smol_id)))) + } else { + Ok(None) + } +} + +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 == '\'') +} + +fn render_label(kind: StringKind, text: &str) -> SmolStr { + if is_valid_label(text) { + SmolStr::new(text) + } else { + match kind { + StringKind::String => SmolStr::new(format!(r#""{text}""#)), + StringKind::RawString => SmolStr::new(format!(r#""""{text}""""#)), + } + } +} + +/// Renders a `Doc` type into a string for custom type error messages. +/// +/// Returns `None` if the doc is stuck on an unsolved unification variable, +/// signalling that the constraint solver should return `Stuck`. +fn render_doc( + state: &mut CheckState, + context: &CheckContext, + doc: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let doc = normalise::expand(state, context, doc)?; + + if matches!(context.lookup_type(doc), Type::Unification(_)) { + return Ok(None); + } + + let (constructor, arguments) = toolkit::extract_type_application(state, context, doc)?; + let prim = &context.prim_type_error; + + if constructor == prim.text { + let &[symbol] = arguments.as_slice() else { return Ok(None) }; + if is_stuck(state, context, symbol)? { + return Ok(None); + } + extract_symbol_text(state, context, symbol) + } else if constructor == prim.quote { + let &[quoted_type] = arguments.as_slice() else { return Ok(None) }; + if is_stuck(state, context, quoted_type)? { + return Ok(None); + } + let quoted_type = zonk::zonk(state, context, quoted_type)?; + let rendered = Pretty::new(context.queries, &state.checked).render(quoted_type); + Ok(Some(rendered)) + } else if constructor == prim.quote_label { + let &[symbol] = arguments.as_slice() else { return Ok(None) }; + if is_stuck(state, context, symbol)? { + return Ok(None); + } + Ok(extract_symbol_with_kind(state, context, symbol)? + .map(|(kind, text)| render_label(kind, &text))) + } else if constructor == prim.beside { + let &[left, right] = arguments.as_slice() else { return Ok(None) }; + let Some(left_rendered) = render_doc(state, context, left)? else { + return Ok(None); + }; + let Some(right_rendered) = render_doc(state, context, right)? else { + return Ok(None); + }; + Ok(Some(format_smolstr!("{left_rendered}{right_rendered}"))) + } else if constructor == prim.above { + let &[upper, lower] = arguments.as_slice() else { return Ok(None) }; + let Some(upper_rendered) = render_doc(state, context, upper)? else { + return Ok(None); + }; + let Some(lower_rendered) = render_doc(state, context, lower)? else { + return Ok(None); + }; + Ok(Some(format_smolstr!("{upper_rendered}\n{lower_rendered}"))) + } else { + Ok(None) + } +} + +pub fn match_warn( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[doc] = arguments else { return Ok(None) }; + + let Some(message) = render_doc(state, context, doc)? else { + return Ok(Some(MatchInstance::Stuck)); + }; + + let message_id = context.queries.intern_smol_str(message); + state.insert_error(ErrorKind::CustomWarning { message_id }); + + Ok(Some(MatchInstance::Match { constraints: vec![], equalities: vec![] })) +} + +pub fn match_fail( + state: &mut CheckState, + context: &CheckContext, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let &[doc] = arguments else { return Ok(None) }; + + let Some(message) = render_doc(state, context, doc)? else { + return Ok(Some(MatchInstance::Stuck)); + }; + + let message_id = context.queries.intern_smol_str(message); + state.insert_error(ErrorKind::CustomFailure { message_id }); + + Ok(Some(MatchInstance::Match { constraints: vec![], equalities: vec![] })) +} diff --git a/compiler-core/checking2/src/core/constraint/fd.rs b/compiler-core/checking2/src/core/constraint/fd.rs new file mode 100644 index 00000000..22ec2341 --- /dev/null +++ b/compiler-core/checking2/src/core/constraint/fd.rs @@ -0,0 +1,148 @@ +use std::collections::HashSet; + +use crate::safe_loop; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Fd { + pub determiners: HashSet, + pub determined: HashSet, +} + +impl Fd { + pub fn new( + determiners: impl IntoIterator, + determined: impl IntoIterator, + ) -> Fd { + Fd { + determiners: determiners.into_iter().collect(), + determined: determined.into_iter().collect(), + } + } +} + +pub fn get_all_determined(functional_dependencies: &[Fd]) -> HashSet { + functional_dependencies.iter().flat_map(|fd| fd.determined.iter().copied()).collect() +} + +pub fn compute_closure( + functional_dependencies: &[Fd], + initial_positions: &HashSet, +) -> HashSet { + let mut determined = initial_positions.clone(); + safe_loop! { + let mut changed = false; + + for functional_dependency in functional_dependencies { + if functional_dependency.determiners.is_subset(&determined) { + for &position in &functional_dependency.determined { + if determined.insert(position) { + changed = true; + } + } + } + } + + if !changed { + return determined; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_no_fundeps() { + let initial: HashSet = [0, 1].into_iter().collect(); + let result = compute_closure(&[], &initial); + assert_eq!(result, initial); + } + + #[test] + fn test_single_fundep() { + let fundeps = vec![Fd::new([0], [1])]; + let initial: HashSet = [0].into_iter().collect(); + let result = compute_closure(&fundeps, &initial); + assert_eq!(result, [0, 1].into_iter().collect()); + } + + #[test] + fn test_fundep_not_triggered() { + let fundeps = vec![Fd::new([0], [1])]; + let initial: HashSet = [1].into_iter().collect(); + let result = compute_closure(&fundeps, &initial); + assert_eq!(result, [1].into_iter().collect()); + } + + #[test] + fn test_transitive_fundeps() { + let fundeps = vec![Fd::new([0], [1]), Fd::new([1], [2])]; + let initial: HashSet = [0].into_iter().collect(); + let result = compute_closure(&fundeps, &initial); + assert_eq!(result, [0, 1, 2].into_iter().collect()); + } + + #[test] + fn test_multi_determiner() { + let fundeps = vec![Fd::new([0, 1], [2])]; + + let initial: HashSet = [0].into_iter().collect(); + let result = compute_closure(&fundeps, &initial); + assert_eq!(result, [0].into_iter().collect()); + + let initial: HashSet = [0, 1].into_iter().collect(); + let result = compute_closure(&fundeps, &initial); + assert_eq!(result, [0, 1, 2].into_iter().collect()); + } + + #[test] + fn test_multiple_determined() { + let fundeps = vec![Fd::new([0], [1, 2])]; + let initial: HashSet = [0].into_iter().collect(); + let result = compute_closure(&fundeps, &initial); + assert_eq!(result, [0, 1, 2].into_iter().collect()); + } + + #[test] + fn test_empty_determiners() { + let fundeps = vec![Fd::new([], [0])]; + let initial: HashSet = HashSet::new(); + let result = compute_closure(&fundeps, &initial); + assert_eq!(result, [0].into_iter().collect()); + } + + #[test] + fn test_all_determined_no_fundeps() { + let result = get_all_determined(&[]); + assert_eq!(result, HashSet::new()); + } + + #[test] + fn test_all_determined_single_fundep() { + let fundeps = vec![Fd::new([0], [1])]; + let result = get_all_determined(&fundeps); + assert_eq!(result, [1].into_iter().collect()); + } + + #[test] + fn test_all_determined_multiple_fundeps() { + let fundeps = vec![Fd::new([0], [1]), Fd::new([1], [2])]; + let result = get_all_determined(&fundeps); + assert_eq!(result, [1, 2].into_iter().collect()); + } + + #[test] + fn test_all_determined_overlapping() { + let fundeps = vec![Fd::new([0], [1, 2]), Fd::new([3], [1])]; + let result = get_all_determined(&fundeps); + assert_eq!(result, [1, 2].into_iter().collect()); + } + + #[test] + fn test_all_determined_empty_determiners() { + let fundeps = vec![Fd::new([], [0])]; + let result = get_all_determined(&fundeps); + assert_eq!(result, [0].into_iter().collect()); + } +} diff --git a/compiler-core/checking2/src/core/constraint/given.rs b/compiler-core/checking2/src/core/constraint/given.rs new file mode 100644 index 00000000..93bdc36e --- /dev/null +++ b/compiler-core/checking2/src/core/constraint/given.rs @@ -0,0 +1,243 @@ +use std::iter; + +use building_types::QueryResult; +use itertools::Itertools; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{KindOrType, Type, TypeId, normalise}; +use crate::state::CheckState; + +use super::{ + ConstraintApplication, MatchInstance, MatchType, constraint_application, elaborate_superclasses, +}; + +pub fn elaborate_given( + state: &mut CheckState, + context: &CheckContext, + given: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let mut elaborated = vec![]; + + for &constraint in given { + elaborated.push(constraint); + elaborate_superclasses(state, context, constraint, &mut elaborated)?; + } + + let mut applications = elaborated + .into_iter() + .map(|constraint| constraint_application(state, context, constraint)) + .filter_map_ok(|constraint| constraint) + .collect::>>()?; + + let symmetric = applications.iter().filter_map(|application| { + let is_coercible = application.file_id == context.prim_coerce.file_id + && application.item_id == context.prim_coerce.coercible; + + let &[left, right] = application.arguments.as_slice() else { + return None; + }; + + is_coercible.then(|| ConstraintApplication { + file_id: application.file_id, + item_id: application.item_id, + arguments: vec![right, left], + }) + }); + + let symmetric = symmetric.collect_vec(); + applications.extend(symmetric); + + Ok(applications) +} + +/// Matches a wanted constraint to given constraints. +pub fn match_given_instances( + state: &mut CheckState, + context: &CheckContext, + wanted: &ConstraintApplication, + given: &[ConstraintApplication], +) -> QueryResult> +where + Q: ExternalQueries, +{ + 'given: for given in given { + if wanted.file_id != given.file_id || wanted.item_id != given.item_id { + continue; + } + + if wanted.arguments.len() != given.arguments.len() { + continue; + } + + let mut stuck_positions = vec![]; + + for (index, (&wanted_argument, &given_argument)) in + iter::zip(wanted.arguments.iter(), given.arguments.iter()).enumerate() + { + let match_result = match_given_type(state, context, wanted_argument, given_argument)?; + + if matches!(match_result, MatchType::Apart) { + continue 'given; + } + + if matches!(match_result, MatchType::Stuck) { + stuck_positions.push(index); + } + } + + // Given constraints are valid by construction. When a unification + // variable makes a position stuck, it's safe to emit an equality + // rather than require functional dependencies to cover it. + let equalities = stuck_positions + .into_iter() + .map(|index| (wanted.arguments[index], given.arguments[index])); + return Ok(Some(MatchInstance::Match { + constraints: vec![], + equalities: equalities.collect(), + })); + } + + Ok(None) +} + +/// Matches an argument from a wanted constraint to one from a given constraint. +/// +/// This function is specialised for matching given constraints, like those +/// found in value signatures rather than top-level instance declarations; +/// unlike [`match_type`], this function does not build bindings or equalities +/// for rigid variables. +/// +/// [`match_type`]: super::match_type +fn match_given_type( + state: &mut CheckState, + context: &CheckContext, + wanted: TypeId, + given: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let wanted = normalise::expand(state, context, wanted)?; + let given = normalise::expand(state, context, given)?; + + if wanted == given { + return Ok(MatchType::Match); + } + + let wanted_t = context.lookup_type(wanted); + let given_t = context.lookup_type(given); + + match (wanted_t, given_t) { + (Type::Unification(_), _) => Ok(MatchType::Stuck), + + (Type::Rigid(wanted_name, _, wanted_kind), Type::Rigid(given_name, _, given_kind)) => { + if wanted_name == given_name { + match_given_type(state, context, wanted_kind, given_kind) + } else { + Ok(MatchType::Apart) + } + } + + ( + Type::Application(wanted_function, wanted_argument), + Type::Application(given_function, given_argument), + ) => match_given_type(state, context, wanted_function, given_function)? + .and_then(|| match_given_type(state, context, wanted_argument, given_argument)), + + ( + Type::Function(wanted_argument, wanted_result), + Type::Function(given_argument, given_result), + ) => match_given_type(state, context, wanted_argument, given_argument)? + .and_then(|| match_given_type(state, context, wanted_result, given_result)), + + (Type::Function(wanted_argument, wanted_result), Type::Application(_, _)) => { + let wanted = context.intern_function_application(wanted_argument, wanted_result); + match_given_type(state, context, wanted, given) + } + + (Type::Application(_, _), Type::Function(given_argument, given_result)) => { + let given = context.intern_function_application(given_argument, given_result); + match_given_type(state, context, wanted, given) + } + + (Type::Row(wanted_row_id), Type::Row(given_row_id)) => { + let wanted_row = context.lookup_row_type(wanted_row_id); + let given_row = context.lookup_row_type(given_row_id); + + if wanted_row.fields.len() != given_row.fields.len() { + return Ok(MatchType::Apart); + } + + let mut result = MatchType::Match; + for (wanted_field, given_field) in + iter::zip(wanted_row.fields.iter(), given_row.fields.iter()) + { + if wanted_field.label != given_field.label { + return Ok(MatchType::Apart); + } + result = result.and_then(|| { + match_given_type(state, context, wanted_field.id, given_field.id) + })?; + } + + match (wanted_row.tail, given_row.tail) { + (Some(wanted_tail), Some(given_tail)) => { + result.and_then(|| match_given_type(state, context, wanted_tail, given_tail)) + } + (Some(wanted_tail), None) => { + let wanted_tail = normalise::expand(state, context, wanted_tail)?; + if matches!(context.lookup_type(wanted_tail), Type::Unification(_)) { + Ok(MatchType::Stuck) + } else { + Ok(MatchType::Apart) + } + } + (None, Some(given_tail)) => { + let given_tail = normalise::expand(state, context, given_tail)?; + if matches!(context.lookup_type(given_tail), Type::Unification(_)) { + Ok(MatchType::Stuck) + } else { + Ok(MatchType::Apart) + } + } + (None, None) => Ok(result), + } + } + + ( + Type::KindApplication(wanted_function, wanted_argument), + Type::KindApplication(given_function, given_argument), + ) => match_given_type(state, context, wanted_function, given_function)? + .and_then(|| match_given_type(state, context, wanted_argument, given_argument)), + + (Type::SynonymApplication(wanted_synonym), Type::SynonymApplication(given_synonym)) => { + let wanted_synonym = context.lookup_synonym(wanted_synonym); + let given_synonym = context.lookup_synonym(given_synonym); + + if wanted_synonym.reference != given_synonym.reference + || wanted_synonym.arguments.len() != given_synonym.arguments.len() + { + return Ok(MatchType::Apart); + } + + iter::zip(wanted_synonym.arguments.iter(), given_synonym.arguments.iter()).try_fold( + MatchType::Match, + |result, (wanted_argument, given_argument)| { + result.and_then(|| match (wanted_argument, given_argument) { + (KindOrType::Kind(wanted_kind), KindOrType::Kind(given_kind)) + | (KindOrType::Type(wanted_kind), KindOrType::Type(given_kind)) => { + match_given_type(state, context, *wanted_kind, *given_kind) + } + _ => Ok(MatchType::Apart), + }) + }, + ) + } + + _ => Ok(MatchType::Apart), + } +} diff --git a/compiler-core/checking2/src/core/constraint/instances.rs b/compiler-core/checking2/src/core/constraint/instances.rs new file mode 100644 index 00000000..7e0f1a55 --- /dev/null +++ b/compiler-core/checking2/src/core/constraint/instances.rs @@ -0,0 +1,605 @@ +use std::collections::HashSet; +use std::iter; + +use building_types::QueryResult; +use files::FileId; +use indexing::{InstanceChainId, TypeItemId}; +use itertools::Itertools; +use rustc_hash::{FxHashMap, FxHashSet}; + +use crate::context::CheckContext; +use crate::core::substitute::SubstituteName; +use crate::core::unification::{CanUnify, can_unify}; +use crate::core::walk::{TypeWalker, WalkAction, walk_type}; +use crate::core::{ + CheckedInstance, KindOrType, Name, RowField, RowType, Type, TypeId, normalise, toolkit, +}; +use crate::error::ErrorKind; +use crate::state::CheckState; +use crate::{CheckedModule, ExternalQueries}; + +use super::fd::{Fd, compute_closure, get_all_determined}; +use super::{ConstraintApplication, MatchInstance, MatchType}; + +#[derive(Clone)] +pub struct CandidateInstance { + chain_id: Option, + position: u32, + checked: CheckedInstance, +} + +/// Collects instance chains for a constraint from all eligible files. +pub fn collect_instance_chains( + state: &mut CheckState, + context: &CheckContext, + application: &ConstraintApplication, +) -> QueryResult>> +where + Q: ExternalQueries, +{ + let mut files_to_search = FxHashSet::default(); + files_to_search.insert(application.file_id); + + for &argument in &application.arguments { + CollectFileReferences::collect(state, context, argument, &mut files_to_search)?; + } + + let mut instances = vec![]; + + for &file_id in &files_to_search { + if file_id == context.id { + collect_instances_from_checked( + &mut instances, + &state.checked, + &context.indexed, + application.file_id, + application.item_id, + ); + } else { + let checked = context.queries.checked2(file_id)?; + let indexed = context.queries.indexed(file_id)?; + collect_instances_from_checked( + &mut instances, + &checked, + &indexed, + application.file_id, + application.item_id, + ); + } + } + + let mut grouped: FxHashMap> = FxHashMap::default(); + let mut singleton = vec![]; + + for instance in instances { + if let Some(chain_id) = instance.chain_id { + grouped.entry(chain_id).or_default().push(instance); + } else { + singleton.push(vec![instance]); + } + } + + let mut chains = singleton; + for (_, mut chain) in grouped { + chain.sort_by_key(|instance| instance.position); + chains.push(chain); + } + + Ok(chains) +} + +fn collect_instances_from_checked( + output: &mut Vec, + checked: &CheckedModule, + indexed: &indexing::IndexedModule, + class_file: FileId, + class_id: TypeItemId, +) { + output.extend( + checked + .instances + .iter() + .filter(|(_, instance)| instance.resolution == (class_file, class_id)) + .map(|(&id, checked)| CandidateInstance { + chain_id: indexed.pairs.instance_chain_id(id), + position: indexed.pairs.instance_chain_position(id).unwrap_or(0), + checked: checked.clone(), + }), + ); + + output.extend( + checked + .derived + .values() + .filter(|instance| instance.resolution == (class_file, class_id)) + .cloned() + .map(|checked| CandidateInstance { chain_id: None, position: 0, checked }), + ); +} + +fn get_functional_dependencies( + context: &CheckContext, + file_id: FileId, + item_id: TypeItemId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + fn extract(type_item: Option<&lowering::TypeItemIr>) -> Vec { + let Some(lowering::TypeItemIr::ClassGroup { class: Some(class), .. }) = type_item else { + return vec![]; + }; + + class + .functional_dependencies + .iter() + .map(|functional_dependency| { + Fd::new( + functional_dependency.determiners.iter().map(|&x| x as usize), + functional_dependency.determined.iter().map(|&x| x as usize), + ) + }) + .collect() + } + + if file_id == context.id { + Ok(extract(context.lowered.info.get_type_item(item_id))) + } else { + let lowered = context.queries.lowered(file_id)?; + Ok(extract(lowered.info.get_type_item(item_id))) + } +} + +/// Determines if [`MatchType::Stuck`] arguments can be determined by functional dependencies. +fn can_determine_stuck( + context: &CheckContext, + file_id: FileId, + item_id: TypeItemId, + match_results: &[MatchType], + stuck_positions: &[usize], +) -> QueryResult +where + Q: ExternalQueries, +{ + if stuck_positions.is_empty() { + return Ok(true); + } + + let functional_dependencies = get_functional_dependencies(context, file_id, item_id)?; + let initial: HashSet<_> = match_results + .iter() + .enumerate() + .filter_map(|(index, result)| matches!(result, MatchType::Match).then_some(index)) + .collect(); + + let determined = compute_closure(&functional_dependencies, &initial); + Ok(stuck_positions.iter().all(|index| determined.contains(index))) +} + +/// Matches a wanted constraint to an instance. +pub fn match_instance( + state: &mut CheckState, + context: &CheckContext, + wanted: &ConstraintApplication, + instance: &CandidateInstance, +) -> QueryResult +where + Q: ExternalQueries, +{ + let Some(decomposed) = toolkit::decompose_instance(state, context, &instance.checked)? else { + return Ok(MatchInstance::Apart); + }; + + if wanted.arguments.len() != decomposed.arguments.len() { + return Ok(MatchInstance::Apart); + } + + let mut bindings = FxHashMap::default(); + let mut equalities = vec![]; + let mut match_results = vec![]; + let mut stuck_positions = vec![]; + + for (index, (&wanted_argument, &instance_argument)) in + iter::zip(wanted.arguments.iter(), decomposed.arguments.iter()).enumerate() + { + let match_result = match_type( + state, + context, + &mut bindings, + &mut equalities, + wanted_argument, + instance_argument, + )?; + + if matches!(match_result, MatchType::Apart) { + return Ok(MatchInstance::Apart); + } + + if matches!(match_result, MatchType::Stuck) { + stuck_positions.push(index); + } + + match_results.push(match_result); + } + + if !stuck_positions.is_empty() + && !can_determine_stuck( + context, + wanted.file_id, + wanted.item_id, + &match_results, + &stuck_positions, + )? + { + return Ok(MatchInstance::Stuck); + } + + for &index in &stuck_positions { + let substituted = + SubstituteName::many(state, context, &bindings, decomposed.arguments[index])?; + equalities.push((wanted.arguments[index], substituted)); + } + + for binder in &decomposed.binders { + bindings + .entry(binder.name) + .or_insert_with(|| state.fresh_unification(context.queries, binder.kind)); + } + + let constraints = decomposed + .constraints + .into_iter() + .map(|constraint| SubstituteName::many(state, context, &bindings, constraint)) + .collect::>>()?; + + Ok(MatchInstance::Match { constraints, equalities }) +} + +/// Matches an argument from a wanted constraint to one from an instance. +/// +/// This function emits substitutions and equalities when matching against +/// instances, for example: +/// +/// ```purescript +/// instance TypeEq a a True +/// ``` +/// +/// When matching the wanted constraint `TypeEq Int ?0` against this instance, +/// it creates the binding `a := Int`. This means that subsequent usages of `a` +/// are equal to `Int`, turning the second `match(a, ?0)` into `match(Int, ?0)`. +/// We use the [`can_unify`] function to speculate if these two types can be +/// unified, or if unifying them solves unification variables, encoded by the +/// [`CanUnify::Unify`] variant. +fn match_type( + state: &mut CheckState, + context: &CheckContext, + bindings: &mut FxHashMap, + equalities: &mut Vec<(TypeId, TypeId)>, + wanted: TypeId, + given: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let wanted = normalise::expand(state, context, wanted)?; + let given = normalise::expand(state, context, given)?; + + if wanted == given { + return Ok(MatchType::Match); + } + + let wanted_t = context.lookup_type(wanted); + let given_t = context.lookup_type(given); + + match (wanted_t, given_t) { + (_, Type::Rigid(name, _, _)) => { + if let Some(&bound) = bindings.get(&name) { + match can_unify(state, context, wanted, bound)? { + CanUnify::Equal => Ok(MatchType::Match), + CanUnify::Apart => Ok(MatchType::Apart), + CanUnify::Unify => { + equalities.push((wanted, bound)); + Ok(MatchType::Match) + } + } + } else { + bindings.insert(name, wanted); + Ok(MatchType::Match) + } + } + + (Type::Unification(_), _) => Ok(MatchType::Stuck), + + (Type::Row(wanted_row_id), Type::Row(given_row_id)) => { + let wanted_row = context.lookup_row_type(wanted_row_id); + let given_row = context.lookup_row_type(given_row_id); + match_row_type(state, context, bindings, equalities, wanted_row, given_row) + } + + ( + Type::Application(wanted_function, wanted_argument), + Type::Application(given_function, given_argument), + ) => match_type(state, context, bindings, equalities, wanted_function, given_function)? + .and_then(|| { + match_type(state, context, bindings, equalities, wanted_argument, given_argument) + }), + + ( + Type::Function(wanted_argument, wanted_result), + Type::Function(given_argument, given_result), + ) => match_type(state, context, bindings, equalities, wanted_argument, given_argument)? + .and_then(|| { + match_type(state, context, bindings, equalities, wanted_result, given_result) + }), + + (Type::Function(wanted_argument, wanted_result), Type::Application(_, _)) => { + let wanted = context.intern_function_application(wanted_argument, wanted_result); + match_type(state, context, bindings, equalities, wanted, given) + } + + (Type::Application(_, _), Type::Function(given_argument, given_result)) => { + let given = context.intern_function_application(given_argument, given_result); + match_type(state, context, bindings, equalities, wanted, given) + } + + ( + Type::KindApplication(wanted_function, wanted_argument), + Type::KindApplication(given_function, given_argument), + ) => match_type(state, context, bindings, equalities, wanted_function, given_function)? + .and_then(|| { + match_type(state, context, bindings, equalities, wanted_argument, given_argument) + }), + + (Type::Kinded(wanted_inner, wanted_kind), Type::Kinded(given_inner, given_kind)) => { + match_type(state, context, bindings, equalities, wanted_inner, given_inner)?.and_then( + || match_type(state, context, bindings, equalities, wanted_kind, given_kind), + ) + } + + (Type::SynonymApplication(wanted_synonym), Type::SynonymApplication(given_synonym)) => { + let wanted_synonym = context.lookup_synonym(wanted_synonym); + let given_synonym = context.lookup_synonym(given_synonym); + + if wanted_synonym.reference != given_synonym.reference + || wanted_synonym.arguments.len() != given_synonym.arguments.len() + { + return Ok(MatchType::Apart); + } + + iter::zip(wanted_synonym.arguments.iter(), given_synonym.arguments.iter()).try_fold( + MatchType::Match, + |result, (wanted_argument, given_argument)| { + result.and_then(|| match (wanted_argument, given_argument) { + (KindOrType::Kind(wanted_kind), KindOrType::Kind(given_kind)) + | (KindOrType::Type(wanted_kind), KindOrType::Type(given_kind)) => { + match_type( + state, + context, + bindings, + equalities, + *wanted_kind, + *given_kind, + ) + } + _ => Ok(MatchType::Apart), + }) + }, + ) + } + + _ => Ok(MatchType::Apart), + } +} + +/// Matches row types in instance heads. +/// +/// This function handles structural row matching for both the tail variable +/// form `( | r )` in determiner positions and labeled rows in determined +/// positions `( x :: T | r )`. This function partitions the two row types, +/// matches the shared fields, and handles the row tail. +fn match_row_type( + state: &mut CheckState, + context: &CheckContext, + bindings: &mut FxHashMap, + equalities: &mut Vec<(TypeId, TypeId)>, + wanted_row: RowType, + given_row: RowType, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut wanted_only = vec![]; + let mut given_only = vec![]; + let mut result = MatchType::Match; + + for field in itertools::merge_join_by( + wanted_row.fields.iter(), + given_row.fields.iter(), + |wanted, given| wanted.label.cmp(&given.label), + ) { + match field { + itertools::EitherOrBoth::Both(wanted, given) => { + result = result.and_then(|| { + match_type(state, context, bindings, equalities, wanted.id, given.id) + })?; + // Given an open wanted row, additional fields from the + // given row can be absorbed into the wanted row's tail. + if matches!(result, MatchType::Apart) && wanted_row.tail.is_none() { + return Ok(MatchType::Apart); + } + } + itertools::EitherOrBoth::Left(wanted) => wanted_only.push(wanted), + itertools::EitherOrBoth::Right(given) => given_only.push(given), + } + } + + enum RowRest { + /// `( a :: Int )` and `( a :: Int | r )` + Additional, + /// `( | r )` + Open(TypeId), + /// `( )` + Closed, + } + + impl RowRest { + fn new(only: &[&RowField], tail: Option) -> RowRest { + if !only.is_empty() { + RowRest::Additional + } else if let Some(tail) = tail { + RowRest::Open(tail) + } else { + RowRest::Closed + } + } + } + + use RowRest::*; + + let given_rest = RowRest::new(&given_only, given_row.tail); + let wanted_rest = RowRest::new(&wanted_only, wanted_row.tail); + + match given_rest { + // If there are additional given fields + Additional => match wanted_rest { + // we cannot match it against a tail-less wanted, + // nor against the additional wanted fields. + Closed | Additional => Ok(MatchType::Apart), + // we could potentially make progress by having the + // wanted tail absorb the additional given fields + Open(_) => Ok(MatchType::Stuck), + }, + // If the given row has a tail, match it against the + // additional fields and tail from the wanted row + Open(given_tail) => { + let fields = wanted_only.into_iter().cloned().collect_vec(); + let row = + context.intern_row(context.intern_row_type(RowType::new(fields, wanted_row.tail))); + result.and_then(|| match_type(state, context, bindings, equalities, row, given_tail)) + } + // If we have a closed given row + Closed => match wanted_rest { + // we cannot match it against fields in the wanted row + Additional => Ok(MatchType::Apart), + // we could make progress with an open wanted row + Open(_) => Ok(MatchType::Stuck), + // we can match it directly with a closed wanted row + Closed => Ok(result), + }, + } +} + +/// Validates that all rows in instance declaration arguments +/// do not have labels in non-determined positions. +/// +/// In PureScript, instance declarations can only contain rows with labels +/// in positions that are determined by functional dependencies. In the +/// determiner position, only row variables such as `( | r )` are valid. +pub fn validate_rows( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_item: TypeItemId, + arguments: &[TypeId], +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let functional_dependencies = get_functional_dependencies(context, class_file, class_item)?; + let all_determined = get_all_determined(&functional_dependencies); + + for (position, &argument_type) in arguments.iter().enumerate() { + if all_determined.contains(&position) { + continue; + } + + if HasLabeledRole::contains(state, context, argument_type)? { + let type_message = state.pretty_id(context, argument_type)?; + state.insert_error(ErrorKind::InstanceHeadLabeledRow { + class_file, + class_item, + position, + type_message, + }); + } + } + + Ok(()) +} + +struct CollectFileReferences<'a> { + files: &'a mut FxHashSet, +} + +impl<'a> CollectFileReferences<'a> { + fn collect( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, + files: &'a mut FxHashSet, + ) -> QueryResult<()> + where + Q: ExternalQueries, + { + walk_type(state, context, id, &mut CollectFileReferences { files }) + } +} + +impl TypeWalker for CollectFileReferences<'_> { + fn visit( + &mut self, + _state: &mut CheckState, + _context: &CheckContext, + _id: TypeId, + t: &Type, + ) -> QueryResult + where + Q: ExternalQueries, + { + if let Type::Constructor(file_id, _) = t { + self.files.insert(*file_id); + } + Ok(WalkAction::Continue) + } +} + +struct HasLabeledRole { + contains: bool, +} + +impl HasLabeledRole { + fn contains( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, + ) -> QueryResult + where + Q: ExternalQueries, + { + let mut walker = HasLabeledRole { contains: false }; + walk_type(state, context, id, &mut walker)?; + Ok(walker.contains) + } +} + +impl TypeWalker for HasLabeledRole { + fn visit( + &mut self, + _state: &mut CheckState, + context: &CheckContext, + _id: TypeId, + t: &Type, + ) -> QueryResult + where + Q: ExternalQueries, + { + if let Type::Row(row_id) = t { + let row = context.lookup_row_type(*row_id); + if !row.fields.is_empty() { + self.contains = true; + return Ok(WalkAction::Stop); + } + } + Ok(WalkAction::Continue) + } +} diff --git a/compiler-core/checking2/src/core/exhaustive.rs b/compiler-core/checking2/src/core/exhaustive.rs new file mode 100644 index 00000000..da1e82ec --- /dev/null +++ b/compiler-core/checking2/src/core/exhaustive.rs @@ -0,0 +1,1262 @@ +mod convert; +mod pretty; + +use std::iter; + +use building_types::QueryResult; +use files::FileId; +use indexing::TermItemId; +use itertools::Itertools; +use rustc_hash::FxHashSet; +use smol_str::SmolStr; + +use crate::context::CheckContext; +use crate::core::substitute::SubstituteName; +use crate::core::{Type, TypeId, normalise, toolkit}; +use crate::state::CheckState; +use crate::{ExternalQueries, safe_loop}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Pattern { + pub kind: PatternKind, + pub t: TypeId, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum PatternKind { + Wildcard, + Constructor { constructor: PatternConstructor }, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum PatternConstructor { + DataConstructor { file_id: FileId, item_id: TermItemId, fields: Vec }, + Record { labels: Vec, fields: Vec }, + Array { fields: Vec }, + Boolean(bool), + Integer(i32), + Number(bool, SmolStr), + String(SmolStr), + Char(char), +} + +impl PatternConstructor { + /// Returns the arity of this pattern constructor. + /// + /// [`PatternConstructor::DataConstructor`], [`PatternConstructor::Record`], + /// and [`PatternConstructor::Array`] have non-zero arity based on their fields. + pub fn arity(&self) -> usize { + match self { + PatternConstructor::DataConstructor { fields, .. } => fields.len(), + PatternConstructor::Record { fields, .. } => fields.len(), + PatternConstructor::Array { fields } => fields.len(), + _ => 0, + } + } + + /// Returns the fields of this pattern constructor. + /// + /// [`PatternConstructor::DataConstructor`], [`PatternConstructor::Record`], + /// and [`PatternConstructor::Array`] have fields corresponding to their arguments. + pub fn fields(&self) -> &[PatternId] { + match self { + PatternConstructor::DataConstructor { fields, .. } => fields, + PatternConstructor::Record { fields, .. } => fields, + PatternConstructor::Array { fields } => fields, + _ => &[], + } + } + + /// Checks if a pattern constructor matches another. + /// + /// This is used during the specialisation algorithm to determine if a + /// pattern row should be included in the specialised pattern matrix. + pub fn matches(&self, other: &PatternConstructor) -> bool { + match (self, other) { + ( + PatternConstructor::DataConstructor { file_id: f1, item_id: i1, .. }, + PatternConstructor::DataConstructor { file_id: f2, item_id: i2, .. }, + ) => f1 == f2 && i1 == i2, + // Any record constructor matches any other record constructor + (PatternConstructor::Record { .. }, PatternConstructor::Record { .. }) => true, + // Array constructors match only when their lengths match + ( + PatternConstructor::Array { fields: f1 }, + PatternConstructor::Array { fields: f2 }, + ) => f1.len() == f2.len(), + (PatternConstructor::Boolean(b1), PatternConstructor::Boolean(b2)) => b1 == b2, + (PatternConstructor::Integer(i1), PatternConstructor::Integer(i2)) => i1 == i2, + (PatternConstructor::Number(n1, v1), PatternConstructor::Number(n2, v2)) => { + n1 == n2 && v1 == v2 + } + (PatternConstructor::String(s1), PatternConstructor::String(s2)) => s1 == s2, + (PatternConstructor::Char(c1), PatternConstructor::Char(c2)) => c1 == c2, + _ => false, + } + } + + /// Reconstructs a [`PatternConstructor`] with the given fields. + /// + /// For [`PatternConstructor::DataConstructor`], [`PatternConstructor::Record`], + /// and [`PatternConstructor::Array`], this function overrides the fields. + /// Otherwise, the fields must be empty as enforced by an assertion. + /// + /// This algorithm is used in [`algorithm_m`] to replace the fields of the + /// pattern constructor we're specialising on with the fields generated by + /// the witnesses. + pub fn reconstruct(&self, fields: &[PatternId]) -> PatternConstructor { + match *self { + PatternConstructor::DataConstructor { file_id, item_id, .. } => { + let fields = fields.to_vec(); + PatternConstructor::DataConstructor { file_id, item_id, fields } + } + PatternConstructor::Record { ref labels, .. } => { + let fields = fields.to_vec(); + PatternConstructor::Record { labels: labels.clone(), fields } + } + PatternConstructor::Array { .. } => { + let fields = fields.to_vec(); + PatternConstructor::Array { fields } + } + PatternConstructor::Boolean(b) => { + assert!(fields.is_empty(), "Boolean constructor has arity 0"); + PatternConstructor::Boolean(b) + } + PatternConstructor::Integer(i) => { + assert!(fields.is_empty(), "Integer constructor has arity 0"); + PatternConstructor::Integer(i) + } + PatternConstructor::Number(negative, ref n) => { + assert!(fields.is_empty(), "Number constructor has arity 0"); + PatternConstructor::Number(negative, SmolStr::clone(n)) + } + PatternConstructor::String(ref s) => { + assert!(fields.is_empty(), "String constructor has arity 0"); + PatternConstructor::String(SmolStr::clone(s)) + } + PatternConstructor::Char(c) => { + assert!(fields.is_empty(), "Char constructor has arity 0"); + PatternConstructor::Char(c) + } + } + } +} + +impl MissingConstructor { + /// Constructs a witness pattern for this missing constructor. + pub fn construct_missing_witness(&self, state: &mut CheckState, t: TypeId) -> PatternId { + match *self { + MissingConstructor::DataConstructor { file_id, item_id, ref fields } => { + let fields = fields + .iter() + .map(|&field_type| state.allocate_wildcard(field_type)) + .collect_vec(); + + let constructor = PatternConstructor::DataConstructor { file_id, item_id, fields }; + let pattern = PatternKind::Constructor { constructor }; + + state.allocate_pattern(pattern, t) + } + MissingConstructor::Boolean(b) => { + let constructor = PatternConstructor::Boolean(b); + let pattern = PatternKind::Constructor { constructor }; + state.allocate_pattern(pattern, t) + } + } + } +} + +pub type PatternId = interner::Id; +pub type PatternInterner = interner::Interner; + +type PatternVector = Vec; +type PatternMatrix = Vec; +pub type WitnessVector = Vec; + +/// Determines if a [`PatternVector`] is useful with respect to a [`PatternMatrix`]. +/// +/// A pattern vector is useful if it matches at least one value not matched by +/// any pattern vector in the matrix. This is one of the core algorithms from +/// Maranget's "Warnings for pattern matching" paper. +/// +/// See [`algorithm_u_constructor`] and [`algorithm_u_wildcard`] for reference. +fn algorithm_u( + state: &mut CheckState, + context: &CheckContext, + matrix: &PatternMatrix, + vector: &PatternVector, +) -> QueryResult +where + Q: ExternalQueries, +{ + // Base case: any pattern is useful against an empty matrix + if matrix.is_empty() { + return Ok(true); + } + + // Base case: an empty pattern vector against non-empty matrix is useless + let [first_pattern, ..] = vector[..] else { + return Ok(false); + }; + + let first_pattern = state.patterns[first_pattern].clone(); + + match first_pattern.kind { + PatternKind::Constructor { constructor } => { + algorithm_u_constructor(state, context, matrix, vector, constructor) + } + PatternKind::Wildcard => { + algorithm_u_wildcard(state, context, matrix, vector, first_pattern.t) + } + } +} + +/// Induction 1 +/// +/// This function uses specialisation to spread the provided [`PatternConstructor`] +/// over both the [`PatternMatrix`] and the [`PatternVector`], before calling +/// [`algorithm_u`] recursively with the specialised structures. +fn algorithm_u_constructor( + state: &mut CheckState, + context: &CheckContext, + matrix: &PatternMatrix, + vector: &PatternVector, + constructor: PatternConstructor, +) -> QueryResult +where + Q: ExternalQueries, +{ + let constructor = canonicalise_record_constructor(state, constructor, matrix); + let specialised_matrix = specialise_matrix(state, &constructor, matrix); + + let Some(specialised_vector) = specialise_vector(state, &constructor, vector) else { + unreachable!("invariant violated: vector contains constructor"); + }; + + algorithm_u(state, context, &specialised_matrix, &specialised_vector) +} + +/// Induction 2 +/// +/// This function collects all constructor references from the first column of +/// the matrix into a collection called the sigma. +/// +/// If the sigma is complete, for each constructor in the sigma, we specialise +/// the pattern matrix and pattern vector against it. Then, we recursively call +/// [`algorithm_u`] against the specialised structures. The pattern vector is +/// useful if any specialised pattern vector is useful against its specialised +/// pattern matrix. +/// +/// If the sigma is incomplete, we recursively call [`algorithm_u`] against the +/// [`default_matrix`] of the pattern matrix and the tail of the pattern vector. +fn algorithm_u_wildcard( + state: &mut CheckState, + context: &CheckContext, + matrix: &PatternMatrix, + vector: &PatternVector, + t: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let sigma = collect_sigma(state, context, matrix, t)?; + let complete = sigma_is_complete(context, &sigma)?; + + if complete { + algorithm_u_wildcard_complete(state, context, matrix, vector, sigma) + } else { + algorithm_u_wildcard_incomplete(state, context, matrix, vector) + } +} + +fn algorithm_u_wildcard_complete( + state: &mut CheckState, + context: &CheckContext, + matrix: &PatternMatrix, + vector: &PatternVector, + sigma: Sigma, +) -> QueryResult +where + Q: ExternalQueries, +{ + for constructor in sigma.constructors { + let specialised_matrix = specialise_matrix(state, &constructor, matrix); + + let Some(specialised_vector) = specialise_vector(state, &constructor, vector) else { + unreachable!("invariant violated: vector contains constructor"); + }; + + if algorithm_u(state, context, &specialised_matrix, &specialised_vector)? { + return Ok(true); + } + } + Ok(false) +} + +fn algorithm_u_wildcard_incomplete( + state: &mut CheckState, + context: &CheckContext, + matrix: &PatternMatrix, + vector: &PatternVector, +) -> QueryResult +where + Q: ExternalQueries, +{ + let default = default_matrix(state, matrix); + let tail_columns = vector[1..].to_vec(); + algorithm_u(state, context, &default, &tail_columns) +} + +/// Determines the matching [`WitnessVector`] given a [`PatternMatrix`] +/// and some [`PatternVector`]. +/// +/// If the pattern vector is useful against the provided matrix, that is, +/// there are cases yet to be covered, this function will return a non-empty +/// list of witnesses. Inversely, if the pattern vector is useless against +/// the provided matrix, that is, the cases are exhaustive, this function +/// will return [`None`]. +/// +/// So... what exactly are witnesses? In the paper, these are defined as +/// 'value vectors' that are known not to be matched against the pattern +/// matrix but are instantiations of the pattern vector. In our implementation, +/// these witnesses are pattern vectors that denote values not yet covered by +/// the matrix. +/// +/// The [`algorithm_m_wildcard`] induction is prolific for producing these +/// these witnesses as it compares the constructors that appear in the +/// matrix against the constructors available in the checking environment. +fn algorithm_m( + state: &mut CheckState, + context: &CheckContext, + matrix: &PatternMatrix, + vector: &PatternVector, +) -> QueryResult>> +where + Q: ExternalQueries, +{ + // Base case: any pattern is its own witness against an empty matrix + if matrix.is_empty() { + let vector = vector.clone(); + return Ok(Some(vec![vector])); + } + + // Base case: an empty pattern vector against non-empty matrix has no witnesses + let [first_pattern, ..] = vector[..] else { + return Ok(None); + }; + + let first_pattern = state.patterns[first_pattern].clone(); + + match first_pattern.kind { + PatternKind::Constructor { constructor } => { + algorithm_m_constructor(state, context, matrix, vector, constructor, first_pattern.t) + } + PatternKind::Wildcard => { + algorithm_m_wildcard(state, context, matrix, vector, first_pattern.t) + } + } +} + +/// Induction 1 +/// +/// This function uses specialisation to spread the provided [`PatternConstructor`] +/// over both the [`PatternMatrix`] and the [`PatternVector`], before calling +/// [`algorithm_m`] recursively with the specialised structures. +/// +/// The final set of witnesses returned by this induction includes a +/// reconstruction of the original constructor passed to this function. +/// +/// See documentation for [`specialise_matrix`] and [`specialise_vector`] for +/// more information on what specialisation entails given a constructor. +fn algorithm_m_constructor( + state: &mut CheckState, + context: &CheckContext, + matrix: &PatternMatrix, + vector: &PatternVector, + constructor: PatternConstructor, + t: TypeId, +) -> QueryResult>> +where + Q: ExternalQueries, +{ + let constructor = canonicalise_record_constructor(state, constructor, matrix); + let arity = constructor.arity(); + + let specialised_matrix = specialise_matrix(state, &constructor, matrix); + + let Some(specialised_vector) = specialise_vector(state, &constructor, vector) else { + unreachable!("invariant violated: vector contains constructor"); + }; + + let witnesses = algorithm_m(state, context, &specialised_matrix, &specialised_vector)?; + + let Some(witnesses) = witnesses else { + return Ok(None); + }; + + let witnesses = witnesses.into_iter().map(|witness| { + let (argument_columns, tail_columns) = witness.split_at(arity); + + let constructor = constructor.reconstruct(argument_columns); + let constructor_id = state.allocate_constructor(constructor, t); + let tail_columns = tail_columns.iter().copied(); + + iter::once(constructor_id).chain(tail_columns).collect() + }); + + let witnesses = witnesses.collect(); + Ok(Some(witnesses)) +} + +/// Induction 2 +/// +/// If the first column in the [`PatternVector`] is a wildcard, this function +/// produces witnesses that correspond to patterns not yet covered by the +/// [`PatternMatrix`]. This is where pattern suggestion warnings are built +/// for the compiler! +/// +/// This function collects all constructor references from the first column +/// of all rows in the matrix into a collection called the sigma. We handle +/// the structure in different ways: +/// +/// If the sigma is complete, for each constructor in the sigma, we apply +/// a rule similar to [`algorithm_m_constructor`] to collect witnesses +/// across all constructors. +/// +/// If the sigma is incomplete, we recursively apply [`algorithm_m`] to the +/// [`default_matrix`] of the pattern matrix and the tail columns of the +/// pattern vector. The induction ends if the recursive call is exhaustive. +/// +/// If the recursive call returns witnesses, and the sigma is non-empty, +/// we move our attention to generating [`Constructor`] patterns for +/// constructors not present in the sigma. This is what we use for +/// reporting pattern warnings. Otherwise, if the sigma is empty, we +/// simply produce a wildcard pattern. +fn algorithm_m_wildcard( + state: &mut CheckState, + context: &CheckContext, + matrix: &PatternMatrix, + vector: &PatternVector, + t: TypeId, +) -> QueryResult>> +where + Q: ExternalQueries, +{ + let sigma = collect_sigma(state, context, matrix, t)?; + let complete = sigma_is_complete(context, &sigma)?; + if complete { + algorithm_m_wildcard_complete(state, context, matrix, vector, t, &sigma) + } else { + algorithm_m_wildcard_incomplete(state, context, matrix, vector, t, &sigma) + } +} + +fn algorithm_m_wildcard_complete( + state: &mut CheckState, + context: &CheckContext, + matrix: &PatternMatrix, + vector: &PatternVector, + t: TypeId, + sigma: &Sigma, +) -> QueryResult>> +where + Q: ExternalQueries, +{ + let mut all_witnesses = vec![]; + + for constructor in &sigma.constructors { + let arity = constructor.arity(); + + let specialised_matrix = specialise_matrix(state, constructor, matrix); + + let Some(specialised_vector) = specialise_vector(state, constructor, vector) else { + unreachable!("invariant violated: vector contains constructor"); + }; + + if let Some(witnesses) = + algorithm_m(state, context, &specialised_matrix, &specialised_vector)? + { + for witness in witnesses { + let (argument_columns, tail_columns) = witness.split_at(arity); + + let constructor = constructor.reconstruct(argument_columns); + let constructor_id = state.allocate_constructor(constructor, t); + let tail_columns = tail_columns.iter().copied(); + + let witnesses = iter::once(constructor_id).chain(tail_columns).collect(); + all_witnesses.push(witnesses); + } + } + } + + if all_witnesses.is_empty() { Ok(None) } else { Ok(Some(all_witnesses)) } +} + +fn algorithm_m_wildcard_incomplete( + state: &mut CheckState, + context: &CheckContext, + matrix: &PatternMatrix, + vector: &PatternVector, + t: TypeId, + sigma: &Sigma, +) -> QueryResult>> +where + Q: ExternalQueries, +{ + let default = default_matrix(state, matrix); + let tail_columns = vector[1..].to_vec(); + + let witnesses = algorithm_m(state, context, &default, &tail_columns)?; + + let Some(witnesses) = witnesses else { + return Ok(None); + }; + + let first_column = if let Some(constructor) = sigma.missing.first() { + constructor.construct_missing_witness(state, t) + } else { + state.allocate_wildcard(t) + }; + + let witness = witnesses + .into_iter() + .map(|witness| iter::once(first_column).chain(witness).collect()) + .collect(); + + Ok(Some(witness)) +} + +/// Computes a canonical [`PatternConstructor::Record`] that includes the union of all +/// record labels appearing in the first column of the matrix and the given constructor. +/// +/// Record patterns in PureScript can mention different subsets of the full row type. +/// For example, `{ a }` and `{ a, b }` both match `{ a :: Int, b :: Int }`, but they +/// produce record constructors with different arities. The specialisation algorithm +/// requires all rows to have the same width, so we normalise to a canonical label set +/// before specialising. +/// +/// For non-record constructors this function returns the constructor unchanged. +fn canonicalise_record_constructor( + state: &CheckState, + constructor: PatternConstructor, + matrix: &PatternMatrix, +) -> PatternConstructor { + let PatternConstructor::Record { labels, fields } = &constructor else { + return constructor; + }; + + let mut canonical = { + let labels = labels.iter().cloned(); + let fields = fields.iter().copied(); + labels.zip(fields).collect_vec() + }; + + let initial_length = canonical.len(); + + for row in matrix { + let Some(&first) = row.first() else { + continue; + }; + + let pattern = state.patterns[first].clone(); + if let PatternKind::Constructor { + constructor: PatternConstructor::Record { labels, fields }, + } = pattern.kind + { + for (label, field) in iter::zip(labels, fields) { + if !canonical.iter().any(|(existing, _)| existing == &label) { + canonical.push((label, field)); + } + } + } + } + + if canonical.len() == initial_length { + return constructor; + } + + // Subtle: stable sorting by label + canonical.sort_by(|(a, _), (b, _)| a.cmp(b)); + + let (labels, fields) = canonical.into_iter().unzip(); + PatternConstructor::Record { labels, fields } +} + +/// Specialises a [`PatternMatrix`] given a [`PatternConstructor`]. +/// +/// See documentation below for [`specialise_vector`]. +fn specialise_matrix( + state: &mut CheckState, + expected: &PatternConstructor, + matrix: &PatternMatrix, +) -> PatternMatrix { + matrix.iter().filter_map(|row| specialise_vector(state, expected, row)).collect() +} + +/// Specialises a [`PatternVector`] given a [`PatternConstructor`]. +/// +/// Specialisation takes a pattern vector and applies the following rules: +/// 1. If the first column is a wildcard, it expands it to `n` wildcards +/// where `n` is the arity of the expected [`PatternConstructor`]. +/// For non-ADT constructors, arity is 0 (no expansion needed). +/// 2. It returns `None` for constructors that are not the expected +/// [`PatternConstructor`], which excludes them from the specialised matrix. +/// For example, a pattern vector specialised on `Just` removes `Nothing`. +/// 3. For matching constructors, it 'splats' the fields, effectively turning +/// a pattern vector like `[Just _]` into `[_]` or `[Nothing]` into `[]`. +/// For non-ADT constructors, arity is 0 so nothing is splatted. +fn specialise_vector( + state: &mut CheckState, + expected: &PatternConstructor, + vector: &PatternVector, +) -> Option { + let [first_column_id, ref tail_columns @ ..] = vector[..] else { + unreachable!("invariant violated: specialise_vector processed empty row"); + }; + + // Clone to release any borrow on state.patterns, allowing mutable + // access later when allocating wildcard patterns for record padding. + let first_pattern = state.patterns[first_column_id].clone(); + + if let PatternKind::Wildcard = first_pattern.kind { + // Expand wildcard to the expected constructor's arity + match expected { + PatternConstructor::DataConstructor { fields, .. } + | PatternConstructor::Record { fields, .. } => { + let wildcards = fields.iter().map(|&pattern_id| { + let t = state.patterns[pattern_id].t; + state.allocate_wildcard(t) + }); + let tail_columns = tail_columns.iter().copied(); + return Some(iter::chain(wildcards, tail_columns).collect()); + } + PatternConstructor::Array { fields } => { + let wildcards = fields.iter().map(|&pattern_id| { + let t = state.patterns[pattern_id].t; + state.allocate_wildcard(t) + }); + let tail_columns = tail_columns.iter().copied(); + return Some(iter::chain(wildcards, tail_columns).collect()); + } + _ => { + return Some(tail_columns.to_vec()); + } + } + } + + let PatternKind::Constructor { constructor } = first_pattern.kind else { + return Some(tail_columns.to_vec()); + }; + + // Check if constructors match + if !constructor.matches(expected) { + return None; + } + + // Splat fields for constructors with arity + match &constructor { + PatternConstructor::DataConstructor { fields, .. } => { + Some(fields.iter().copied().chain(tail_columns.iter().copied()).collect()) + } + PatternConstructor::Record { labels: actual_labels, fields: actual_fields } => { + specialise_record_fields(state, expected, actual_labels, actual_fields, tail_columns) + } + PatternConstructor::Array { fields } => { + Some(fields.iter().copied().chain(tail_columns.iter().copied()).collect()) + } + _ => Some(tail_columns.to_vec()), + } +} + +/// Maps the fields of an actual record pattern to the expected (canonical) label set. +/// +/// When record patterns in different branches mention different subsets of labels, +/// the actual pattern may have fewer labels than the expected canonical constructor. +/// This function aligns the actual fields to the expected label positions, inserting +/// wildcard patterns for any labels present in the expected set but absent from the +/// actual pattern. +fn specialise_record_fields( + state: &mut CheckState, + expected: &PatternConstructor, + actual_labels: &[SmolStr], + actual_fields: &[PatternId], + tail_columns: &[PatternId], +) -> Option { + let PatternConstructor::Record { labels: expected_labels, fields: expected_fields } = expected + else { + return Some( + iter::chain(actual_fields.iter().copied(), tail_columns.iter().copied()).collect(), + ); + }; + + // Fast path: labels match exactly. + if actual_labels == expected_labels.as_slice() { + return Some( + iter::chain(actual_fields.iter().copied(), tail_columns.iter().copied()).collect(), + ); + } + + let mut mapped_fields = Vec::with_capacity(expected_labels.len()); + for (expected_label, &expected_field) in expected_labels.iter().zip(expected_fields.iter()) { + if let Some(position) = actual_labels.iter().position(|label| label == expected_label) { + mapped_fields.push(actual_fields[position]); + } else { + let t = state.patterns[expected_field].t; + mapped_fields.push(state.allocate_wildcard(t)); + } + } + + Some(mapped_fields.into_iter().chain(tail_columns.iter().copied()).collect()) +} + +fn default_matrix(state: &CheckState, matrix: &PatternMatrix) -> PatternMatrix { + let filter_map = matrix.iter().filter_map(|row| { + let [first_column, ref default_columns @ ..] = row[..] else { + unreachable!("invariant violated: default_matrix processed empty row"); + }; + if let PatternKind::Wildcard = state.patterns[first_column].kind { + Some(default_columns.to_vec()) + } else { + None + } + }); + filter_map.collect() +} + +/// Key for identifying a unique constructor. +#[derive(Clone, PartialEq, Eq, Hash)] +enum ConstructorKey { + Data(FileId, TermItemId), + Record, + Array(usize), + Boolean(bool), + Integer(i32), + Number(bool, SmolStr), + String(SmolStr), + Char(char), +} + +impl ConstructorKey { + fn from_pattern_constructor(pc: &PatternConstructor) -> Self { + match pc { + PatternConstructor::DataConstructor { file_id, item_id, .. } => { + ConstructorKey::Data(*file_id, *item_id) + } + // All record constructors share the same key (single constructor semantics) + PatternConstructor::Record { .. } => ConstructorKey::Record, + // Array constructors are keyed by their length + PatternConstructor::Array { fields } => ConstructorKey::Array(fields.len()), + PatternConstructor::Boolean(b) => ConstructorKey::Boolean(*b), + PatternConstructor::Integer(i) => ConstructorKey::Integer(*i), + PatternConstructor::Number(negative, n) => { + let n = SmolStr::clone(n); + ConstructorKey::Number(*negative, n) + } + PatternConstructor::String(s) => { + let s = SmolStr::clone(s); + ConstructorKey::String(s) + } + PatternConstructor::Char(c) => ConstructorKey::Char(*c), + } + } +} + +#[derive(Clone, Debug)] +struct Sigma { + constructors: Vec, + missing: Vec, +} + +#[derive(Clone, Debug)] +enum MissingConstructor { + DataConstructor { file_id: FileId, item_id: TermItemId, fields: Vec }, + Boolean(bool), +} + +/// Extracts the sigma, a set of constructors from the first column of the matrix. +/// +/// Returns a list of unique constructors seen in the first column, keeping one +/// representative [`PatternConstructor`] per distinct constructor. Other patterns +/// like wildcards, records, and arrays are ignored for now. +fn collect_sigma( + state: &mut CheckState, + context: &CheckContext, + matrix: &PatternMatrix, + scrutinee_type: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut seen = FxHashSet::default(); + let mut constructors = vec![]; + + for row in matrix { + let [first_column, ..] = row[..] else { + continue; + }; + let pattern = state.patterns[first_column].clone(); + if let PatternKind::Constructor { constructor } = pattern.kind { + let key = ConstructorKey::from_pattern_constructor(&constructor); + if seen.insert(key) { + constructors.push(constructor); + } + } + } + + // Canonicalise record constructors to include all labels from the matrix. + // Different record patterns may mention different subsets of the row type, + // and the specialisation algorithm requires a consistent arity. + if let Some(index) = + constructors.iter().position(|c| matches!(c, PatternConstructor::Record { .. })) + { + let record = constructors.remove(index); + let canonical = canonicalise_record_constructor(state, record, matrix); + constructors.insert(index, canonical); + } + + let missing = collect_missing_constructors(state, context, scrutinee_type, &constructors)?; + Ok(Sigma { constructors, missing }) +} + +/// Checks whether the set of constructors (sigma) is complete for the scrutinee type. +/// +/// A sigma is complete if it contains all constructors of the data type. +/// For Boolean, both true and false must be present. +/// For records, there is exactly one constructor, so any record pattern makes sigma complete. +/// For other literal constructors (Integer, Number, String, Char), sigma is never complete +/// (infinite domains). +/// If we can't determine the type or its constructors, we conservatively return false. +fn sigma_is_complete(context: &CheckContext, sigma: &Sigma) -> QueryResult +where + Q: ExternalQueries, +{ + // Empty sigma is never complete + let Some(first) = sigma.constructors.first() else { + return Ok(false); + }; + + match first { + PatternConstructor::DataConstructor { file_id, item_id, .. } => { + // Get the indexed module for the constructor's file + let indexed = context.queries.indexed(*file_id)?; + + // Find the type this constructor belongs to + let Some(type_item_id) = indexed.pairs.constructor_type(*item_id) else { + return Ok(false); + }; + + // Get all constructors for this type + let all_constructors: FxHashSet = + indexed.pairs.data_constructors(type_item_id).collect(); + + // Check if sigma covers all constructors + let sigma_terms: FxHashSet = sigma + .constructors + .iter() + .filter_map(|c| match c { + PatternConstructor::DataConstructor { item_id, .. } => Some(*item_id), + _ => None, + }) + .collect(); + + Ok(all_constructors.iter().all(|term_id| sigma_terms.contains(term_id))) + } + // Records have exactly one constructor, so sigma is always complete for records + PatternConstructor::Record { .. } => Ok(true), + // Arrays have infinite possible lengths, so sigma is never complete + PatternConstructor::Array { .. } => Ok(false), + PatternConstructor::Boolean(_) => { + // Boolean is complete when both true and false are present + let has_true = + sigma.constructors.iter().any(|c| matches!(c, PatternConstructor::Boolean(true))); + let has_false = + sigma.constructors.iter().any(|c| matches!(c, PatternConstructor::Boolean(false))); + Ok(has_true && has_false) + } + // Other literal constructors have infinite domains, so they're never complete + PatternConstructor::Integer(_) + | PatternConstructor::Number(_, _) + | PatternConstructor::String(_) + | PatternConstructor::Char(_) => Ok(false), + } +} + +fn collect_missing_constructors( + state: &mut CheckState, + context: &CheckContext, + scrutinee_type: TypeId, + constructors: &[PatternConstructor], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let Some(first_constructor) = constructors.first() else { + return Ok(vec![]); + }; + + match first_constructor { + PatternConstructor::DataConstructor { file_id, item_id, .. } => { + let indexed = context.queries.indexed(*file_id)?; + + let Some(type_item_id) = indexed.pairs.constructor_type(*item_id) else { + return Ok(vec![]); + }; + + let sigma: FxHashSet = constructors + .iter() + .filter_map(|c| match c { + PatternConstructor::DataConstructor { item_id, .. } => Some(*item_id), + _ => None, + }) + .collect(); + let arguments = extract_all_applications(state, context, scrutinee_type)?; + + let mut missing = vec![]; + for missing_item_id in indexed.pairs.data_constructors(type_item_id) { + if !sigma.contains(&missing_item_id) { + let fields = constructor_field_types( + state, + context, + *file_id, + missing_item_id, + &arguments, + )?; + missing.push(MissingConstructor::DataConstructor { + file_id: *file_id, + item_id: missing_item_id, + fields, + }); + } + } + + Ok(missing) + } + // Arrays have infinite possible lengths, so we don't report specific missing values. + // The algorithm will fall back to wildcard suggestion. + PatternConstructor::Array { .. } => Ok(vec![]), + PatternConstructor::Boolean(_) => { + // Check which boolean values are missing + let has_true = + constructors.iter().any(|c| matches!(c, PatternConstructor::Boolean(true))); + let has_false = + constructors.iter().any(|c| matches!(c, PatternConstructor::Boolean(false))); + let mut missing = vec![]; + if !has_true { + missing.push(MissingConstructor::Boolean(true)); + } + if !has_false { + missing.push(MissingConstructor::Boolean(false)); + } + Ok(missing) + } + // Other literal constructors have infinite domains, so we don't report specific missing values + _ => Ok(vec![]), + } +} + +fn constructor_field_types( + state: &mut CheckState, + context: &CheckContext, + file_id: FileId, + term_id: TermItemId, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let constructor_type = if file_id == context.id { + state.checked.lookup_term(term_id) + } else { + let checked = context.queries.checked2(file_id)?; + checked.lookup_term(term_id) + }; + + if let Some(constructor_type) = constructor_type { + let constructor = instantiate_with_arguments(state, context, constructor_type, arguments)?; + let toolkit::InspectFunction { arguments: fields, .. } = + toolkit::inspect_function(state, context, constructor)?; + Ok(fields) + } else { + let arity = get_constructor_arity(context, file_id, term_id)?; + let unknown = context.unknown("missing constructor field"); + Ok(iter::repeat_n(unknown, arity).collect()) + } +} + +/// Extracts type and kind arguments from a type application. +/// +/// Peels off [`Type::Application`] and [`Type::KindApplication`] layers, +/// collecting both type and kind application arguments. +fn extract_all_applications( + state: &mut CheckState, + context: &CheckContext, + applied_type: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let mut arguments = vec![]; + let mut current_id = applied_type; + + safe_loop! { + current_id = normalise::expand(state, context, current_id)?; + match context.lookup_type(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(); + Ok(arguments) +} + +/// Instantiates [`Type::Forall`] with the provided arguments. +/// +/// This function falls back to constructing rigid variables if there's +/// not enough arguments provided. +fn instantiate_with_arguments( + state: &mut CheckState, + context: &CheckContext, + mut type_id: TypeId, + arguments: impl AsRef<[TypeId]>, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut arguments_iter = arguments.as_ref().iter().copied(); + + safe_loop! { + type_id = normalise::expand(state, context, type_id)?; + match context.lookup_type(type_id) { + Type::Forall(binder_id, inner) => { + let binder = context.lookup_forall_binder(binder_id); + let argument_type = if let Some(argument) = arguments_iter.next() { + argument + } else { + context.intern_rigid(binder.name, state.depth, binder.kind) + }; + type_id = SubstituteName::one(state, context, binder.name, argument_type, inner)?; + } + _ => break, + } + } + + Ok(type_id) +} + +/// Gets the arity (number of fields) of a constructor. +fn get_constructor_arity( + context: &CheckContext, + file_id: FileId, + term_id: TermItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let on_lowered = |lowered: &lowering::LoweredModule| { + if let Some(lowering::TermItemIr::Constructor { arguments }) = + lowered.info.get_term_item(term_id) + { + arguments.len() + } else { + 0 + } + }; + if file_id == context.id { + let lowered = &context.lowered; + Ok(on_lowered(lowered)) + } else { + let lowered = context.queries.lowered(file_id)?; + Ok(on_lowered(&lowered)) + } +} + +pub struct ExhaustivenessReport { + pub missing: Option>, + pub redundant: Vec, +} + +pub fn check_lambda_patterns( + state: &mut CheckState, + context: &CheckContext, + pattern_types: &[TypeId], + binders: &[lowering::BinderId], +) -> QueryResult +where + Q: ExternalQueries, +{ + if pattern_types.is_empty() { + return Ok(ExhaustivenessReport { missing: None, redundant: vec![] }); + } + + let unconditional = + collect_unconditional_rows(state, context, &[binders], pattern_types, |binders| { + (binders, &None) + })?; + + check_exhaustiveness_core(state, context, pattern_types, unconditional) +} + +pub fn check_case_patterns( + state: &mut CheckState, + context: &CheckContext, + pattern_types: &[TypeId], + branches: &[lowering::CaseBranch], +) -> QueryResult +where + Q: ExternalQueries, +{ + if pattern_types.is_empty() { + return Ok(ExhaustivenessReport { missing: None, redundant: vec![] }); + } + + let unconditional = collect_unconditional_rows( + state, + context, + branches, + pattern_types, + |branch: &lowering::CaseBranch| (&branch.binders, &branch.guarded_expression), + )?; + + check_exhaustiveness_core(state, context, pattern_types, unconditional) +} + +pub fn check_equation_patterns( + state: &mut CheckState, + context: &CheckContext, + pattern_types: &[TypeId], + equations: &[lowering::Equation], +) -> QueryResult +where + Q: ExternalQueries, +{ + if pattern_types.is_empty() { + return Ok(ExhaustivenessReport { missing: None, redundant: vec![] }); + } + + let unconditional = collect_unconditional_rows( + state, + context, + equations, + pattern_types, + |equation: &lowering::Equation| (&equation.binders, &equation.guarded), + )?; + + check_exhaustiveness_core(state, context, pattern_types, unconditional) +} + +/// Returns `true` if any alternative in a conditional guard set is trivially true. +fn has_trivially_true_alternative( + context: &CheckContext, + pattern_guarded: &[lowering::PatternGuarded], +) -> bool +where + Q: ExternalQueries, +{ + pattern_guarded.iter().any(|pg| { + !pg.pattern_guards.is_empty() + && pg.pattern_guards.iter().all(|g| is_trivially_true_guard(context, g)) + }) +} + +/// Returns `true` if the guard is `true` or `otherwise` from `Data.Boolean`. +fn is_trivially_true_guard(context: &CheckContext, guard: &lowering::PatternGuard) -> bool +where + Q: ExternalQueries, +{ + if guard.binder.is_some() { + return false; + } + let Some(expr_id) = guard.expression else { + return false; + }; + let Some(kind) = context.lowered.info.get_expression_kind(expr_id) else { + return false; + }; + match kind { + lowering::ExpressionKind::Boolean { boolean: true } => true, + lowering::ExpressionKind::Variable { + resolution: Some(lowering::TermVariableResolution::Reference(file_id, term_id)), + } => context.known_terms.otherwise == Some((*file_id, *term_id)), + _ => false, + } +} + +fn collect_unconditional_rows( + state: &mut CheckState, + context: &CheckContext, + items: &[T], + pattern_types: &[TypeId], + to_binders: F, +) -> QueryResult> +where + Q: ExternalQueries, + F: Fn(&T) -> (&[lowering::BinderId], &Option), +{ + let mut pattern_rows = vec![]; + for item in items { + let (binders, guarded) = to_binders(item); + + match guarded { + Some(lowering::GuardedExpression::Unconditional { .. }) | None => {} + Some(lowering::GuardedExpression::Conditionals { pattern_guarded }) => { + if !has_trivially_true_alternative(context, pattern_guarded) { + continue; + } + } + } + + let mut pattern_row = vec![]; + for &binder_id in binders { + pattern_row.push(convert::convert_binder(state, context, binder_id)?); + } + + let additional = pattern_types.iter().skip(pattern_row.len()); + pattern_row.extend(additional.map(|&t| state.allocate_wildcard(t))); + + if !pattern_row.is_empty() { + pattern_rows.push(pattern_row); + } + } + Ok(pattern_rows) +} + +fn check_exhaustiveness_core( + state: &mut CheckState, + context: &CheckContext, + pattern_types: &[TypeId], + unconditional: PatternMatrix, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut redundant = vec![]; + let mut matrix = vec![]; + for vector in &unconditional { + let useful = algorithm_u(state, context, &matrix, vector)?; + if useful { + matrix.push(PatternVector::clone(vector)); + } else { + redundant.push(pretty::pretty_witness(context, state, vector)); + } + } + + let query = pattern_types.iter().map(|&t| state.allocate_wildcard(t)).collect(); + let witnesses = algorithm_m(state, context, &unconditional, &query)?; + let missing = witnesses.map(|witnesses| { + witnesses + .iter() + .take(5) + .map(|witness| pretty::pretty_witness(context, state, witness)) + .collect() + }); + + Ok(ExhaustivenessReport { missing, redundant }) +} diff --git a/compiler-core/checking2/src/core/exhaustive/convert.rs b/compiler-core/checking2/src/core/exhaustive/convert.rs new file mode 100644 index 00000000..fa5e2c9c --- /dev/null +++ b/compiler-core/checking2/src/core/exhaustive/convert.rs @@ -0,0 +1,382 @@ +use std::sync::Arc; + +use building_types::QueryResult; +use files::FileId; +use indexing::TermItemId; +use itertools::Itertools; +use lowering::{BinderId, TermOperatorId}; +use rustc_hash::FxHashMap; +use smol_str::SmolStr; +use sugar::OperatorTree; + +use crate::context::CheckContext; +use crate::core::exhaustive::{PatternConstructor, PatternId}; +use crate::core::{Type, TypeId, normalise}; +use crate::state::CheckState; +use crate::{ExternalQueries, OperatorBranchTypes, safe_loop}; + +pub fn convert_binder( + state: &mut CheckState, + context: &CheckContext, + id: BinderId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let t = state + .checked + .nodes + .lookup_binder(id) + .unwrap_or_else(|| context.unknown("unresolved binder")); + + let Some(kind) = context.lowered.info.get_binder_kind(id) else { + return Ok(state.allocate_wildcard(t)); + }; + + match kind { + lowering::BinderKind::Typed { binder, .. } => match binder { + Some(id) => convert_binder(state, context, *id), + None => Ok(state.allocate_wildcard(t)), + }, + lowering::BinderKind::OperatorChain { .. } => { + convert_operator_chain_binder(state, context, id, t) + } + lowering::BinderKind::Integer { value } => match value { + Some(v) => { + let constructor = PatternConstructor::Integer(*v); + Ok(state.allocate_constructor(constructor, t)) + } + None => Ok(state.allocate_wildcard(t)), + }, + lowering::BinderKind::Number { negative, value } => { + if let Some(value) = value { + let constructor = PatternConstructor::Number(*negative, SmolStr::clone(value)); + Ok(state.allocate_constructor(constructor, t)) + } else { + Ok(state.allocate_wildcard(t)) + } + } + lowering::BinderKind::Constructor { resolution, arguments } => { + convert_constructor_binder(state, context, resolution, arguments, t) + } + lowering::BinderKind::Variable { .. } => Ok(state.allocate_wildcard(t)), + lowering::BinderKind::Named { binder, .. } => match binder { + Some(id) => convert_binder(state, context, *id), + None => Ok(state.allocate_wildcard(t)), + }, + lowering::BinderKind::Wildcard => Ok(state.allocate_wildcard(t)), + lowering::BinderKind::String { value, .. } => { + if let Some(value) = value { + let constructor = PatternConstructor::String(SmolStr::clone(value)); + Ok(state.allocate_constructor(constructor, t)) + } else { + Ok(state.allocate_wildcard(t)) + } + } + lowering::BinderKind::Char { value } => match value { + Some(v) => { + let constructor = PatternConstructor::Char(*v); + Ok(state.allocate_constructor(constructor, t)) + } + None => Ok(state.allocate_wildcard(t)), + }, + lowering::BinderKind::Boolean { boolean } => { + let constructor = PatternConstructor::Boolean(*boolean); + Ok(state.allocate_constructor(constructor, t)) + } + lowering::BinderKind::Array { array } => lower_array_binder(state, context, array, t), + lowering::BinderKind::Record { record } => lower_record_binder(state, context, record, t), + lowering::BinderKind::Parenthesized { parenthesized } => match parenthesized { + Some(id) => convert_binder(state, context, *id), + None => Ok(state.allocate_wildcard(t)), + }, + } +} + +fn lower_array_binder( + state: &mut CheckState, + context: &CheckContext, + array: &[BinderId], + t: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut fields = vec![]; + for &element in array { + fields.push(convert_binder(state, context, element)?); + } + let constructor = PatternConstructor::Array { fields }; + Ok(state.allocate_constructor(constructor, t)) +} + +fn lower_record_binder( + state: &mut CheckState, + context: &CheckContext, + record: &[lowering::BinderRecordItem], + t: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + match try_build_record_constructor(state, context, record, t)? { + Some((labels, fields)) => { + let constructor = PatternConstructor::Record { labels, fields }; + Ok(state.allocate_constructor(constructor, t)) + } + None => { + // Fallback: use a wildcard when we can't build a canonical record constructor + Ok(state.allocate_wildcard(t)) + } + } +} + +fn try_build_record_constructor( + state: &mut CheckState, + context: &CheckContext, + record: &[lowering::BinderRecordItem], + t: TypeId, +) -> QueryResult, Vec)>> +where + Q: ExternalQueries, +{ + let expanded_t = normalise_expand_type(state, context, t)?; + + let (constructor, arguments) = extract_type_application(state, context, expanded_t)?; + + if constructor != context.prim.record { + return Ok(None); + } + + let Some(row_type_id) = arguments.first() else { + return Ok(None); + }; + + let row_type_id = normalise::expand(state, context, *row_type_id)?; + + let row_fields = if let Type::Row(row_type_id) = context.lookup_type(row_type_id) { + context.lookup_row_type(row_type_id).fields + } else { + return Ok(None); + }; + + let field_type_map: FxHashMap = + row_fields.iter().map(|field| (SmolStr::clone(&field.label), field.id)).collect(); + + let mut provided_patterns = FxHashMap::default(); + for element in record.iter() { + match element { + lowering::BinderRecordItem::RecordField { name, value } => { + let Some(name) = name.clone() else { + continue; + }; + + let pattern = if let Some(value) = value { + convert_binder(state, context, *value)? + } else { + state.allocate_wildcard(context.unknown("record field")) + }; + + provided_patterns.insert(name, pattern); + } + lowering::BinderRecordItem::RecordPun { id: _, name } => { + let Some(name) = name.clone() else { + continue; + }; + + let t = field_type_map + .get(&name) + .copied() + .unwrap_or_else(|| context.unknown("record pun")); + + let pattern = state.allocate_wildcard(t); + provided_patterns.insert(name, pattern); + } + } + } + + let mut sorted_labels = field_type_map.keys().cloned().collect_vec(); + sorted_labels.sort(); + + let mut labels = Vec::with_capacity(sorted_labels.len()); + let mut fields = Vec::with_capacity(sorted_labels.len()); + + for label in sorted_labels { + let pattern = provided_patterns.get(&label).copied().unwrap_or_else(|| { + let t = field_type_map[&label]; + state.allocate_wildcard(t) + }); + labels.push(label); + fields.push(pattern); + } + + Ok(Some((labels, fields))) +} + +fn convert_constructor_binder( + state: &mut CheckState, + context: &CheckContext, + resolution: &Option<(FileId, TermItemId)>, + arguments: &Arc<[BinderId]>, + t: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let Some((file_id, item_id)) = *resolution else { + return Ok(state.allocate_wildcard(t)); + }; + + let mut fields = vec![]; + for &argument in arguments.iter() { + fields.push(convert_binder(state, context, argument)?); + } + + let constructor = PatternConstructor::DataConstructor { file_id, item_id, fields }; + Ok(state.allocate_constructor(constructor, t)) +} + +fn convert_operator_chain_binder( + state: &mut CheckState, + context: &CheckContext, + id: BinderId, + t: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let Some(tree) = context.bracketed.binders.get(&id) else { + return Ok(state.allocate_wildcard(t)); + }; + + let Ok(tree) = tree else { + return Ok(state.allocate_wildcard(t)); + }; + + convert_operator_tree(state, context, tree, t) +} + +fn convert_operator_tree( + state: &mut CheckState, + context: &CheckContext, + tree: &OperatorTree, + t: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + match tree { + OperatorTree::Leaf(None) => Ok(state.allocate_wildcard(t)), + OperatorTree::Leaf(Some(binder_id)) => convert_binder(state, context, *binder_id), + OperatorTree::Branch(operator_id, children) => { + convert_operator_branch(state, context, *operator_id, children, t) + } + } +} + +fn convert_operator_branch( + state: &mut CheckState, + context: &CheckContext, + operator_id: TermOperatorId, + children: &[OperatorTree; 2], + t: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let Some((file_id, item_id)) = context.lowered.info.get_term_operator(operator_id) else { + return Ok(state.allocate_wildcard(t)); + }; + + // The operator_id points to itself, thus we need to follow the + // resolution to find the constructor that it actually points to. + let Some((constructor_file_id, constructor_item_id)) = + resolve_term_operator(context, file_id, item_id)? + else { + return Ok(state.allocate_wildcard(t)); + }; + + let Some(OperatorBranchTypes { left, right, result }) = + state.checked.nodes.lookup_term_operator(operator_id) + else { + return Ok(state.allocate_wildcard(t)); + }; + + let [left_tree, right_tree] = children; + + let left_pattern = convert_operator_tree(state, context, left_tree, left)?; + let right_pattern = convert_operator_tree(state, context, right_tree, right)?; + + let constructor = PatternConstructor::DataConstructor { + file_id: constructor_file_id, + item_id: constructor_item_id, + fields: vec![left_pattern, right_pattern], + }; + + Ok(state.allocate_constructor(constructor, result)) +} + +fn resolve_term_operator( + context: &CheckContext, + file_id: FileId, + item_id: TermItemId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let on_lowered = |lowered: &lowering::LoweredModule| { + if let Some(lowering::TermItemIr::Operator { resolution, .. }) = + lowered.info.get_term_item(item_id) + { + *resolution + } else { + None + } + }; + + if file_id == context.id { + Ok(on_lowered(&context.lowered)) + } else { + let lowered = context.queries.lowered(file_id)?; + Ok(on_lowered(&lowered)) + } +} + +fn extract_type_application( + state: &mut CheckState, + context: &CheckContext, + mut type_id: TypeId, +) -> QueryResult<(TypeId, Vec)> +where + Q: ExternalQueries, +{ + let mut arguments = vec![]; + + safe_loop! { + type_id = normalise::expand(state, context, type_id)?; + match context.lookup_type(type_id) { + Type::Application(function, argument) => { + arguments.push(argument); + type_id = function; + } + Type::KindApplication(function, _) => { + type_id = function; + } + _ => break, + } + } + + arguments.reverse(); + Ok((type_id, arguments)) +} + +fn normalise_expand_type( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + normalise::expand(state, context, type_id) +} diff --git a/compiler-core/checking2/src/core/exhaustive/pretty.rs b/compiler-core/checking2/src/core/exhaustive/pretty.rs new file mode 100644 index 00000000..424c4300 --- /dev/null +++ b/compiler-core/checking2/src/core/exhaustive/pretty.rs @@ -0,0 +1,172 @@ +use std::sync::Arc; + +use files::FileId; +use indexing::TermItemId; +use smol_str::{SmolStr, SmolStrBuilder}; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::exhaustive::{PatternConstructor, PatternId, PatternKind, WitnessVector}; +use crate::state::CheckState; + +pub fn pretty_witness( + context: &CheckContext, + state: &CheckState, + witness: &WitnessVector, +) -> SmolStr +where + Q: ExternalQueries, +{ + join_smolstr(witness.iter().map(|&id| pretty_pattern(context, state, id)), ", ") +} + +fn pretty_pattern(context: &CheckContext, state: &CheckState, id: PatternId) -> SmolStr +where + Q: ExternalQueries, +{ + let pattern = &state.patterns[id]; + match &pattern.kind { + PatternKind::Wildcard => SmolStr::new_inline("_"), + PatternKind::Constructor { constructor } => pretty_constructor(context, state, constructor), + } +} + +fn join_smolstr(iterator: impl Iterator, separator: &str) -> SmolStr { + let mut builder = SmolStrBuilder::default(); + join_with_sep(&mut builder, iterator, separator, |builder, item| builder.push_str(&item)); + builder.finish() +} + +fn join_with_sep( + builder: &mut SmolStrBuilder, + iter: impl Iterator, + sep: &str, + mut render: impl FnMut(&mut SmolStrBuilder, T), +) { + let mut first = true; + for item in iter { + if !first { + builder.push_str(sep); + } + first = false; + render(builder, item); + } +} + +fn pretty_constructor( + context: &CheckContext, + state: &CheckState, + constructor: &PatternConstructor, +) -> SmolStr +where + Q: ExternalQueries, +{ + match constructor { + PatternConstructor::DataConstructor { file_id, item_id, fields } => { + let name = lookup_constructor_name(context, *file_id, *item_id) + .unwrap_or_else(|| SmolStr::new_inline("")); + + if fields.is_empty() { + return name; + } + + let mut builder = SmolStrBuilder::default(); + builder.push_str(&name); + + for &id in fields.iter() { + builder.push(' '); + let rendered = pretty_pattern(context, state, id); + let pattern = &state.patterns[id]; + if let PatternKind::Constructor { constructor } = &pattern.kind + && !constructor.fields().is_empty() + { + builder.push('('); + builder.push_str(&rendered); + builder.push(')'); + } else { + builder.push_str(&rendered); + } + } + + builder.finish() + } + PatternConstructor::Record { labels, fields } => { + if labels.len() != fields.len() { + return SmolStr::new_inline("{ }"); + } + + let mut builder = SmolStrBuilder::default(); + builder.push_str("{ "); + join_with_sep( + &mut builder, + labels.iter().zip(fields.iter()), + ", ", + |b: &mut SmolStrBuilder, (label, field_id)| { + let field = pretty_pattern(context, state, *field_id); + b.push_str(label); + b.push_str(": "); + b.push_str(&field); + }, + ); + builder.push_str(" }"); + builder.finish() + } + PatternConstructor::Array { fields } => { + let mut builder = SmolStrBuilder::default(); + builder.push('['); + join_with_sep( + &mut builder, + fields.iter().copied(), + ", ", + |b: &mut SmolStrBuilder, id| { + let rendered = pretty_pattern(context, state, id); + b.push_str(&rendered); + }, + ); + builder.push(']'); + builder.finish() + } + PatternConstructor::Boolean(b) => SmolStr::from(b.to_string()), + PatternConstructor::Char(c) => { + let mut builder = SmolStrBuilder::default(); + builder.push('\''); + builder.push(*c); + builder.push('\''); + builder.finish() + } + PatternConstructor::String(s) => { + let mut builder = SmolStrBuilder::default(); + builder.push('"'); + builder.push_str(s); + builder.push('"'); + builder.finish() + } + PatternConstructor::Integer(i) => SmolStr::from(i.to_string()), + PatternConstructor::Number(negative, n) => { + let mut builder = SmolStrBuilder::default(); + if *negative { + builder.push('-'); + } + builder.push_str(n.as_ref()); + builder.finish() + } + } +} + +fn lookup_constructor_name( + context: &CheckContext, + file_id: FileId, + term_id: TermItemId, +) -> Option +where + Q: ExternalQueries, +{ + let indexed = if file_id == context.id { + Arc::clone(&context.indexed) + } else { + context.queries.indexed(file_id).ok()? + }; + + let item = &indexed.items[term_id]; + item.name.clone() +} diff --git a/compiler-core/checking2/src/core/fold.rs b/compiler-core/checking2/src/core/fold.rs new file mode 100644 index 00000000..4d440993 --- /dev/null +++ b/compiler-core/checking2/src/core/fold.rs @@ -0,0 +1,131 @@ +//! Implements type folding for the core representation. + +use std::sync::Arc; + +use building_types::QueryResult; + +use crate::context::CheckContext; +use crate::core::{ForallBinder, KindOrType, Type, TypeId, normalise}; +use crate::state::CheckState; +use crate::{ExternalQueries, safe_loop}; + +pub enum FoldAction { + Replace(TypeId), + ReplaceThen(TypeId), + Continue, +} + +pub trait TypeFold { + fn transform( + &mut self, + state: &mut CheckState, + context: &CheckContext, + id: TypeId, + t: &Type, + ) -> QueryResult; + + fn transform_binder(&mut self, _binder: &mut ForallBinder) {} +} + +pub fn fold_type( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, + folder: &mut F, +) -> QueryResult +where + Q: ExternalQueries, + F: TypeFold, +{ + let mut id = normalise::normalise(state, context, id)?; + + safe_loop! { + let t = context.lookup_type(id); + match folder.transform(state, context, id, &t)? { + FoldAction::Replace(id) => return Ok(id), + FoldAction::ReplaceThen(then_id) => { + let then_id = normalise::normalise(state, context, then_id)?; + if then_id == id { + break; + } + id = then_id; + continue; + } + FoldAction::Continue => { + break; + }, + } + } + + let t = context.lookup_type(id); + let result = match t { + Type::Application(function, argument) => { + let function = fold_type(state, context, function, folder)?; + let argument = fold_type(state, context, argument, folder)?; + context.intern_application(function, argument) + } + Type::KindApplication(function, argument) => { + let function = fold_type(state, context, function, folder)?; + let argument = fold_type(state, context, argument, folder)?; + context.intern_kind_application(function, argument) + } + Type::SynonymApplication(synonym_id) => { + let mut synonym = context.lookup_synonym(synonym_id); + synonym.arguments = synonym + .arguments + .iter() + .map(|application| match application { + KindOrType::Kind(argument) => { + fold_type(state, context, *argument, folder).map(KindOrType::Kind) + } + KindOrType::Type(argument) => { + fold_type(state, context, *argument, folder).map(KindOrType::Type) + } + }) + .collect::>()?; + let synonym_id = context.intern_synonym(synonym); + context.intern_synonym_application(synonym_id) + } + Type::Forall(binder_id, inner) => { + let mut binder = context.lookup_forall_binder(binder_id); + folder.transform_binder(&mut binder); + binder.kind = fold_type(state, context, binder.kind, folder)?; + let inner = fold_type(state, context, inner, folder)?; + let binder_id = context.intern_forall_binder(binder); + context.intern_forall(binder_id, inner) + } + Type::Constrained(constraint, inner) => { + let constraint = fold_type(state, context, constraint, folder)?; + let inner = fold_type(state, context, inner, folder)?; + context.intern_constrained(constraint, inner) + } + Type::Function(argument, result) => { + let argument = fold_type(state, context, argument, folder)?; + let result = fold_type(state, context, result, folder)?; + context.intern_function(argument, result) + } + Type::Kinded(inner, kind) => { + let inner = fold_type(state, context, inner, folder)?; + let kind = fold_type(state, context, kind, folder)?; + context.intern_kinded(inner, kind) + } + Type::Constructor(_, _) => id, + Type::Integer(_) | Type::String(_, _) => id, + Type::Row(row_id) => { + let mut row = context.lookup_row_type(row_id); + for field in Arc::make_mut(&mut row.fields).iter_mut() { + field.id = fold_type(state, context, field.id, folder)?; + } + row.tail = row.tail.map(|tail| fold_type(state, context, tail, folder)).transpose()?; + let row_id = context.intern_row_type(row); + context.intern_row(row_id) + } + Type::Rigid(name, depth, kind) => { + let kind = fold_type(state, context, kind, folder)?; + context.intern_rigid(name, depth, kind) + } + Type::Unification(_) | Type::Free(_) | Type::Unknown(_) => id, + }; + + Ok(result) +} diff --git a/compiler-core/checking2/src/core/generalise.rs b/compiler-core/checking2/src/core/generalise.rs new file mode 100644 index 00000000..e2d47394 --- /dev/null +++ b/compiler-core/checking2/src/core/generalise.rs @@ -0,0 +1,481 @@ +//! Implements generalisation algorithms for the core representation. +//! +//! Simply put, generalisation is an operation that takes some inferred +//! type full of unsolved [unification variables] and replaces them with +//! [universally quantified] [rigid type variables]. For example: +//! +//! ```purescript +//! id :: ?0 -> ?0 +//! ``` +//! +//! this will generalise into the following: +//! +//! ```purescript +//! id :: forall (t0 :: Type). t0 -> t0 +//! ``` +//! +//! [unification variables]: crate::core::Type::Unification +//! [universally quantified]: crate::core::Type::Forall +//! [rigid type variables]: crate::core::Type::Rigid + +use building_types::QueryResult; +use itertools::Itertools; +use petgraph::algo; +use petgraph::prelude::DiGraphMap; +use rustc_hash::FxHashSet; + +use crate::context::CheckContext; +use crate::core::walk::{TypeWalker, WalkAction, walk_type}; +use crate::core::{ForallBinder, KindOrType, Name, Type, TypeId, constraint, normalise, zonk}; +use crate::state::{CheckState, UnificationEntry, UnificationState}; +use crate::{ExternalQueries, safe_loop}; + +type UniGraph = DiGraphMap; + +fn collect_unification_into( + graph: &mut UniGraph, + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + fn aux( + graph: &mut UniGraph, + state: &mut CheckState, + context: &CheckContext, + id: TypeId, + dependent: Option, + visited_kinds: &mut FxHashSet, + ) -> QueryResult<()> + where + Q: ExternalQueries, + { + let id = normalise::normalise(state, context, id)?; + let t = context.lookup_type(id); + + match t { + Type::Application(function, argument) | Type::KindApplication(function, argument) => { + aux(graph, state, context, function, dependent, visited_kinds)?; + aux(graph, state, context, argument, dependent, visited_kinds)?; + } + Type::SynonymApplication(synonym_id) => { + let synonym = context.lookup_synonym(synonym_id); + for application in synonym.arguments.iter() { + let argument = match application { + KindOrType::Kind(argument) | KindOrType::Type(argument) => *argument, + }; + aux(graph, state, context, argument, dependent, visited_kinds)?; + } + } + Type::Forall(binder_id, inner) => { + let binder = context.lookup_forall_binder(binder_id); + aux(graph, state, context, binder.kind, dependent, visited_kinds)?; + aux(graph, state, context, inner, dependent, visited_kinds)?; + } + Type::Constrained(constraint, inner) => { + aux(graph, state, context, constraint, dependent, visited_kinds)?; + aux(graph, state, context, inner, dependent, visited_kinds)?; + } + Type::Function(argument, result) => { + aux(graph, state, context, argument, dependent, visited_kinds)?; + aux(graph, state, context, result, dependent, visited_kinds)?; + } + Type::Kinded(inner, kind) => { + aux(graph, state, context, inner, dependent, visited_kinds)?; + aux(graph, state, context, kind, dependent, visited_kinds)?; + } + Type::Row(row_id) => { + let row = context.lookup_row_type(row_id); + for field in row.fields.iter() { + aux(graph, state, context, field.id, dependent, visited_kinds)?; + } + if let Some(tail) = row.tail { + aux(graph, state, context, tail, dependent, visited_kinds)?; + } + } + Type::Rigid(_, _, kind) => { + aux(graph, state, context, kind, dependent, visited_kinds)?; + } + Type::Unification(unification_id) => { + graph.add_node(unification_id); + + if let Some(dependent_id) = dependent { + graph.add_edge(dependent_id, unification_id, ()); + } + + if visited_kinds.insert(unification_id) { + let entry = state.unifications.get(unification_id); + aux(graph, state, context, entry.kind, Some(unification_id), visited_kinds)?; + } + } + Type::Constructor(_, _) + | Type::Integer(_) + | Type::String(_, _) + | Type::Free(_) + | Type::Unknown(_) => {} + } + + Ok(()) + } + + let mut visited_kinds = FxHashSet::default(); + aux(graph, state, context, id, None, &mut visited_kinds) +} + +/// Collect the unsolved unification variables in a type. +/// +/// This function returns the unification variables topologically sorted +/// based on their dependencies, such as when unification variables appear +/// in another unification variable's kind. +pub fn unsolved_unifications( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let mut graph = UniGraph::new(); + collect_unification_into(&mut graph, state, context, id)?; + + if graph.node_count() == 0 { + return Ok(vec![]); + } + + let Ok(unsolved) = algo::toposort(&graph, None) else { + return Ok(vec![]); + }; + + Ok(unsolved) +} + +fn collect_unification( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut graph = UniGraph::new(); + collect_unification_into(&mut graph, state, context, id)?; + Ok(graph) +} + +/// Generalise a type with the given unification variables. +/// +/// The `unsolved` parameter should be sourced from [`unsolved_unifications`]. +/// This split is necessary for generalisation on mutually-recursive bindings. +/// Note that while this function expects unsolved unification variables, it +/// also handles solved ones gracefully in the event that they become solved +/// before being generalised. +pub fn generalise_unsolved( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, + unsolved: &[u32], +) -> QueryResult +where + Q: ExternalQueries, +{ + if unsolved.is_empty() { + return Ok(id); + } + + let mut quantified = id; + + // All rigid type variables in a single generalisation share the same + // depth, one level deeper than the ambient scope. Note that the depth + // refers to the nesting level of forall scopes with respect to higher + // rank types, not the number of bindings introduced. For example, + // + // forall a b. a -> b -> a + // + // has `a` and `b` on the same depth, whereas, + // + // forall a. (forall r. ST r a) -> a + // forall a. a -> (forall b. b -> a) + // + // have `r` and `b` one level deeper. Note that the latter example is + // actually still a Rank-1 type; a forall can be floated trivially when + // it occurs to the right of the function arrow. + // + // forall a. a -> (forall b. b -> a) + // forall a b. a -> b -> a + // + // See also: https://wiki.haskell.org/Rank-N_types + let depth = state.depth.increment(); + + for &unification_id in unsolved.iter() { + let UnificationEntry { kind, state: unification_state, .. } = + *state.unifications.get(unification_id); + + let (name, kind) = match unification_state { + UnificationState::Unsolved => { + let name = state.names.fresh(); + let rigid = context.intern_rigid(name, depth, kind); + state.unifications.solve(unification_id, rigid); + (name, kind) + } + UnificationState::Solved(solution) => { + let solution = normalise::expand(state, context, solution)?; + let Type::Rigid(name, _, kind) = context.lookup_type(solution) else { + continue; + }; + (name, kind) + } + }; + + let binder = ForallBinder { visible: false, name, kind }; + let binder = context.intern_forall_binder(binder); + quantified = context.intern_forall(binder, quantified); + } + + zonk::zonk(state, context, quantified) +} + +/// Generalises a given type. See also module-level documentation. +pub fn generalise( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let unsolved = unsolved_unifications(state, context, id)?; + generalise_unsolved(state, context, id, &unsolved) +} + +#[derive(Default)] +pub struct ConstraintErrors { + pub ambiguous: Vec, + pub unsatisfied: Vec, +} + +pub fn insert_inferred_residuals( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, + residuals: Vec, + errors: &mut ConstraintErrors, +) -> QueryResult +where + Q: ExternalQueries, +{ + if residuals.is_empty() { + return Ok(type_id); + } + + let mut pending = vec![]; + let mut latent = vec![]; + + for residual in residuals { + let residual = zonk::zonk(state, context, residual)?; + + if residual == context.prim.partial { + latent.push(residual); + continue; + } + + let unification: FxHashSet = + collect_unification(state, context, residual)?.nodes().collect(); + + if unification.is_empty() { + errors.unsatisfied.push(residual); + } else { + pending.push((residual, unification)); + } + } + + let unifications = collect_unification(state, context, type_id)?.nodes().collect(); + let generalised = classify_constraints(pending, unifications, errors); + + let generalised = latent.into_iter().chain(generalised).sorted().collect_vec(); + let generalised = minimize_by_superclasses(state, context, generalised)?; + + let constrained = generalised + .into_iter() + .rfold(type_id, |inner, constraint| context.intern_constrained(constraint, inner)); + + Ok(constrained) +} + +fn minimize_by_superclasses( + state: &mut CheckState, + context: &CheckContext, + constraints: Vec, +) -> QueryResult> +where + Q: ExternalQueries, +{ + if constraints.len() <= 1 { + return Ok(constraints); + } + + let mut superclasses = FxHashSet::default(); + for &constraint in &constraints { + for application in superclass_applications(state, context, constraint)? { + superclasses.insert(application); + } + } + + Ok(constraints + .into_iter() + .filter(|&constraint| { + let application = + constraint::constraint_application(state, context, constraint).ok().flatten(); + application.is_none_or(|constraint| !superclasses.contains(&constraint)) + }) + .collect()) +} + +fn superclass_applications( + state: &mut CheckState, + context: &CheckContext, + constraint: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let mut superclasses = vec![]; + constraint::elaborate_superclasses(state, context, constraint, &mut superclasses)?; + superclasses + .into_iter() + .map(|constraint| constraint::constraint_application(state, context, constraint)) + .filter_map_ok(|constraint| constraint) + .collect() +} + +fn classify_constraints( + pending: Vec<(TypeId, FxHashSet)>, + unifications: FxHashSet, + errors: &mut ConstraintErrors, +) -> FxHashSet { + let mut reachable = unifications; + let mut valid = FxHashSet::default(); + let mut remaining = pending; + + safe_loop! { + let (connected, disconnected): (Vec<_>, Vec<_>) = + remaining.into_iter().partition(|(_, unification)| { + unification.iter().any(|variable| reachable.contains(variable)) + }); + + if connected.is_empty() { + break errors.ambiguous.extend(disconnected.into_iter().map(|(id, _)| id)); + } + + for (constraint, unification) in connected { + valid.insert(constraint); + reachable.extend(unification); + } + + remaining = disconnected; + } + + valid +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum ImplicitOrUnification { + Implicit(Name, TypeId), + Unification(u32, TypeId), +} + +#[derive(Default)] +struct GeneraliseImplicit { + owner: Option, + graph: DiGraphMap, + bound: FxHashSet, +} + +impl TypeWalker for GeneraliseImplicit { + fn visit( + &mut self, + state: &mut CheckState, + context: &CheckContext, + _id: TypeId, + t: &Type, + ) -> QueryResult + where + Q: ExternalQueries, + { + match t { + Type::Rigid(name, _, kind) => { + let next_owner = ImplicitOrUnification::Implicit(*name, *kind); + let prev_owner = self.owner.replace(next_owner); + + self.graph.add_node(next_owner); + if let Some(prev_owner) = prev_owner { + self.graph.add_edge(prev_owner, next_owner, ()); + } + + walk_type(state, context, *kind, self)?; + self.owner = prev_owner; + } + Type::Unification(id) => { + let UnificationEntry { kind, .. } = state.unifications.get(*id); + + let next_owner = ImplicitOrUnification::Unification(*id, *kind); + let prev_owner = self.owner.replace(next_owner); + + self.graph.add_node(next_owner); + if let Some(prev_owner) = prev_owner { + self.graph.add_edge(prev_owner, next_owner, ()); + } + + walk_type(state, context, *kind, self)?; + self.owner = prev_owner; + } + _ => {} + } + Ok(WalkAction::Continue) + } + + fn visit_binder(&mut self, binder: &ForallBinder) { + self.bound.insert(binder.name); + } +} + +pub fn generalise_implicit( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut walker = GeneraliseImplicit::default(); + walk_type(state, context, id, &mut walker)?; + + let Ok(implicits_unifications) = algo::toposort(&walker.graph, None) else { + return Ok(context.unknown("invalid recursive graph")); + }; + + let depth = state.depth.increment(); + + let mut binders = vec![]; + for implicit_unification in implicits_unifications { + match implicit_unification { + ImplicitOrUnification::Implicit(name, kind) => { + binders.push(ForallBinder { visible: false, name, kind }) + } + ImplicitOrUnification::Unification(id, kind) => { + let name = state.names.fresh(); + let rigid = context.intern_rigid(name, depth, kind); + state.unifications.solve(id, rigid); + binders.push(ForallBinder { visible: false, name, kind }) + } + } + } + + let id = binders.into_iter().fold(id, |inner, binder| { + let binder = context.intern_forall_binder(binder); + context.intern_forall(binder, inner) + }); + + zonk::zonk(state, context, id) +} diff --git a/compiler-core/checking2/src/core/normalise.rs b/compiler-core/checking2/src/core/normalise.rs new file mode 100644 index 00000000..7fa7e61a --- /dev/null +++ b/compiler-core/checking2/src/core/normalise.rs @@ -0,0 +1,299 @@ +//! Implements normalisation algorithms for the core representation. + +use std::iter; +use std::sync::Arc; + +use building_types::QueryResult; +use itertools::Itertools; + +use crate::context::CheckContext; +use crate::core::substitute::{NameToType, SubstituteName}; +use crate::core::{KindOrType, RowType, Saturation, Synonym, Type, TypeId, toolkit}; +use crate::state::{CheckState, UnificationState}; +use crate::{ExternalQueries, safe_loop}; + +struct ReductionContext<'a, 'q, Q> +where + Q: ExternalQueries, +{ + state: &'a mut CheckState, + context: &'a CheckContext<'q, Q>, + compression: Vec, +} + +impl<'a, 'q, Q> ReductionContext<'a, 'q, Q> +where + Q: ExternalQueries, +{ + fn new(state: &'a mut CheckState, context: &'a CheckContext<'q, Q>) -> Self { + ReductionContext { state, context, compression: vec![] } + } + + fn reduce_once(&mut self, id: TypeId) -> Option { + let t = self.context.lookup_type(id); + + if let Some(next) = self.rule_prune_unifications(&t) { + return Some(next); + } + if let Some(next) = self.rule_simplify_rows(&t) { + return Some(next); + } + + None + } + + fn rule_prune_unifications(&mut self, t: &Type) -> Option { + let Type::Unification(unification_id) = *t else { + return None; + }; + + let UnificationState::Solved(solution_id) = + self.state.unifications.get(unification_id).state + else { + return None; + }; + + self.compression.push(unification_id); + Some(solution_id) + } + + fn rule_simplify_rows(&self, t: &Type) -> Option { + let Type::Row(row_id) = *t else { + return None; + }; + + let row = self.context.lookup_row_type(row_id); + + if row.fields.is_empty() { + return row.tail; + } + + let tail_id = row.tail?; + let tail_t = self.context.lookup_type(tail_id); + + let Type::Row(inner_row_id) = tail_t else { + return None; + }; + + if inner_row_id == row_id { + return None; + } + + let inner = self.context.lookup_row_type(inner_row_id); + + let merged_fields = { + let left = row.fields.iter().cloned(); + let right = inner.fields.iter().cloned(); + left.merge_by(right, |left, right| left.label <= right.label) + }; + + let merged_row = RowType::new(merged_fields, inner.tail); + let merged_row = self.context.queries.intern_row_type(merged_row); + let merged_row = self.context.queries.intern_type(Type::Row(merged_row)); + + Some(merged_row) + } +} + +/// Normalises a [`Type`] head. +/// +/// Notably, this function applies the following rules: +/// 1. Replaces solved unfiication variables, compressing them +/// if they solve to other solved unification variables. +/// 2. Simplifies row types, such as merging concrete row tails, +/// or extracting an empty row's tail as a standalone type. +/// +/// This function should be used in checking rules where +/// synonyms must remain opaque such as in kind checking. +pub fn normalise( + state: &mut CheckState, + context: &CheckContext, + mut id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut reduction = ReductionContext::new(state, context); + + let id = safe_loop! { + if let Some(reduced_id) = reduction.reduce_once(id) { + id = reduced_id; + } else { + break id; + } + }; + + for unification_id in reduction.compression { + state.unifications.solve(unification_id, id); + } + + Ok(id) +} + +/// Expands [`Type::SynonymApplication`] heads. +/// +/// This function also applies normalisation using [`normalise`], +/// and should be used in checking rules where synonyms must be +/// transparent and inspected. +pub fn expand( + state: &mut CheckState, + context: &CheckContext, + mut id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + safe_loop! { + let expanded = expand_synonym(state, context, id)?; + let normalised = normalise(state, context, expanded)?; + if normalised == id { + return Ok(id); + } + id = normalised; + } +} + +/// Expands [`Type::SynonymApplication`] with respect to oversaturation. +/// +/// In certain cases, type synonyms can be oversaturated or applied with more +/// arguments than they're declared to accept. In the following example: +/// +/// ```purescript +/// type Identity :: forall k. k -> k +/// type Identity a = a +/// +/// data Tuple a b = Tuple a b +/// +/// test1 :: Identity Array Int +/// test1 = [42] +/// +/// test2 :: Identity Tuple Int String +/// test2 = Tuple 42 "hello" +/// +/// forceSolve = { test1, test2 } +/// ``` +/// +/// The `Identity Array` and `Identity Tuple` will be expanded to reveal +/// `Array` and `Tuple` which are applied to their respective arguments. +fn expand_synonym( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut current = id; + let mut applied_arguments = vec![]; + + // Collect oversaturated arguments, in our example above, this + // would collect `[Int]` and `[Int, String]` into `arguments`. + // Additionally, these are used for Constructor-based synonyms, + // which occur since we don't normalise applications back into + // the SynonymApplication form. This will be rendered obsolete + // once these variants are unified. + safe_loop! { + current = normalise(state, context, current)?; + match context.lookup_type(current) { + Type::Application(function, argument) => { + applied_arguments.push(KindOrType::Type(argument)); + current = function; + } + Type::KindApplication(function, argument) => { + applied_arguments.push(KindOrType::Kind(argument)); + current = function; + } + _ => break, + } + } + + current = normalise(state, context, current)?; + let ((file_id, type_id), synonym_arguments) = match context.lookup_type(current) { + Type::Constructor(file_id, type_id) => { + let reference = (file_id, type_id); + let arguments = [].into(); + (reference, arguments) + } + Type::SynonymApplication(synonym_id) => { + let Synonym { saturation, reference, arguments } = context.lookup_synonym(synonym_id); + + if saturation != Saturation::Full { + return Ok(id); + } + + (reference, Arc::clone(&arguments)) + } + _ => return Ok(id), + }; + + let checked_synonym = toolkit::lookup_file_synonym(state, context, file_id, type_id)?; + let Some(checked_synonym) = checked_synonym else { + return Ok(id); + }; + + let mut bindings = NameToType::default(); + let mut kind = checked_synonym.kind; + let mut arguments = { + let synonym_arguments = synonym_arguments.iter().copied(); + let applied_arguments = applied_arguments.iter().copied().rev(); + iter::chain(synonym_arguments, applied_arguments) + }; + + // Create substitutions for kind arguments. For example, + // + // type T :: forall k. k -> Type + // type T (a :: k) = Proxy (a :: k) + // + // given an application such as, + // + // T @Type Int + // + // this loop produces the replacement, + // + // k := Type + // + // which is later substituted into the synonym body. Without this step, + // expansion would leave `k` rigid inside the synonym body causing + // unification errors downstream. + safe_loop! { + kind = normalise(state, context, kind)?; + + let Type::Forall(binder_id, inner) = context.lookup_type(kind) else { + break; + }; + + let Some(KindOrType::Kind(argument)) = arguments.next() else { + return Ok(id); + }; + + let binder = context.lookup_forall_binder(binder_id); + bindings.insert(binder.name, argument); + + kind = inner; + } + + // Create substitutions for type arguments. + for parameter in &checked_synonym.parameters { + let Some(KindOrType::Type(argument)) = arguments.next() else { + return Ok(id); + }; + bindings.insert(parameter.name, argument); + } + + // Apply the substitutions if there are any. + let mut substituted = if bindings.is_empty() { + checked_synonym.synonym + } else { + SubstituteName::many(state, context, &bindings, checked_synonym.synonym)? + }; + + // Reconstruct applications from remaining oversaturated arguments. + for argument in arguments { + substituted = match argument { + KindOrType::Type(argument) => context.intern_application(substituted, argument), + KindOrType::Kind(argument) => context.intern_kind_application(substituted, argument), + }; + } + + Ok(substituted) +} diff --git a/compiler-core/checking2/src/core/pretty.rs b/compiler-core/checking2/src/core/pretty.rs new file mode 100644 index 00000000..751dc11a --- /dev/null +++ b/compiler-core/checking2/src/core/pretty.rs @@ -0,0 +1,511 @@ +//! Implements the pretty printer for core types. + +use itertools::Itertools; +use lowering::StringKind; +use pretty::{Arena, DocAllocator, DocBuilder}; +use rustc_hash::FxHashMap; +use smol_str::{SmolStr, SmolStrBuilder}; + +use crate::core::{ + ForallBinder, ForallBinderId, KindOrType, Name, RowField, RowType, RowTypeId, SmolStrId, + Synonym, SynonymId, Type, TypeId, +}; +use crate::{CheckedModule, ExternalQueries}; + +type Doc<'a> = DocBuilder<'a, Arena<'a>, ()>; + +pub struct Pretty<'a, Q> { + queries: &'a Q, + width: usize, + signature: Option<&'a str>, + checked: &'a CheckedModule, +} + +impl<'a, Q: ExternalQueries> Pretty<'a, Q> { + pub fn new(queries: &'a Q, checked: &'a CheckedModule) -> Self { + Pretty { queries, width: 100, signature: None, checked } + } + + pub fn width(mut self, width: usize) -> Self { + self.width = width; + self + } + + pub fn signature(mut self, name: &'a str) -> Self { + self.signature = Some(name); + self + } + + pub fn render(self, id: TypeId) -> SmolStr { + let arena = Arena::new(); + let mut printer = Printer::new(&arena, self.queries, &self.checked.names); + + let document = if let Some(name) = self.signature { + printer.signature(name, id) + } else { + printer.traverse(Precedence::Top, id) + }; + + let mut output = SmolStrBuilder::new(); + document + .render_fmt(self.width, &mut output) + .expect("critical failure: failed to render type"); + output.finish() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum Precedence { + Top, + Constraint, + Function, + Application, + Atom, +} + +struct Printer<'a, Q> +where + Q: ExternalQueries, +{ + arena: &'a Arena<'a>, + queries: &'a Q, + names: &'a FxHashMap, +} + +impl<'a, Q> Printer<'a, Q> +where + Q: ExternalQueries, +{ + fn new( + arena: &'a Arena<'a>, + queries: &'a Q, + names: &'a FxHashMap, + ) -> Printer<'a, Q> { + Printer { arena, queries, names } + } + + fn lookup_type(&self, id: TypeId) -> Type { + self.queries.lookup_type(id) + } + + fn lookup_forall_binder(&self, id: ForallBinderId) -> ForallBinder { + self.queries.lookup_forall_binder(id) + } + + fn lookup_row_type(&self, id: RowTypeId) -> RowType { + self.queries.lookup_row_type(id) + } + + fn lookup_synonym(&self, id: SynonymId) -> Synonym { + self.queries.lookup_synonym(id) + } + + fn lookup_smol_str(&self, id: SmolStrId) -> smol_str::SmolStr { + self.queries.lookup_smol_str(id) + } + + fn lookup_type_name( + &self, + file_id: files::FileId, + type_id: indexing::TypeItemId, + ) -> Option { + let indexed = self.queries.indexed(file_id).ok()?; + indexed.items[type_id].name.as_ref().map(|name| name.to_string()) + } + + fn is_record_constructor(&self, id: TypeId) -> bool { + if let Type::Constructor(file_id, type_id) = self.lookup_type(id) + && file_id == self.queries.prim_id() + && let Some(name) = self.lookup_type_name(file_id, type_id) + { + return name == "Record"; + } + false + } + + fn parens_if(&self, condition: bool, doc: Doc<'a>) -> Doc<'a> { + if condition { self.arena.text("(").append(doc).append(self.arena.text(")")) } else { doc } + } + + fn signature(&mut self, name: &str, id: TypeId) -> Doc<'a> { + let signature = self.traverse(Precedence::Top, id); + let signature = self.arena.line().append(signature).nest(2); + self.arena.text(format!("{name} ::")).append(signature).group() + } + + fn traverse(&mut self, precedence: Precedence, id: TypeId) -> Doc<'a> { + match self.lookup_type(id) { + Type::Application(function, argument) => { + self.traverse_application(precedence, function, argument) + } + + Type::KindApplication(function, argument) => { + self.traverse_kind_application(precedence, function, argument) + } + + Type::SynonymApplication(synonym_id) => { + let synonym = self.lookup_synonym(synonym_id); + let (file_id, type_id) = synonym.reference; + let function = self + .lookup_type_name(file_id, type_id) + .unwrap_or_else(|| "".to_string()); + + if synonym.arguments.is_empty() { + return self.arena.text(function); + } + + let function = self.arena.text(function); + + let arguments = synonym + .arguments + .iter() + .map(|application| match application { + KindOrType::Kind(argument) => { + self.arena.text("@").append(self.traverse(Precedence::Atom, *argument)) + } + KindOrType::Type(argument) => self.traverse(Precedence::Atom, *argument), + }) + .collect_vec(); + + let arguments = + arguments.into_iter().fold(self.arena.nil(), |builder, argument| { + builder.append(self.arena.line()).append(argument) + }); + + let synonym = function.append(arguments.nest(2)).group(); + self.parens_if(precedence > Precedence::Application, synonym) + } + + Type::Forall(binder_id, inner) => self.traverse_forall(precedence, binder_id, inner), + + Type::Constrained(constraint, inner) => { + self.traverse_constrained(precedence, constraint, inner) + } + + Type::Function(argument, result) => { + self.traverse_function(precedence, argument, result) + } + + Type::Kinded(inner, kind) => { + let inner = self.traverse(Precedence::Application, inner); + let kind = self.traverse(Precedence::Top, kind); + let kinded = inner.append(self.arena.text(" :: ")).append(kind); + self.parens_if(precedence > Precedence::Atom, kinded) + } + + Type::Constructor(file_id, type_id) => { + let name = self + .lookup_type_name(file_id, type_id) + .unwrap_or_else(|| "".to_string()); + self.arena.text(name) + } + + Type::Integer(integer) => { + let negative = integer.is_negative(); + let integer = self.arena.text(format!("{integer}")); + self.parens_if(negative, integer) + } + + Type::String(kind, string_id) => { + let string = self.lookup_smol_str(string_id); + match kind { + StringKind::String => self.arena.text(format!("\"{string}\"")), + StringKind::RawString => self.arena.text(format!("\"\"\"{string}\"\"\"")), + } + } + + Type::Row(row_id) => { + let row = self.lookup_row_type(row_id); + if row.fields.is_empty() && row.tail.is_none() { + return self.arena.text("()"); + } + self.format_row(&row.fields, row.tail) + } + + Type::Rigid(name, _, kind) => { + let text = match self.names.get(&name) { + Some(&id) => self.lookup_smol_str(id), + None => name.as_text(), + }; + let kind = self.traverse(Precedence::Top, kind); + self.arena.text(format!("({text} :: ")).append(kind).append(self.arena.text(")")) + } + + Type::Unification(unification_id) => self.arena.text(format!("?{unification_id}")), + + Type::Free(name_id) => { + let name = self.lookup_smol_str(name_id); + self.arena.text(format!("{name}")) + } + + Type::Unknown(name_id) => { + let name = self.lookup_smol_str(name_id); + self.arena.text(format!("?[{name}]")) + } + } + } +} + +impl<'a, Q> Printer<'a, Q> +where + Q: ExternalQueries, +{ + fn traverse_application( + &mut self, + precedence: Precedence, + mut function: TypeId, + argument: TypeId, + ) -> Doc<'a> { + if self.is_record_constructor(function) { + return self.format_record_application(argument); + } + + let mut arguments = vec![argument]; + + while let Type::Application(inner_function, argument) = self.lookup_type(function) { + function = inner_function; + arguments.push(argument); + } + + let function = self.traverse(Precedence::Application, function); + + let arguments = arguments + .iter() + .rev() + .map(|&argument| self.traverse(Precedence::Atom, argument)) + .collect_vec(); + + let arguments = arguments.into_iter().fold(self.arena.nil(), |builder, argument| { + builder.append(self.arena.line()).append(argument) + }); + + let application = function.append(arguments.nest(2)).group(); + self.parens_if(precedence > Precedence::Application, application) + } + + fn format_record_application(&mut self, argument: TypeId) -> Doc<'a> { + match self.lookup_type(argument) { + Type::Row(row_id) => { + let row = self.lookup_row_type(row_id); + self.format_record(&row.fields, row.tail) + } + _ => { + let inner = self.traverse(Precedence::Top, argument); + self.arena.text("{| ").append(inner).append(self.arena.text(" }")) + } + } + } + + fn traverse_kind_application( + &mut self, + precedence: Precedence, + mut function: TypeId, + argument: TypeId, + ) -> Doc<'a> { + let mut arguments = vec![argument]; + + while let Type::KindApplication(inner_function, argument) = self.lookup_type(function) { + function = inner_function; + arguments.push(argument); + } + + let function = self.traverse(Precedence::Application, function); + + let arguments = arguments + .iter() + .rev() + .map(|&argument| self.traverse(Precedence::Atom, argument)) + .collect_vec(); + + let arguments = arguments.into_iter().fold(self.arena.nil(), |builder, argument| { + builder.append(self.arena.line()).append(self.arena.text("@")).append(argument) + }); + + let application = function.append(arguments.nest(2)).group(); + self.parens_if(precedence > Precedence::Application, application) + } +} + +impl<'a, Q> Printer<'a, Q> +where + Q: ExternalQueries, +{ + fn traverse_forall( + &mut self, + precedence: Precedence, + binder_id: ForallBinderId, + mut inner: TypeId, + ) -> Doc<'a> { + let binder = self.lookup_forall_binder(binder_id); + let mut binders = vec![binder]; + + while let Type::Forall(next_binder_id, next_inner) = self.lookup_type(inner) { + binders.push(self.lookup_forall_binder(next_binder_id)); + inner = next_inner; + } + + let binders = binders + .iter() + .map(|binder| { + let text = match self.names.get(&binder.name) { + Some(&id) => self.lookup_smol_str(id), + None => binder.name.as_text(), + }; + let kind = self.traverse(Precedence::Top, binder.kind); + self.arena + .text(format!("({} :: ", text)) + .append(kind) + .append(self.arena.text(")")) + .group() + }) + .collect_vec(); + + let mut binders = binders.into_iter(); + let binders = if let Some(first) = binders.next() { + binders.fold(first, |builder, binder| { + builder.append(self.arena.line().append(binder).nest(2).group()) + }) + } else { + self.arena.nil() + }; + + let header = self.arena.text("forall ").append(binders).append(self.arena.text(".")); + let inner = self.traverse(Precedence::Top, inner); + let inner = self.arena.line().append(inner).nest(2); + let forall = header.append(inner).group(); + + self.parens_if(precedence > Precedence::Top, forall) + } + + fn traverse_constrained( + &mut self, + precedence: Precedence, + constraint: TypeId, + mut inner: TypeId, + ) -> Doc<'a> { + let mut constraints = vec![constraint]; + + while let Type::Constrained(constraint, next_inner) = self.lookup_type(inner) { + constraints.push(constraint); + inner = next_inner; + } + + let constraints = constraints + .iter() + .map(|&constraint| self.traverse(Precedence::Application, constraint)) + .collect_vec(); + + let inner = self.traverse(Precedence::Constraint, inner); + + let arrow = self.arena.text(" =>").append(self.arena.line()); + let constraints = constraints.into_iter().fold(self.arena.nil(), |builder, constraint| { + builder.append(constraint).append(arrow.clone()) + }); + + let constraints = constraints.append(inner).group(); + self.parens_if(precedence > Precedence::Constraint, constraints) + } + + fn traverse_function( + &mut self, + precedence: Precedence, + argument: TypeId, + mut result: TypeId, + ) -> Doc<'a> { + let mut arguments = vec![argument]; + + while let Type::Function(argument, next_result) = self.lookup_type(result) { + result = next_result; + arguments.push(argument); + } + + let arguments = arguments + .iter() + .map(|&argument| self.traverse(Precedence::Application, argument)) + .collect_vec(); + + let result = self.traverse(Precedence::Function, result); + + let arrow = self.arena.text(" ->").append(self.arena.line()); + let arguments = arguments.into_iter().fold(self.arena.nil(), |builder, argument| { + builder.append(argument).append(arrow.clone()) + }); + + let function = arguments.append(result).group(); + self.parens_if(precedence > Precedence::Function, function) + } +} + +impl<'a, Q> Printer<'a, Q> +where + Q: ExternalQueries, +{ + fn format_record(&mut self, fields: &[RowField], tail: Option) -> Doc<'a> { + if fields.is_empty() && tail.is_none() { + return self.arena.text("{}"); + } + let body = self.format_row_body(fields, tail); + self.arena + .text("{ ") + .append(body) + .append(self.arena.line()) + .append(self.arena.text("}")) + .group() + } + + fn format_row(&mut self, fields: &[RowField], tail: Option) -> Doc<'a> { + let body = self.format_row_body(fields, tail); + self.arena + .text("( ") + .append(body) + .append(self.arena.line()) + .append(self.arena.text(")")) + .group() + } + + fn format_row_body(&mut self, fields: &[RowField], tail: Option) -> Doc<'a> { + if fields.is_empty() { + return if let Some(tail) = tail { + let tail = self.traverse(Precedence::Top, tail); + self.arena.text("| ").append(tail) + } else { + self.arena.nil() + }; + } + + let fields = fields + .iter() + .map(|field| { + let field_type = self.traverse(Precedence::Top, field.id); + (field.label.to_string(), field_type) + }) + .collect_vec(); + + let format_field = |arena: &'a Arena<'a>, label: String, field_type: Doc<'a>| { + let field_type = arena.line().append(field_type).nest(2).group(); + arena.text(format!("{label} ::")).append(field_type).align() + }; + + let mut fields = fields.into_iter(); + let (first_label, first_type) = fields.next().unwrap(); + let first = format_field(self.arena, first_label, first_type); + + let leading_comma = self.arena.hardline().append(self.arena.text(", ")); + let leading_comma = leading_comma.flat_alt(self.arena.text(", ")); + + let fields = fields.fold(first, |builder, (label, field_type)| { + builder + .append(leading_comma.clone()) + .append(format_field(self.arena, label, field_type)) + }); + + if let Some(tail) = tail { + let tail = self.traverse(Precedence::Top, tail); + let leading_pipe = self.arena.hardline().append(self.arena.text("| ")); + let leading_pipe = leading_pipe.flat_alt(self.arena.text(" | ")); + fields.append(leading_pipe).append(tail) + } else { + fields + } + } +} diff --git a/compiler-core/checking2/src/core/signature.rs b/compiler-core/checking2/src/core/signature.rs new file mode 100644 index 00000000..b42c9493 --- /dev/null +++ b/compiler-core/checking2/src/core/signature.rs @@ -0,0 +1,146 @@ +use building_types::QueryResult; +use lowering::TypeVariableBinding; + +use crate::context::CheckContext; +use crate::core::{ForallBinder, Type, TypeId, normalise}; +use crate::error::ErrorKind; +use crate::state::CheckState; +use crate::{ExternalQueries, safe_loop}; + +pub struct DecomposedSignature { + pub binders: Vec, + pub constraints: Vec, + pub arguments: Vec, + pub result: TypeId, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DecomposeSignatureMode { + Full, + Patterns { required: usize }, +} + +pub fn decompose_signature( + state: &mut CheckState, + context: &CheckContext, + mut current: TypeId, + mode: DecomposeSignatureMode, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut binders = vec![]; + let mut constraints = vec![]; + let mut arguments = vec![]; + + safe_loop! { + current = normalise::expand(state, context, current)?; + + match context.lookup_type(current) { + Type::Forall(binder_id, inner) => { + binders.push(context.lookup_forall_binder(binder_id)); + current = inner; + } + + Type::Constrained(constraint, constrained) => { + constraints.push(constraint); + current = constrained; + } + + Type::Function(argument, result) => { + if let DecomposeSignatureMode::Patterns { required } = mode + && arguments.len() >= required + { + return Ok(DecomposedSignature { binders, constraints, arguments, result: current }); + } + + arguments.push(argument); + current = result; + } + + Type::Application(function_argument, result) => { + if let DecomposeSignatureMode::Patterns { required } = mode + && arguments.len() >= required + { + return Ok(DecomposedSignature { binders, constraints, arguments, result: current }); + } + + let function_argument = + normalise::expand(state, context, function_argument)?; + + let Type::Application(function, argument) = context.lookup_type(function_argument) + else { + return Ok(DecomposedSignature { binders, constraints, arguments, result: current }); + }; + + let function = normalise::expand(state, context, function)?; + if function == context.prim.function { + arguments.push(argument); + current = result; + } else { + return Ok(DecomposedSignature { binders, constraints, arguments, result: current }); + } + } + + _ => return Ok(DecomposedSignature { binders, constraints, arguments, result: current }), + } + } +} + +pub fn expect_signature_bindings( + state: &mut CheckState, + context: &CheckContext, + (signature_id, signature_type): (lowering::TypeId, TypeId), + bindings: &[TypeVariableBinding], +) -> QueryResult +where + Q: ExternalQueries, +{ + let signature = + decompose_signature(state, context, signature_type, DecomposeSignatureMode::Full)?; + + let actual = bindings.len() as u32; + let expected = signature.arguments.len() as u32; + + if actual > expected { + state.insert_error(ErrorKind::TypeSignatureVariableMismatch { + id: signature_id, + expected, + actual, + }); + } + + let mut remaining = signature.arguments.into_iter(); + let arguments = remaining.by_ref().take(actual as usize).collect(); + let result = context.intern_function_chain_iter(remaining, signature.result); + + Ok(DecomposedSignature { + binders: signature.binders, + constraints: signature.constraints, + arguments, + result, + }) +} + +pub fn expect_signature_patterns( + state: &mut CheckState, + context: &CheckContext, + signature_type: TypeId, + required: usize, +) -> QueryResult +where + Q: ExternalQueries, +{ + let signature = decompose_signature( + state, + context, + signature_type, + DecomposeSignatureMode::Patterns { required }, + )?; + + for &constraint in &signature.constraints { + state.push_given(constraint); + } + + Ok(signature) +} diff --git a/compiler-core/checking2/src/core/substitute.rs b/compiler-core/checking2/src/core/substitute.rs new file mode 100644 index 00000000..ce3ccc05 --- /dev/null +++ b/compiler-core/checking2/src/core/substitute.rs @@ -0,0 +1,71 @@ +//! Implements name-based type substitution for the core representation. + +use rustc_hash::FxHashMap; + +use building_types::QueryResult; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::fold::{FoldAction, TypeFold, fold_type}; +use crate::core::{Name, Type, TypeId}; +use crate::state::CheckState; + +pub type NameToType = FxHashMap; + +/// Implements [`Name`]-based substitution for [`Type::Rigid`] variables. +/// +/// Names are globally unique, removing the need for scope tracking and +/// removing the need for capture-avoiding substitutions. This property +/// is extremely useful for for instantiation. +pub struct SubstituteName<'a> { + bindings: &'a NameToType, +} + +impl SubstituteName<'_> { + pub fn one( + state: &mut CheckState, + context: &CheckContext, + name: Name, + replacement: TypeId, + in_type: TypeId, + ) -> QueryResult + where + Q: ExternalQueries, + { + let bindings = NameToType::from_iter([(name, replacement)]); + fold_type(state, context, in_type, &mut SubstituteName { bindings: &bindings }) + } + + pub fn many( + state: &mut CheckState, + context: &CheckContext, + bindings: &NameToType, + in_type: TypeId, + ) -> QueryResult + where + Q: ExternalQueries, + { + fold_type(state, context, in_type, &mut SubstituteName { bindings }) + } +} + +impl TypeFold for SubstituteName<'_> { + fn transform( + &mut self, + _state: &mut CheckState, + _context: &CheckContext, + _id: TypeId, + t: &Type, + ) -> QueryResult + where + Q: ExternalQueries, + { + if let Type::Rigid(name, _, _) = t + && let Some(id) = self.bindings.get(name) + { + Ok(FoldAction::Replace(*id)) + } else { + Ok(FoldAction::Continue) + } + } +} diff --git a/compiler-core/checking2/src/core/toolkit.rs b/compiler-core/checking2/src/core/toolkit.rs new file mode 100644 index 00000000..497d867e --- /dev/null +++ b/compiler-core/checking2/src/core/toolkit.rs @@ -0,0 +1,754 @@ +//! Implements shared utilities for core type operations. + +use std::sync::Arc; + +use building_types::QueryResult; +use files::FileId; +use indexing::{TermItemId, TypeItemId}; +use lowering::TypeItemIr; + +use crate::context::CheckContext; +use crate::core::substitute::SubstituteName; +use crate::core::{ + CheckedClass, CheckedInstance, CheckedSynonym, ForallBinder, KindOrType, Role, Type, TypeId, + constraint, normalise, unification, +}; +use crate::error::ErrorKind; +use crate::state::CheckState; +use crate::{ExternalQueries, safe_loop}; + +pub struct InspectQuantified { + pub binders: Vec, + pub quantified: TypeId, +} + +pub struct InspectFunction { + pub arguments: Vec, + pub result: TypeId, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InspectMode { + Some(usize), + Full, +} + +pub struct DecomposedInstance { + pub binders: Vec, + pub constraints: Vec, + pub arguments: Vec, +} + +pub struct NewtypeInner { + pub inner: TypeId, + pub rigids: Vec, +} + +pub fn report_invalid_type_application( + state: &mut CheckState, + context: &CheckContext, + function_type: TypeId, + function_kind: TypeId, + argument_type: TypeId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let function_type = state.pretty_id(context, function_type)?; + let function_kind = state.pretty_id(context, function_kind)?; + let argument_type = state.pretty_id(context, argument_type)?; + + state.insert_error(ErrorKind::InvalidTypeApplication { + function_type, + function_kind, + argument_type, + }); + + Ok(()) +} + +pub fn extract_type_application( + state: &mut CheckState, + context: &CheckContext, + mut id: TypeId, +) -> QueryResult<(TypeId, Vec)> +where + Q: ExternalQueries, +{ + let mut arguments = vec![]; + + safe_loop! { + id = normalise::expand(state, context, id)?; + match context.lookup_type(id) { + Type::Application(function, argument) => { + arguments.push(argument); + id = function; + } + Type::KindApplication(function, _) => { + id = function; + } + _ => break, + } + } + + arguments.reverse(); + Ok((id, arguments)) +} + +pub fn extract_all_applications( + state: &mut CheckState, + context: &CheckContext, + mut id: TypeId, +) -> QueryResult<(TypeId, Vec)> +where + Q: ExternalQueries, +{ + let mut arguments = vec![]; + + safe_loop! { + id = normalise::expand(state, context, id)?; + match context.lookup_type(id) { + Type::Application(function, argument) => { + arguments.push(crate::core::KindOrType::Type(argument)); + id = function; + } + Type::KindApplication(function, argument) => { + arguments.push(crate::core::KindOrType::Kind(argument)); + id = function; + } + _ => break, + } + } + + arguments.reverse(); + Ok((id, arguments)) +} + +pub fn lookup_file_type( + state: &CheckState, + context: &CheckContext, + file_id: FileId, + type_id: TypeItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let kind = if file_id == context.id { + state.checked.lookup_type(type_id) + } else { + let checked = context.queries.checked2(file_id)?; + checked.lookup_type(type_id) + }; + + if let Some(kind) = kind { Ok(kind) } else { Ok(context.unknown("invalid type item")) } +} + +pub fn lookup_file_term( + state: &CheckState, + context: &CheckContext, + file_id: FileId, + term_id: TermItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let term = if file_id == context.id { + state.checked.lookup_term(term_id) + } else { + let checked = context.queries.checked2(file_id)?; + checked.lookup_term(term_id) + }; + + if let Some(term) = term { Ok(term) } else { Ok(context.unknown("invalid term item")) } +} + +pub fn lookup_term_variable( + state: &mut CheckState, + context: &CheckContext, + resolution: lowering::TermVariableResolution, +) -> QueryResult +where + Q: ExternalQueries, +{ + match resolution { + lowering::TermVariableResolution::Binder(binder_id) => Ok(state + .checked + .nodes + .lookup_binder(binder_id) + .unwrap_or_else(|| context.unknown("unresolved binder"))), + lowering::TermVariableResolution::Let(let_binding_id) => Ok(state + .checked + .nodes + .lookup_let(let_binding_id) + .unwrap_or_else(|| context.unknown("unresolved let"))), + lowering::TermVariableResolution::RecordPun(pun_id) => Ok(state + .checked + .nodes + .lookup_pun(pun_id) + .unwrap_or_else(|| context.unknown("unresolved pun"))), + lowering::TermVariableResolution::Reference(file_id, term_id) => { + lookup_file_term(state, context, file_id, term_id) + } + } +} + +pub fn lookup_file_class( + state: &mut CheckState, + context: &CheckContext, + file_id: FileId, + item_id: TypeItemId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + if file_id == context.id { + Ok(state.checked.lookup_class(item_id)) + } else { + let checked = context.queries.checked2(file_id)?; + Ok(checked.lookup_class(item_id)) + } +} + +pub fn lookup_file_synonym( + state: &CheckState, + context: &CheckContext, + file_id: FileId, + type_id: TypeItemId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + if file_id == context.id { + Ok(state.checked.lookup_synonym(type_id)) + } else { + let checked = context.queries.checked2(file_id)?; + Ok(checked.lookup_synonym(type_id)) + } +} + +pub fn lookup_file_type_operator( + state: &CheckState, + context: &CheckContext, + file_id: FileId, + type_id: TypeItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let operator_kind = if file_id == context.id { + state.checked.lookup_type(type_id) + } else { + let checked = context.queries.checked2(file_id)?; + checked.lookup_type(type_id) + }; + + if let Some(operator_kind) = operator_kind { + Ok(operator_kind) + } else { + Ok(context.unknown("invalid operator item")) + } +} + +pub fn resolve_type_operator_target( + context: &CheckContext, + file_id: FileId, + type_id: TypeItemId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let resolve = |lowered: &lowering::LoweredModule| { + lowered.info.get_type_item(type_id).and_then(|item| match item { + TypeItemIr::Operator { resolution, .. } => *resolution, + _ => None, + }) + }; + + if file_id == context.id { + Ok(resolve(&context.lowered)) + } else { + let lowered = context.queries.lowered(file_id)?; + Ok(resolve(&lowered)) + } +} + +pub fn lookup_file_term_operator( + state: &CheckState, + context: &CheckContext, + file_id: FileId, + term_id: TermItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let operator_type = if file_id == context.id { + state.checked.lookup_term(term_id) + } else { + let checked = context.queries.checked2(file_id)?; + checked.lookup_term(term_id) + }; + + if let Some(operator_type) = operator_type { + Ok(operator_type) + } else { + Ok(context.unknown("invalid operator item")) + } +} + +pub fn inspect_quantified( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut binders = vec![]; + let mut current = id; + + safe_loop! { + current = normalise::expand(state, context, current)?; + + let Type::Forall(binder_id, inner) = context.lookup_type(current) else { + break; + }; + + binders.push(context.lookup_forall_binder(binder_id)); + current = inner; + } + + Ok(InspectQuantified { binders, quantified: current }) +} + +pub fn inspect_function( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + inspect_function_with(state, context, id, InspectMode::Full) +} + +pub fn inspect_function_with( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, + mode: InspectMode, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut arguments = vec![]; + let mut current = id; + + safe_loop! { + current = normalise::expand(state, context, current)?; + + if let InspectMode::Some(required) = mode + && arguments.len() >= required + { + return Ok(InspectFunction { arguments, result: current }); + } + + match context.lookup_type(current) { + Type::Function(argument, result) => { + arguments.push(argument); + current = result; + } + Type::Application(function_argument, result) => { + let function_argument = normalise::expand(state, context, function_argument)?; + + let Type::Application(function, argument) = context.lookup_type(function_argument) + else { + return Ok(InspectFunction { arguments, result: current }); + }; + + let function = normalise::expand(state, context, function)?; + if function == context.prim.function { + arguments.push(argument); + current = result; + } else { + return Ok(InspectFunction { arguments, result: current }); + } + } + _ => return Ok(InspectFunction { arguments, result: current }), + } + } +} + +pub fn is_binary_operator_type( + state: &mut CheckState, + context: &CheckContext, + mut id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + safe_loop! { + id = normalise::expand(state, context, id)?; + match context.lookup_type(id) { + Type::Forall(_, inner) | Type::Constrained(_, inner) => { + id = inner; + } + _ => break, + } + } + + let inspected = inspect_function_with(state, context, id, InspectMode::Some(2))?; + Ok(inspected.arguments.len() >= 2) +} + +pub fn decompose_instance( + state: &mut CheckState, + context: &CheckContext, + instance: &CheckedInstance, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let InspectQuantified { binders, quantified } = + inspect_quantified(state, context, instance.canonical)?; + + let mut current = quantified; + let mut constraints = vec![]; + + safe_loop! { + current = normalise::expand(state, context, current)?; + match context.lookup_type(current) { + Type::Constrained(constraint, constrained) => { + constraints.push(constraint); + current = constrained; + } + _ => break, + } + } + + let Some(constraint::ConstraintApplication { file_id, item_id, arguments }) = + constraint::constraint_application(state, context, current)? + else { + return Ok(None); + }; + + if (file_id, item_id) != instance.resolution { + return Ok(None); + } + + Ok(Some(DecomposedInstance { binders, constraints, arguments })) +} + +pub fn instantiate_unifications( + state: &mut CheckState, + context: &CheckContext, + mut id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + safe_loop! { + id = normalise::expand(state, context, id)?; + + let Type::Forall(binder_id, inner) = context.lookup_type(id) else { + break; + }; + + let binder = context.lookup_forall_binder(binder_id); + let binder_kind = normalise::normalise(state, context, binder.kind)?; + + let replacement = state.fresh_unification(context.queries, binder_kind); + id = SubstituteName::one(state, context, binder.name, replacement, inner)?; + } + + Ok(id) +} + +/// Replaces forall binders with rigid (skolem) variables. +pub fn skolemise_forall( + state: &mut CheckState, + context: &CheckContext, + mut id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + safe_loop! { + id = normalise::expand(state, context, id)?; + + let Type::Forall(binder_id, inner) = context.lookup_type(id) else { + break; + }; + + let binder = context.lookup_forall_binder(binder_id); + let binder_kind = normalise::normalise(state, context, binder.kind)?; + + let rigid = state.fresh_rigid(context.queries, binder_kind); + id = SubstituteName::one(state, context, binder.name, rigid, inner)?; + } + + Ok(id) +} + +/// Peels constraint layers, pushing each as a wanted. +pub fn collect_wanteds( + state: &mut CheckState, + context: &CheckContext, + mut id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + safe_loop! { + id = normalise::expand(state, context, id)?; + match context.lookup_type(id) { + Type::Constrained(constraint, constrained) => { + state.push_wanted(constraint); + id = constrained; + } + _ => return Ok(id), + } + } +} + +/// Peels constraint layers, pushing each as a given. +pub fn collect_givens( + state: &mut CheckState, + context: &CheckContext, + mut id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + safe_loop! { + id = normalise::expand(state, context, id)?; + match context.lookup_type(id) { + Type::Constrained(constraint, constrained) => { + state.push_given(constraint); + id = constrained; + } + _ => return Ok(id), + } + } +} + +/// Peels constraint layers without introducing givens or wanteds. +pub fn without_constraints( + state: &mut CheckState, + context: &CheckContext, + mut id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + safe_loop! { + id = normalise::expand(state, context, id)?; + match context.lookup_type(id) { + Type::Constrained(_, constrained) => { + id = constrained; + } + _ => return Ok(id), + } + } +} + +/// Instantiates forall binders and collects wanted constraints. +pub fn instantiate_constrained( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let id = instantiate_unifications(state, context, id)?; + collect_wanteds(state, context, id) +} + +pub fn decompose_function( + state: &mut CheckState, + context: &CheckContext, + t: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let t = normalise::expand(state, context, t)?; + + match context.lookup_type(t) { + Type::Function(argument, result) => Ok(Some((argument, result))), + + Type::Unification(unification_id) => { + let argument = state.fresh_unification(context.queries, context.prim.t); + let result = state.fresh_unification(context.queries, context.prim.t); + + let function = context.intern_function(argument, result); + unification::solve(state, context, t, unification_id, function)?; + + Ok(Some((argument, result))) + } + + Type::Application(partial, result) => { + let partial = normalise::expand(state, context, partial)?; + if let Type::Application(constructor, argument) = context.lookup_type(partial) { + let constructor = normalise::expand(state, context, constructor)?; + if constructor == context.prim.function { + return Ok(Some((argument, result))); + } + if let Type::Unification(unification_id) = context.lookup_type(constructor) { + unification::solve( + state, + context, + constructor, + unification_id, + context.prim.function, + )?; + return Ok(Some((argument, result))); + } + } + Ok(None) + } + + _ => Ok(None), + } +} + +pub fn lookup_file_roles( + state: &mut CheckState, + context: &CheckContext, + file_id: FileId, + item_id: TypeItemId, +) -> QueryResult>> +where + Q: ExternalQueries, +{ + if file_id == context.id { + Ok(state.checked.lookup_roles(item_id)) + } else { + let checked = context.queries.checked2(file_id)?; + Ok(checked.lookup_roles(item_id)) + } +} + +pub fn is_newtype( + context: &CheckContext, + file_id: FileId, + item_id: TypeItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let type_item = if file_id == context.id { + context.lowered.info.get_type_item(item_id) + } else { + let lowered = context.queries.lowered(file_id)?; + return Ok(matches!( + lowered.info.get_type_item(item_id), + Some(lowering::TypeItemIr::NewtypeGroup { .. }) + )); + }; + Ok(matches!(type_item, Some(lowering::TypeItemIr::NewtypeGroup { .. }))) +} + +pub fn extract_type_constructor( + state: &mut CheckState, + context: &CheckContext, + mut id: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + safe_loop! { + id = normalise::expand(state, context, id)?; + match context.lookup_type(id) { + Type::Constructor(file_id, item_id) => return Ok(Some((file_id, item_id))), + Type::Application(function, _) | Type::KindApplication(function, _) => { + id = function; + } + _ => return Ok(None), + } + } +} + +pub 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)) +} + +pub fn get_newtype_inner( + state: &mut CheckState, + context: &CheckContext, + newtype_file: FileId, + newtype_id: TypeItemId, + newtype_type: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + if !is_newtype(context, newtype_file, newtype_id)? { + return Ok(None); + } + + let constructor_term_id = if newtype_file == context.id { + context.indexed.pairs.data_constructors(newtype_id).next() + } else { + let indexed = context.queries.indexed(newtype_file)?; + indexed.pairs.data_constructors(newtype_id).next() + }; + + let Some(constructor_term_id) = constructor_term_id else { + return Ok(None); + }; + + let constructor_type = lookup_file_term(state, context, newtype_file, constructor_term_id)?; + + let (_, arguments) = extract_all_applications(state, context, newtype_type)?; + + let mut current = constructor_type; + let mut arguments = arguments.iter().copied(); + let mut rigids = vec![]; + + safe_loop! { + current = normalise::expand(state, context, current)?; + let Type::Forall(binder_id, inner) = context.lookup_type(current) else { + break; + }; + + let binder = context.lookup_forall_binder(binder_id); + let replacement = arguments + .next() + .map(|argument| match argument { + KindOrType::Kind(argument) | KindOrType::Type(argument) => argument, + }) + .unwrap_or_else(|| { + let rigid = state.fresh_rigid(context.queries, binder.kind); + rigids.push(rigid); + rigid + }); + + current = SubstituteName::one(state, context, binder.name, replacement, inner)?; + } + + current = normalise::normalise(state, context, current)?; + + let InspectFunction { arguments, .. } = inspect_function(state, context, current)?; + let [inner] = arguments[..] else { return Ok(None) }; + + Ok(Some(NewtypeInner { inner, rigids })) +} diff --git a/compiler-core/checking2/src/core/unification.rs b/compiler-core/checking2/src/core/unification.rs new file mode 100644 index 00000000..458de1cb --- /dev/null +++ b/compiler-core/checking2/src/core/unification.rs @@ -0,0 +1,892 @@ +//! Implements the subtyping and unification algorithms. + +use std::iter; +use std::sync::Arc; + +use building_types::QueryResult; +use itertools::{EitherOrBoth, Itertools}; +use smol_str::SmolStr; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::substitute::SubstituteName; +use crate::core::{Depth, KindOrType, Name, RowField, RowType, RowTypeId, Type, TypeId, normalise}; +use crate::error::ErrorKind; +use crate::source::types; +use crate::state::{CheckState, UnificationEntry}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CanUnify { + Equal, + Apart, + Unify, +} + +impl CanUnify { + pub fn and_then(self, f: impl FnOnce() -> QueryResult) -> QueryResult { + if let CanUnify::Equal = self { f() } else { Ok(self) } + } +} + +/// Strategy for handling constrained types during [`subtype_with`]. +/// +/// Elaboration involves pushing constraints as "wanted". This behaviour is +/// only wanted in positions where the type checker can insert dictionaries. +/// +/// For example in [`Type::Function`], subtyping is [`NonElaborating`] +/// because it's impossible to insert dictionaries there; the same is +/// true when checking [`lowering::BinderKind`] in arguments and patterns. +pub trait SubtypePolicy +where + Q: ExternalQueries, +{ + fn on_constrained( + state: &mut CheckState, + context: &CheckContext, + t1: TypeId, + constraint: TypeId, + constrained: TypeId, + t2: TypeId, + ) -> QueryResult; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Elaborating; + +impl SubtypePolicy for Elaborating +where + Q: ExternalQueries, +{ + fn on_constrained( + state: &mut CheckState, + context: &CheckContext, + _t1: TypeId, + constraint: TypeId, + constrained: TypeId, + t2: TypeId, + ) -> QueryResult { + state.push_wanted(constraint); + subtype_with::(state, context, constrained, t2) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct NonElaborating; + +impl SubtypePolicy for NonElaborating +where + Q: ExternalQueries, +{ + fn on_constrained( + state: &mut CheckState, + context: &CheckContext, + t1: TypeId, + _constraint: TypeId, + _constrained: TypeId, + t2: TypeId, + ) -> QueryResult { + unify(state, context, t1, t2) + } +} + +pub fn subtype( + state: &mut CheckState, + context: &CheckContext, + t1: TypeId, + t2: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + subtype_with::(state, context, t1, t2) +} + +pub fn subtype_with( + state: &mut CheckState, + context: &CheckContext, + t1: TypeId, + t2: TypeId, +) -> QueryResult +where + P: SubtypePolicy, + Q: ExternalQueries, +{ + let t1 = normalise::expand(state, context, t1)?; + let t2 = normalise::expand(state, context, t2)?; + + if t1 == t2 { + return Ok(true); + } + + let t1_core = context.lookup_type(t1); + let t2_core = context.lookup_type(t2); + + match (t1_core, t2_core) { + // Function subtyping is contravariant on the argument type and + // covariant on the result type. It must also be non-elaborating + // on both positions; it's impossible to generate any constraint + // dictionaries at this position. + (Type::Function(t1_argument, t1_result), Type::Function(t2_argument, t2_result)) => { + Ok(subtype_with::(state, context, t2_argument, t1_argument)? + && subtype_with::(state, context, t1_result, t2_result)?) + } + + // Normalise Type::Function into Type::Application before unification + // This is explained in intern_function_application, it simplifies the + // subtyping rule tremendously vs. pattern matching Type::Application + (Type::Application(_, _), Type::Function(t2_argument, t2_result)) => { + let t2 = context.intern_function_application(t2_argument, t2_result); + subtype_with::(state, context, t1, t2) + } + (Type::Function(t1_argument, t1_result), Type::Application(_, _)) => { + let t1 = context.intern_function_application(t1_argument, t1_result); + subtype_with::(state, context, t1, t2) + } + + // Forall-R + // + // A <: forall b. B + // A <: B[b := b'] + // + // In practice, this disallows the subtyping + // + // (Int -> Int) <: (forall b. b -> b) + // (Int -> Int) <: (b' -> b') + // + // The rigid variable b' is scoped within the `forall`; this is + // enforced through unification and rigid variables carrying + // their depth, and promote_type checking that unification + // variables cannot be shallower than rigid variable depths. + // + // The classic example is `runST` + // + // runST :: forall a. (forall h. ST h a) -> a + // + // If `?a` is solved to a type containing `h`, the skolem escape + // check is triggered because `h` was defined in a deeper scope. + (_, Type::Forall(binder_id, inner)) => { + let binder = context.lookup_forall_binder(binder_id); + let skolem = state.fresh_rigid(context.queries, binder.kind); + + let inner = SubstituteName::one(state, context, binder.name, skolem, inner)?; + state.with_depth(|state| subtype_with::(state, context, t1, inner)) + } + + // Forall-L + // + // forall a. A <: B + // A[a := ?a] <: B + // + // In practice, this allows the subtyping + // + // (forall a. a -> a) <: Int -> Int + // (?a -> ?a) <: Int -> Int + // + // with the substitution [?a := Int], and + // + // (forall a. a -> a) <: (forall b. b -> b) + // (?a -> ?a) <: (b' -> b') + // + // with the substitution [?a := b']. + (Type::Forall(binder_id, inner), _) => { + let binder = context.lookup_forall_binder(binder_id); + let unification = state.fresh_unification(context.queries, binder.kind); + + let inner = SubstituteName::one(state, context, binder.name, unification, inner)?; + subtype_with::(state, context, inner, t2) + } + + (Type::Constrained(constraint, constrained), _) => { + P::on_constrained(state, context, t1, constraint, constrained, t2) + } + + // Record subtyping is implemented through subtype_rows. Unlike + // unification, the directionality of subtyping allows us to emit + // errors like AdditionalProperty and PropertyIsMissing. + ( + Type::Application(t1_function, t1_argument), + Type::Application(t2_function, t2_argument), + ) if t1_function == context.prim.record && t2_function == context.prim.record => { + let t1_argument = normalise::expand(state, context, t1_argument)?; + let t2_argument = normalise::expand(state, context, t2_argument)?; + + let t1_argument_core = context.lookup_type(t1_argument); + let t2_argument_core = context.lookup_type(t2_argument); + + if let (Type::Row(t1_row_id), Type::Row(t2_row_id)) = + (t1_argument_core, t2_argument_core) + { + subtype_rows::(state, context, t1_row_id, t2_row_id) + } else { + unify(state, context, t1, t2) + } + } + + (_, _) => unify(state, context, t1, t2), + } +} + +pub fn unify( + state: &mut CheckState, + context: &CheckContext, + t1: TypeId, + t2: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let t1 = normalise::expand(state, context, t1)?; + let t2 = normalise::expand(state, context, t2)?; + + if t1 == t2 { + return Ok(true); + } + + let t1_core = context.lookup_type(t1); + let t2_core = context.lookup_type(t2); + + let unifies = match (t1_core, t2_core) { + // PureScript has an impredicative type system i.e. unification + // variables can be solved to Type::Forall. These rules must + // be placed before the one-sided Type::Forall skolemisation. + (Type::Unification(id), _) => return solve(state, context, t1, id, t2), + (_, Type::Unification(id)) => return solve(state, context, t2, id, t1), + + ( + Type::Application(t1_function, t1_argument), + Type::Application(t2_function, t2_argument), + ) => { + unify(state, context, t1_function, t2_function)? + && unify(state, context, t1_argument, t2_argument)? + } + + ( + Type::KindApplication(t1_function, t1_argument), + Type::KindApplication(t2_function, t2_argument), + ) => { + unify(state, context, t1_function, t2_function)? + && unify(state, context, t1_argument, t2_argument)? + } + + // Forall-Forall + // + // forall a. A ~ forall b. B + // A[a := c] ~ B[b := c] + // + // The rigid variable c is scoped within both `forall`. Unlike + // subtyping that has directionality, unification needs both + // types to be instantiated with the same fresh varible. It + // does not make sense for Forall to instantiate either side. + (Type::Forall(t1_binder_id, t1_inner), Type::Forall(t2_binder_id, t2_inner)) => { + let t1_binder = context.lookup_forall_binder(t1_binder_id); + let t2_binder = context.lookup_forall_binder(t2_binder_id); + + unify(state, context, t1_binder.kind, t2_binder.kind)?; + + let skolem = state.fresh_rigid(context.queries, t1_binder.kind); + + let t1_inner = SubstituteName::one(state, context, t1_binder.name, skolem, t1_inner)?; + let t2_inner = SubstituteName::one(state, context, t2_binder.name, skolem, t2_inner)?; + + state.with_depth(|state| unify(state, context, t1_inner, t2_inner))? + } + + // Forall-B, A-Forall + // + // The Type::Unification pattern above is an early catch-all that + // enables impredicativity. In practice, this unifies the following: + // + // (forall a. a -> a) ~ (?b -> ?b) + // (a' -> a') ~ (?b -> ?b) + // + // with the substitution [?b := 'a], and + // + // (?a -> ?a) ~ (forall b. b -> b) + // (?a -> ?a) ~ (b' -> b') + // + // with the substitution [?a := 'b]. + (Type::Forall(binder_id, inner), _) => { + let binder = context.lookup_forall_binder(binder_id); + let skolem = state.fresh_rigid(context.queries, binder.kind); + let inner = SubstituteName::one(state, context, binder.name, skolem, inner)?; + state.with_depth(|state| unify(state, context, inner, t2))? + } + (_, Type::Forall(binder_id, inner)) => { + let binder = context.lookup_forall_binder(binder_id); + let skolem = state.fresh_rigid(context.queries, binder.kind); + let inner = SubstituteName::one(state, context, binder.name, skolem, inner)?; + state.with_depth(|state| unify(state, context, t1, inner))? + } + + ( + Type::Constrained(t1_constraint, t1_inner), + Type::Constrained(t2_constraint, t2_inner), + ) => { + unify(state, context, t1_constraint, t2_constraint)? + && unify(state, context, t1_inner, t2_inner)? + } + + (Type::Function(t1_argument, t1_result), Type::Function(t2_argument, t2_result)) => { + unify(state, context, t1_argument, t2_argument)? + && unify(state, context, t1_result, t2_result)? + } + + (Type::Kinded(t1_inner, t1_kind), Type::Kinded(t2_inner, t2_kind)) => { + unify(state, context, t1_inner, t2_inner)? && unify(state, context, t1_kind, t2_kind)? + } + + (Type::Constructor(t1_file, t1_item), Type::Constructor(t2_file, t2_item)) + if t1_file == t2_file && t1_item == t2_item => + { + true + } + + (Type::Integer(t1_value), Type::Integer(t2_value)) if t1_value == t2_value => true, + + (Type::String(t1_kind, t1_value), Type::String(t2_kind, t2_value)) + if t1_kind == t2_kind && t1_value == t2_value => + { + true + } + + (Type::Row(t1_row_id), Type::Row(t2_row_id)) => { + unify_rows(state, context, t1_row_id, t2_row_id)? + } + + (Type::Rigid(t1_name, _, t1_kind), Type::Rigid(t2_name, _, t2_kind)) + if t1_name == t2_name => + { + unify(state, context, t1_kind, t2_kind)? + } + + _ => false, + }; + + if !unifies { + let t1 = state.pretty_id(context, t1)?; + let t2 = state.pretty_id(context, t2)?; + state.insert_error(ErrorKind::CannotUnify { t1, t2 }); + } + + Ok(unifies) +} + +/// Determines if two types [`CanUnify`]. +/// +/// This is a pure, speculative version of [`unify`] that does not perform +/// any side effects. It is used in instance matching to check whether two +/// types bound to the same rigid variable can actually unify before +/// generating an equality. +pub fn can_unify( + state: &mut CheckState, + context: &CheckContext, + t1: TypeId, + t2: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let t1 = normalise::expand(state, context, t1)?; + let t2 = normalise::expand(state, context, t2)?; + + if t1 == t2 { + return Ok(CanUnify::Equal); + } + + let t1_core = context.lookup_type(t1); + let t2_core = context.lookup_type(t2); + + match (t1_core, t2_core) { + (Type::Unification(_), _) | (_, Type::Unification(_)) => Ok(CanUnify::Unify), + (Type::Unknown(_), _) | (_, Type::Unknown(_)) => Ok(CanUnify::Unify), + (Type::Row(_), Type::Row(_)) => Ok(CanUnify::Unify), + + ( + Type::Application(t1_function, t1_argument), + Type::Application(t2_function, a22t2_argument), + ) => can_unify(state, context, t1_function, t2_function)? + .and_then(|| can_unify(state, context, t1_argument, a22t2_argument)), + + (Type::Function(t1_argument, t1_result), Type::Function(t2_argument, t2_result)) => { + can_unify(state, context, t1_argument, t2_argument)? + .and_then(|| can_unify(state, context, t1_result, t2_result)) + } + + // Function(a, b) and Application(Application(f, a), b) can + // unify when `f` resolves to the Function constructor. + (Type::Function(..), Type::Application(..)) + | (Type::Application(..), Type::Function(..)) => Ok(CanUnify::Unify), + + ( + Type::KindApplication(t1_function, t1_argument), + Type::KindApplication(t2_function, t2_argument), + ) => can_unify(state, context, t1_function, t2_function)? + .and_then(|| can_unify(state, context, t1_argument, t2_argument)), + + (Type::Kinded(t1_inner, t1_kind), Type::Kinded(t2_inner, t2_kind)) => { + can_unify(state, context, t1_inner, t2_inner)? + .and_then(|| can_unify(state, context, t1_kind, t2_kind)) + } + + ( + Type::Constrained(t1_constraint, t1_constrained), + Type::Constrained(t2_constraint, t2_constrained), + ) => can_unify(state, context, t1_constraint, t2_constraint)? + .and_then(|| can_unify(state, context, t1_constrained, t2_constrained)), + + (Type::Rigid(t1_name, _, t1_kind), Type::Rigid(t2_name, _, t2_kind)) => { + if t1_name == t2_name { + can_unify(state, context, t1_kind, t2_kind) + } else { + Ok(CanUnify::Apart) + } + } + + (Type::Forall(t1_binder, t1_inner), Type::Forall(t2_binder, t2_inner)) => { + let t1_binder = context.lookup_forall_binder(t1_binder); + let t2_binder = context.lookup_forall_binder(t2_binder); + can_unify(state, context, t1_binder.kind, t2_binder.kind)? + .and_then(|| can_unify(state, context, t1_inner, t2_inner)) + } + + (Type::SynonymApplication(t1_synonym), Type::SynonymApplication(t2_synonym)) => { + let t1_synonym = context.lookup_synonym(t1_synonym); + let t2_synonym = context.lookup_synonym(t2_synonym); + + if t1_synonym.reference != t2_synonym.reference + || t1_synonym.arguments.len() != t2_synonym.arguments.len() + { + return Ok(CanUnify::Apart); + } + + iter::zip(&*t1_synonym.arguments, &*t2_synonym.arguments).try_fold( + CanUnify::Equal, + |result, (a1, a2)| { + result.and_then(|| match (a1, a2) { + (KindOrType::Kind(a1), KindOrType::Kind(a2)) + | (KindOrType::Type(a1), KindOrType::Type(a2)) => { + can_unify(state, context, *a1, *a2) + } + _ => Ok(CanUnify::Apart), + }) + }, + ) + } + + _ => Ok(CanUnify::Apart), + } +} + +/// Solves a unification variable to a given type. +pub fn solve( + state: &mut CheckState, + context: &CheckContext, + unification: TypeId, + id: u32, + solution: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let solution = normalise::expand(state, context, solution)?; + + match promote_type(state, context, id, solution)? { + PromoteResult::Ok => {} + PromoteResult::OccursCheck | PromoteResult::SkolemEscape => { + let t1 = state.pretty_id(context, unification)?; + let t2 = state.pretty_id(context, solution)?; + state.insert_error(ErrorKind::CannotUnify { t1, t2 }); + return Ok(false); + } + } + + let unification_kind = state.unifications.get(id).kind; + let solution_kind = types::elaborate_kind(state, context, solution)?; + unify(state, context, unification_kind, solution_kind)?; + + state.unifications.solve(id, solution); + Ok(true) +} + +enum PromoteResult { + Ok, + OccursCheck, + SkolemEscape, +} + +impl PromoteResult { + fn and_then( + self, + f: impl FnOnce() -> QueryResult, + ) -> QueryResult { + match self { + PromoteResult::Ok => f(), + result => Ok(result), + } + } +} + +/// Checks that solving a unification variable is safe. +/// +/// This function has several responsibilities: +/// - the [occurs check], where a solution that contains the unification +/// variable being solved is rejected. This avoids infinite types; +/// - the [skolem escape check], where a solution that contains a rigid +/// type variable deeper than the unification variable is rejected; +/// - lastly, promotion which solves deeper unification variables into +/// fresh unification variables as shallow as the one being solved. +/// +/// Since PureScript is an impredicative language i.e. it allows unification +/// variables to be solved to [`Type::Forall`], we keep track of the names +/// bound in the context specifically to allow `?t := forall a. a -> a` to +/// skip the skolem escape check completely. +/// +/// [occurs check]: PromoteResult::OccursCheck +/// [skolem escape check]: PromoteResult::SkolemEscape +fn promote_type( + state: &mut CheckState, + context: &CheckContext, + id: u32, + solution: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + struct PromotionState { + id: u32, + depth: Depth, + names: Vec, + } + + fn check( + promote: &mut PromotionState, + state: &mut CheckState, + context: &CheckContext, + id: TypeId, + ) -> QueryResult + where + Q: ExternalQueries, + { + let id = normalise::expand(state, context, id)?; + let t = context.lookup_type(id); + + match t { + Type::Application(function, argument) | Type::KindApplication(function, argument) => { + check(promote, state, context, function)? + .and_then(|| check(promote, state, context, argument)) + } + + Type::SynonymApplication(synonym_id) => { + let synonym = context.lookup_synonym(synonym_id); + for application in synonym.arguments.iter() { + let argument = match application { + KindOrType::Kind(argument) | KindOrType::Type(argument) => *argument, + }; + let result = check(promote, state, context, argument)?; + if !matches!(result, PromoteResult::Ok) { + return Ok(result); + } + } + Ok(PromoteResult::Ok) + } + + Type::Forall(binder_id, inner) => { + let binder = context.lookup_forall_binder(binder_id); + + let on_kind = check(promote, state, context, binder.kind)?; + if !matches!(on_kind, PromoteResult::Ok) { + return Ok(on_kind); + } + + promote.names.push(binder.name); + let on_inner = check(promote, state, context, inner)?; + promote.names.pop(); + + Ok(on_inner) + } + + Type::Constrained(constraint, inner) => check(promote, state, context, constraint)? + .and_then(|| check(promote, state, context, inner)), + + Type::Function(argument, result) => check(promote, state, context, argument)? + .and_then(|| check(promote, state, context, result)), + + Type::Kinded(inner, kind) => check(promote, state, context, inner)? + .and_then(|| check(promote, state, context, kind)), + + Type::Constructor(_, _) => Ok(PromoteResult::Ok), + Type::Integer(_) | Type::String(_, _) => Ok(PromoteResult::Ok), + + Type::Row(row_id) => { + let row = context.lookup_row_type(row_id); + for field in row.fields.iter() { + let on_field = check(promote, state, context, field.id)?; + if !matches!(on_field, PromoteResult::Ok) { + return Ok(on_field); + } + } + if let Some(tail) = row.tail { + check(promote, state, context, tail) + } else { + Ok(PromoteResult::Ok) + } + } + + Type::Rigid(name, rigid_depth, kind) => { + if promote.names.contains(&name) { + check(promote, state, context, kind) + } else if rigid_depth > promote.depth { + Ok(PromoteResult::SkolemEscape) + } else { + check(promote, state, context, kind) + } + } + + Type::Unification(id) => { + // Disallow `?t := ?t` + if id == promote.id { + return Ok(PromoteResult::OccursCheck); + } + // When solving `a` to a deeper unification variable `b`, + // + // ?a[0] := ?b[1] + // + // we create a fresh variable `c` as shallow as `a`, + // + // ?b[1] := ?c[0] + // + // which would be pruned into the final solution + // + // ?a[0] := ?c[0] + // + // This process eliminates unsolved unification variables + // at depth markers that are not in scope, and replaces + // them with unification variables that are in scope. + let UnificationEntry { depth, kind, .. } = *state.unifications.get(id); + if depth > promote.depth { + let promoted = state.unifications.fresh(promote.depth, kind); + let promoted = context.queries.intern_type(Type::Unification(promoted)); + state.unifications.solve(id, promoted); + } + + Ok(PromoteResult::Ok) + } + + Type::Free(_) | Type::Unknown(_) => Ok(PromoteResult::Ok), + } + } + + let depth = state.unifications.get(id).depth; + let names = vec![]; + + let mut promote = PromotionState { id, depth, names }; + check(&mut promote, state, context, solution) +} + +/// Unification on row types. +/// +/// Algorithmically, `t1 ~ t2` checks shared labels field-by-field via [`unify`]. +/// - if both are closed, then additional fields are disallowed +/// - if `t1` is closed, additional fields in `t2` are disallowed +/// - if `t2` is closed, additional fields in `t1` are disallowed +/// - finally, [`unify_row_tails`] handles the remaining cases +fn unify_rows( + state: &mut CheckState, + context: &CheckContext, + t1: RowTypeId, + t2: RowTypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let t1_row = context.lookup_row_type(t1); + let t2_row = context.lookup_row_type(t2); + + let (left_only, right_only, ok) = partition_row_fields(state, context, &t1_row, &t2_row)?; + + if !ok { + return Ok(false); + } + + if t1_row.tail.is_none() && t2_row.tail.is_none() { + return Ok(left_only.is_empty() && right_only.is_empty()); + } + + if t2_row.tail.is_none() && !left_only.is_empty() { + return Ok(false); + } + + if t1_row.tail.is_none() && !right_only.is_empty() { + return Ok(false); + } + + unify_row_tails(state, context, t1_row.tail, t2_row.tail, left_only, right_only) +} + +/// Like [`unify_rows`], but uses [`subtype`] for partitioning. +/// +/// Additionally, the directionality of the [`subtype`] relation enables the +/// addition of [`PropertyIsMissing`] and [`AdditionalProperty`] error when +/// dealing with closed rows. +/// +/// Algorithmically, `t1 <: t2` checks shared labels field-by-field via [`subtype`]. +/// +/// Extra labels are only rejected when they appear against a closed row: +/// - if `t1` is closed, labels exclusive to `t2` are [`PropertyIsMissing`]; +/// - if `t2` is closed, labels exclusive to `t1` are [`AdditionalProperty`]. +/// - finally, [`unify_row_tails`] handles the remaining cases +/// +/// [`PropertyIsMissing`]: ErrorKind::PropertyIsMissing +/// [`AdditionalProperty`]: ErrorKind::AdditionalProperty +fn subtype_rows( + state: &mut CheckState, + context: &CheckContext, + t1: RowTypeId, + t2: RowTypeId, +) -> QueryResult +where + P: SubtypePolicy, + Q: ExternalQueries, +{ + let t1_row = context.lookup_row_type(t1); + let t2_row = context.lookup_row_type(t2); + + let (left_only, right_only, ok) = partition_row_fields_with( + state, + context, + &t1_row, + &t2_row, + |state, context, left, right| subtype_with::(state, context, left, right), + )?; + + if !ok { + return Ok(false); + } + + let mut failed = false; + + if t1_row.tail.is_none() && !right_only.is_empty() { + let labels = Arc::from_iter(right_only.iter().map(|field| SmolStr::clone(&field.label))); + state.insert_error(ErrorKind::PropertyIsMissing { labels }); + failed = true; + } + + if t2_row.tail.is_none() && !left_only.is_empty() { + let labels = Arc::from_iter(left_only.iter().map(|field| SmolStr::clone(&field.label))); + state.insert_error(ErrorKind::AdditionalProperty { labels }); + failed = true; + } + + if failed { + return Ok(false); + } + + unify_row_tails(state, context, t1_row.tail, t2_row.tail, left_only, right_only) +} + +fn partition_row_fields( + state: &mut CheckState, + context: &CheckContext, + t1_row: &RowType, + t2_row: &RowType, +) -> QueryResult<(Vec, Vec, bool)> +where + Q: ExternalQueries, +{ + partition_row_fields_with(state, context, t1_row, t2_row, unify) +} + +fn partition_row_fields_with( + state: &mut CheckState, + context: &CheckContext, + t1_row: &RowType, + t2_row: &RowType, + mut field_check: F, +) -> QueryResult<(Vec, Vec, bool)> +where + Q: ExternalQueries, + F: FnMut(&mut CheckState, &CheckContext, TypeId, TypeId) -> QueryResult, +{ + let mut extras_left = vec![]; + let mut extras_right = vec![]; + let mut ok = true; + + let t1_fields = t1_row.fields.iter(); + let t2_fields = t2_row.fields.iter(); + + for field in t1_fields.merge_join_by(t2_fields, |left, right| left.label.cmp(&right.label)) { + match field { + EitherOrBoth::Both(left, right) => { + if !field_check(state, context, left.id, right.id)? { + ok = false; + } + } + EitherOrBoth::Left(left) => extras_left.push(left.clone()), + EitherOrBoth::Right(right) => extras_right.push(right.clone()), + } + } + + Ok((extras_left, extras_right, ok)) +} + +fn unify_row_tails( + state: &mut CheckState, + context: &CheckContext, + t1_tail: Option, + t2_tail: Option, + extras_left: Vec, + extras_right: Vec, +) -> QueryResult +where + Q: ExternalQueries, +{ + match (t1_tail, t2_tail) { + (None, None) => Ok(true), + + // t1_tail ~ ( ...extras_right ) + (Some(t1_tail), None) => { + let row = RowType::new(extras_right, None); + let row_id = context.intern_row_type(row); + let row_type = context.intern_row(row_id); + unify(state, context, t1_tail, row_type) + } + + // ( ...extras_left ) ~ t2_tail + (None, Some(t2_tail)) => { + let row = RowType::new(extras_left, None); + let row_id = context.intern_row_type(row); + let row_type = context.intern_row(row_id); + unify(state, context, t2_tail, row_type) + } + + // ( | t1_tail ) ~ ( | t2_tail ) + // + // If no additional fields + // + // t1_tail ~ t2_tail + // + // with additional fields + // + // t1_tail ~ ( ...extras_right | ?tail ) + // t2_tail ~ ( ...extras_left | ?tail ) + // + (Some(t1_tail), Some(t2_tail)) => { + if extras_left.is_empty() && extras_right.is_empty() { + return unify(state, context, t1_tail, t2_tail); + } + + let tail = Some(state.fresh_unification(context.queries, context.prim.row_type)); + + let left_tail_row = RowType::new(extras_right, tail); + let left_tail_row_id = context.intern_row_type(left_tail_row); + let left_tail_row_type = context.intern_row(left_tail_row_id); + + let right_tail_row = RowType::new(extras_left, tail); + let right_tail_row_id = context.intern_row_type(right_tail_row); + let right_tail_row_type = context.intern_row(right_tail_row_id); + + Ok(unify(state, context, t1_tail, left_tail_row_type)? + && unify(state, context, t2_tail, right_tail_row_type)?) + } + } +} diff --git a/compiler-core/checking2/src/core/walk.rs b/compiler-core/checking2/src/core/walk.rs new file mode 100644 index 00000000..4d9c99f4 --- /dev/null +++ b/compiler-core/checking2/src/core/walk.rs @@ -0,0 +1,94 @@ +//! Implements type walking for the core representation. + +use building_types::QueryResult; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{ForallBinder, KindOrType, Type, TypeId, normalise}; +use crate::state::CheckState; + +pub enum WalkAction { + Stop, + Continue, +} + +pub trait TypeWalker { + fn visit( + &mut self, + state: &mut CheckState, + context: &CheckContext, + id: TypeId, + t: &Type, + ) -> QueryResult; + + fn visit_binder(&mut self, _binder: &ForallBinder) {} +} + +pub fn walk_type( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, + walker: &mut W, +) -> QueryResult<()> +where + Q: ExternalQueries, + W: TypeWalker, +{ + let id = normalise::normalise(state, context, id)?; + let t = context.lookup_type(id); + + if let WalkAction::Stop = walker.visit(state, context, id, &t)? { + return Ok(()); + } + + match t { + Type::Application(function, argument) | Type::KindApplication(function, argument) => { + walk_type(state, context, function, walker)?; + walk_type(state, context, argument, walker)?; + } + Type::SynonymApplication(synonym_id) => { + let synonym = context.lookup_synonym(synonym_id); + for application in synonym.arguments.iter() { + let argument = match application { + KindOrType::Kind(argument) | KindOrType::Type(argument) => *argument, + }; + walk_type(state, context, argument, walker)?; + } + } + Type::Forall(binder_id, inner) => { + let binder = context.lookup_forall_binder(binder_id); + walker.visit_binder(&binder); + walk_type(state, context, binder.kind, walker)?; + walk_type(state, context, inner, walker)?; + } + Type::Constrained(constraint, inner) => { + walk_type(state, context, constraint, walker)?; + walk_type(state, context, inner, walker)?; + } + Type::Function(argument, result) => { + walk_type(state, context, argument, walker)?; + walk_type(state, context, result, walker)?; + } + Type::Kinded(inner, kind) => { + walk_type(state, context, inner, walker)?; + walk_type(state, context, kind, walker)?; + } + Type::Constructor(_, _) => {} + Type::Integer(_) | Type::String(_, _) => {} + Type::Row(row_id) => { + let row = context.lookup_row_type(row_id); + for field in row.fields.iter() { + walk_type(state, context, field.id, walker)?; + } + if let Some(tail) = row.tail { + walk_type(state, context, tail, walker)?; + } + } + Type::Rigid(_, _, kind) => { + walk_type(state, context, kind, walker)?; + } + Type::Unification(_) | Type::Free(_) | Type::Unknown(_) => {} + } + + Ok(()) +} diff --git a/compiler-core/checking2/src/core/zonk.rs b/compiler-core/checking2/src/core/zonk.rs new file mode 100644 index 00000000..bfc20f18 --- /dev/null +++ b/compiler-core/checking2/src/core/zonk.rs @@ -0,0 +1,31 @@ +use building_types::QueryResult; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::fold::{FoldAction, TypeFold, fold_type}; +use crate::core::{Type, TypeId}; +use crate::state::CheckState; + +struct Zonk; + +impl TypeFold for Zonk { + fn transform( + &mut self, + _state: &mut CheckState, + _context: &CheckContext, + _id: TypeId, + _t: &Type, + ) -> QueryResult + where + Q: ExternalQueries, + { + Ok(FoldAction::Continue) + } +} + +pub fn zonk(state: &mut CheckState, context: &CheckContext, id: TypeId) -> QueryResult +where + Q: ExternalQueries, +{ + fold_type(state, context, id, &mut Zonk) +} diff --git a/compiler-core/checking2/src/error.rs b/compiler-core/checking2/src/error.rs new file mode 100644 index 00000000..25db6fb5 --- /dev/null +++ b/compiler-core/checking2/src/error.rs @@ -0,0 +1,156 @@ +//! Implements the errors emitted by the type checker. + +use std::sync::Arc; + +use smol_str::SmolStr; + +use crate::core::SmolStrId; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ErrorCrumb { + TermDeclaration(indexing::TermItemId), + TypeDeclaration(indexing::TypeItemId), + ConstructorArgument(lowering::TypeId), + + InferringKind(lowering::TypeId), + CheckingKind(lowering::TypeId), + + InferringBinder(lowering::BinderId), + CheckingBinder(lowering::BinderId), + + InferringExpression(lowering::ExpressionId), + CheckingExpression(lowering::ExpressionId), + + InferringDoBind(lowering::DoStatementId), + InferringDoDiscard(lowering::DoStatementId), + CheckingDoLet(lowering::DoStatementId), + + InferringAdoMap(lowering::DoStatementId), + InferringAdoApply(lowering::DoStatementId), + CheckingAdoLet(lowering::DoStatementId), + + CheckingLetName(lowering::LetBindingNameGroupId), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ErrorKind { + AmbiguousConstraint { + constraint: SmolStrId, + }, + CannotDeriveClass { + class_file: files::FileId, + class_id: indexing::TypeItemId, + }, + CannotDeriveForType { + type_message: SmolStrId, + }, + ContravariantOccurrence { + type_message: SmolStrId, + }, + CovariantOccurrence { + type_message: SmolStrId, + }, + CannotUnify { + t1: SmolStrId, + t2: SmolStrId, + }, + DeriveInvalidArity { + class_file: files::FileId, + class_id: indexing::TypeItemId, + expected: usize, + actual: usize, + }, + DeriveNotSupportedYet { + class_file: files::FileId, + class_id: indexing::TypeItemId, + }, + DeriveMissingFunctor, + EmptyAdoBlock, + EmptyDoBlock, + InvalidFinalBind, + InvalidFinalLet, + InstanceHeadMismatch { + class_file: files::FileId, + class_item: indexing::TypeItemId, + expected: usize, + actual: usize, + }, + InstanceHeadLabeledRow { + class_file: files::FileId, + class_item: indexing::TypeItemId, + position: usize, + type_message: SmolStrId, + }, + InstanceMemberTypeMismatch { + expected: SmolStrId, + actual: SmolStrId, + }, + InvalidTypeApplication { + function_type: SmolStrId, + function_kind: SmolStrId, + argument_type: SmolStrId, + }, + InvalidTypeOperator { + kind_message: SmolStrId, + }, + ExpectedNewtype { + type_message: SmolStrId, + }, + InvalidNewtypeDeriveSkolemArguments, + NonLocalNewtype { + type_message: SmolStrId, + }, + NoInstanceFound { + constraint: SmolStrId, + }, + PartialSynonymApplication { + id: lowering::TypeId, + }, + RecursiveSynonymExpansion { + file_id: files::FileId, + type_id: indexing::TypeItemId, + }, + TooManyBinders { + signature: Option, + expected: u32, + actual: u32, + }, + TypeSignatureVariableMismatch { + id: lowering::TypeId, + expected: u32, + actual: u32, + }, + InvalidRoleDeclaration { + index: usize, + declared: crate::core::Role, + inferred: crate::core::Role, + }, + CoercibleConstructorNotInScope { + file_id: files::FileId, + item_id: indexing::TypeItemId, + }, + CustomWarning { + message_id: SmolStrId, + }, + RedundantPatterns { + patterns: Arc<[SmolStr]>, + }, + MissingPatterns { + patterns: Arc<[SmolStrId]>, + }, + CustomFailure { + message_id: SmolStrId, + }, + PropertyIsMissing { + labels: Arc<[SmolStr]>, + }, + AdditionalProperty { + labels: Arc<[SmolStr]>, + }, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct CheckError { + pub kind: ErrorKind, + pub crumbs: Arc<[ErrorCrumb]>, +} diff --git a/compiler-core/checking2/src/implication.rs b/compiler-core/checking2/src/implication.rs new file mode 100644 index 00000000..4ebe2ac8 --- /dev/null +++ b/compiler-core/checking2/src/implication.rs @@ -0,0 +1,80 @@ +//! Implements the implication tree for constraint collection. + +use std::collections::VecDeque; +use std::ops::{Index, IndexMut}; + +use crate::core::TypeId; + +/// A unique identifier for an implication scope. +pub type ImplicationId = u32; + +/// A node in the implication tree. +#[derive(Default)] +pub struct Implication { + pub given: Vec, + pub wanted: VecDeque, + pub children: Vec, + pub parent: Option, +} + +impl Implication { + pub fn new(parent: Option) -> Implication { + Implication { parent, ..Implication::default() } + } +} + +/// Keeps track of implications for the type checker. +pub struct Implications { + nodes: Vec, + current: ImplicationId, +} + +impl Implications { + pub fn new() -> Self { + Implications { nodes: vec![Implication::new(None)], current: 0 } + } + + pub fn current(&self) -> ImplicationId { + self.current + } + + pub fn current_mut(&mut self) -> &mut Implication { + &mut self.nodes[self.current as usize] + } + + pub fn push(&mut self) -> ImplicationId { + let parent = self.current; + let id = self.nodes.len() as ImplicationId; + self.nodes.push(Implication::new(Some(parent))); + self.nodes[parent as usize].children.push(id); + self.current = id; + id + } + + pub fn pop(&mut self, implication: ImplicationId) { + debug_assert_eq!(implication, self.current); + let parent = + self[implication].parent.expect("invariant violated: missing implication parent"); + self.current = parent; + } +} + +impl Default for Implications { + fn default() -> Implications { + Implications::new() + } +} + +impl Index for Implications { + type Output = Implication; + + fn index(&self, index: ImplicationId) -> &Self::Output { + &self.nodes[index as usize] + } +} + +impl IndexMut for Implications { + fn index_mut(&mut self, index: ImplicationId) -> &mut Self::Output { + &mut self.nodes[index as usize] + } +} diff --git a/compiler-core/checking2/src/interners.rs b/compiler-core/checking2/src/interners.rs new file mode 100644 index 00000000..c68e180c --- /dev/null +++ b/compiler-core/checking2/src/interners.rs @@ -0,0 +1,90 @@ +use std::ops; + +use smol_str::SmolStr; + +use crate::core::{ + ForallBinder, ForallBinderId, RowType, RowTypeId, Synonym, SynonymId, Type, TypeId, +}; + +#[derive(Default)] +pub struct CoreInterners { + types: interner::Interner, + forall_binders: interner::Interner, + row_types: interner::Interner, + synonyms: interner::Interner, + smol_strs: interner::Interner, +} + +impl CoreInterners { + pub fn intern_type(&mut self, t: Type) -> TypeId { + self.types.intern(t) + } + + pub fn lookup_type(&self, id: TypeId) -> Type { + self.types[id].clone() + } + + pub fn intern_forall_binder(&mut self, b: ForallBinder) -> ForallBinderId { + self.forall_binders.intern(b) + } + + pub fn lookup_forall_binder(&self, id: ForallBinderId) -> ForallBinder { + self.forall_binders[id] + } + + pub fn intern_row_type(&mut self, r: RowType) -> RowTypeId { + self.row_types.intern(r) + } + + pub fn lookup_row_type(&self, id: RowTypeId) -> RowType { + self.row_types[id].clone() + } + + pub fn intern_synonym(&mut self, s: Synonym) -> SynonymId { + self.synonyms.intern(s) + } + + pub fn lookup_synonym(&self, id: SynonymId) -> Synonym { + self.synonyms[id].clone() + } + + pub fn intern_smol_str(&mut self, s: SmolStr) -> crate::core::SmolStrId { + self.smol_strs.intern(s) + } + + pub fn lookup_smol_str(&self, id: crate::core::SmolStrId) -> SmolStr { + self.smol_strs[id].clone() + } +} + +impl ops::Index for CoreInterners { + type Output = Type; + + fn index(&self, id: TypeId) -> &Type { + &self.types[id] + } +} + +impl ops::Index for CoreInterners { + type Output = ForallBinder; + + fn index(&self, id: ForallBinderId) -> &ForallBinder { + &self.forall_binders[id] + } +} + +impl ops::Index for CoreInterners { + type Output = RowType; + + fn index(&self, id: RowTypeId) -> &RowType { + &self.row_types[id] + } +} + +impl ops::Index for CoreInterners { + type Output = Synonym; + + fn index(&self, id: SynonymId) -> &Synonym { + &self.synonyms[id] + } +} diff --git a/compiler-core/checking2/src/lib.rs b/compiler-core/checking2/src/lib.rs new file mode 100644 index 00000000..e72f0649 --- /dev/null +++ b/compiler-core/checking2/src/lib.rs @@ -0,0 +1,254 @@ +pub mod context; +pub mod core; +pub mod error; +pub mod implication; +pub mod safety; +pub mod source; +pub mod state; + +pub mod interners; +pub use interners::CoreInterners; + +use std::sync::Arc; + +use building_types::{QueryProxy, QueryResult}; +use files::FileId; +use indexing::{DeriveId, InstanceId, TermItemId, TypeItemId}; +use resolving::ResolvedModule; +use rustc_hash::FxHashMap; +use smol_str::SmolStr; + +use crate::core::{ + CheckedClass, CheckedInstance, CheckedSynonym, ForallBinder, ForallBinderId, Name, Role, + RowType, RowTypeId, SmolStrId, Synonym, SynonymId, Type, TypeId, +}; +use crate::error::CheckError; + +pub trait ExternalQueries: + QueryProxy< + Parsed = parsing::FullParsedModule, + Stabilized = Arc, + Indexed = Arc, + Lowered = Arc, + Grouped = Arc, + Resolved = Arc, + Bracketed = Arc, + Sectioned = Arc, + > +{ + fn checked2(&self, id: FileId) -> QueryResult>; + + fn intern_type(&self, t: Type) -> TypeId; + fn lookup_type(&self, id: TypeId) -> Type; + + fn intern_forall_binder(&self, b: ForallBinder) -> ForallBinderId; + fn lookup_forall_binder(&self, id: ForallBinderId) -> ForallBinder; + + fn intern_row_type(&self, r: RowType) -> RowTypeId; + fn lookup_row_type(&self, id: RowTypeId) -> RowType; + + fn intern_synonym(&self, s: Synonym) -> SynonymId; + fn lookup_synonym(&self, id: SynonymId) -> Synonym; + + fn intern_smol_str(&self, s: SmolStr) -> core::SmolStrId; + fn lookup_smol_str(&self, id: core::SmolStrId) -> SmolStr; +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct CheckedModule { + pub types: FxHashMap, + pub terms: FxHashMap, + pub synonyms: FxHashMap, + pub classes: FxHashMap, + pub instances: FxHashMap, + pub derived: FxHashMap, + pub roles: FxHashMap>, + pub nodes: CheckedNodes, + pub errors: Vec, + pub names: FxHashMap, +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct CheckedNodes { + pub types: FxHashMap, + pub expressions: FxHashMap, + pub binders: FxHashMap, + pub lets: FxHashMap, + pub puns: FxHashMap, + pub sections: FxHashMap, + pub term_operator: FxHashMap, + pub type_operator: FxHashMap, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OperatorBranchTypes { + pub left: TypeId, + pub right: TypeId, + pub result: TypeId, +} + +impl CheckedModule { + pub fn lookup_type(&self, id: TypeItemId) -> Option { + self.types.get(&id).copied() + } + + pub fn lookup_term(&self, id: TermItemId) -> Option { + self.terms.get(&id).copied() + } + + pub fn lookup_synonym(&self, id: TypeItemId) -> Option { + self.synonyms.get(&id).cloned() + } + + pub fn lookup_class(&self, id: TypeItemId) -> Option { + self.classes.get(&id).cloned() + } + + pub fn lookup_instance(&self, id: InstanceId) -> Option { + self.instances.get(&id).cloned() + } + + pub fn lookup_derived(&self, id: DeriveId) -> Option { + self.derived.get(&id).cloned() + } + + pub fn lookup_roles(&self, id: TypeItemId) -> Option> { + self.roles.get(&id).cloned() + } + + pub fn lookup_name(&self, name: Name) -> Option { + self.names.get(&name).copied() + } +} + +impl CheckedNodes { + pub fn lookup_expression(&self, id: lowering::ExpressionId) -> Option { + self.expressions.get(&id).copied() + } + + pub fn lookup_type(&self, id: lowering::TypeId) -> Option { + self.types.get(&id).copied() + } + + pub fn lookup_binder(&self, id: lowering::BinderId) -> Option { + self.binders.get(&id).copied() + } + + pub fn lookup_let(&self, id: lowering::LetBindingNameGroupId) -> Option { + self.lets.get(&id).copied() + } + + pub fn lookup_pun(&self, id: lowering::RecordPunId) -> Option { + self.puns.get(&id).copied() + } + + pub fn lookup_section(&self, id: lowering::ExpressionId) -> Option { + self.sections.get(&id).copied() + } + + pub fn lookup_type_operator( + &self, + id: lowering::TypeOperatorId, + ) -> Option { + self.type_operator.get(&id).copied() + } + + pub fn lookup_term_operator( + &self, + id: lowering::TermOperatorId, + ) -> Option { + self.term_operator.get(&id).copied() + } +} + +pub fn check_module(queries: &impl ExternalQueries, file_id: FileId) -> QueryResult { + let prim_id = queries.prim_id(); + if file_id == prim_id { check_prim(queries, prim_id) } else { check_source(queries, file_id) } +} + +fn check_source(queries: &impl ExternalQueries, file_id: FileId) -> QueryResult { + let mut state = state::CheckState::new(file_id); + let context = context::CheckContext::new(queries, file_id)?; + + source::check_type_items(&mut state, &context)?; + source::check_term_items(&mut state, &context)?; + + Ok(state.checked) +} + +fn check_prim(queries: &impl ExternalQueries, file_id: FileId) -> QueryResult { + let mut checked = CheckedModule::default(); + let resolved = queries.resolved(file_id)?; + + let lookup_type = |name: &str| { + let prim_type = resolved.exports.lookup_type(name); + prim_type.unwrap_or_else(|| unreachable!("invariant violated: {name} not in Prim")) + }; + + let lookup_class = |name: &str| { + let prim_class = resolved.exports.lookup_class(name); + prim_class.unwrap_or_else(|| unreachable!("invariant violated: {name} not in Prim")) + }; + + let type_core = { + let (file_id, item_id) = lookup_type("Type"); + queries.intern_type(Type::Constructor(file_id, item_id)) + }; + + let row_core = { + let (file_id, item_id) = lookup_type("Row"); + queries.intern_type(Type::Constructor(file_id, item_id)) + }; + + let constraint_core = { + let (file_id, item_id) = lookup_type("Constraint"); + queries.intern_type(Type::Constructor(file_id, item_id)) + }; + + let type_to_type = queries.intern_type(Type::Function(type_core, type_core)); + let function_kind = queries.intern_type(Type::Function(type_core, type_to_type)); + + let row_type = queries.intern_type(Type::Application(row_core, type_core)); + let record_kind = queries.intern_type(Type::Function(row_type, type_core)); + + let mut insert_type = |name: &str, id: TypeId| { + let (_, item_id) = lookup_type(name); + checked.types.insert(item_id, id); + }; + + insert_type("Type", type_core); + insert_type("Function", function_kind); + insert_type("Array", type_to_type); + insert_type("Record", record_kind); + insert_type("Number", type_core); + insert_type("Int", type_core); + insert_type("String", type_core); + insert_type("Char", type_core); + insert_type("Boolean", type_core); + insert_type("Constraint", type_core); + insert_type("Symbol", type_core); + insert_type("Row", type_to_type); + + let (_, partial_id) = lookup_class("Partial"); + checked.types.insert(partial_id, constraint_core); + + let mut insert_roles = |name: &str, roles: &[Role]| { + let (_, item_id) = lookup_type(name); + checked.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) +} diff --git a/compiler-core/checking2/src/safety.rs b/compiler-core/checking2/src/safety.rs new file mode 100644 index 00000000..faea58eb --- /dev/null +++ b/compiler-core/checking2/src/safety.rs @@ -0,0 +1,38 @@ +//! Safety utilities for implementing type checker rules. + +/// 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 = normalise(current_id); +/// if let Type::Application(function, _) = context.lookup_type(current_id) { +/// current_id = function; +/// } else { +/// break; +/// } +/// } +/// ``` +#[macro_export] +macro_rules! safe_loop { + ($($body:tt)*) => {{ + let mut fuel = 0u32; + loop { + if fuel >= $crate::safety::FUEL { + unreachable!("invariant violated: fuel exhausted"); + } + fuel += 1; + $($body)* + } + }}; +} + +pub use safe_loop; diff --git a/compiler-core/checking2/src/source.rs b/compiler-core/checking2/src/source.rs new file mode 100644 index 00000000..6f1fcfb6 --- /dev/null +++ b/compiler-core/checking2/src/source.rs @@ -0,0 +1,15 @@ +//! Implements syntax-driven checking rules for source files. + +pub mod binder; +pub mod operator; +pub mod roles; +pub mod synonym; +pub mod terms; +pub mod types; + +mod derive; +mod term_items; +mod type_items; + +pub use term_items::check_term_items; +pub use type_items::check_type_items; diff --git a/compiler-core/checking2/src/source/binder.rs b/compiler-core/checking2/src/source/binder.rs new file mode 100644 index 00000000..251aeb4b --- /dev/null +++ b/compiler-core/checking2/src/source/binder.rs @@ -0,0 +1,515 @@ +//! Implements syntax-driven checking rules for binders. + +use std::sync::Arc; + +use building_types::QueryResult; +use itertools::{EitherOrBoth, Itertools}; +use smol_str::SmolStr; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{RowField, RowType, Type, TypeId, normalise, toolkit, unification}; +use crate::error::{ErrorCrumb, ErrorKind}; +use crate::source::terms::application; +use crate::source::{operator, types}; +use crate::state::CheckState; + +#[derive(Copy, Clone, Debug)] +enum BinderMode { + Infer, + Check { expected_type: TypeId, elaborating: bool }, +} + +pub fn infer_binder( + state: &mut CheckState, + context: &CheckContext, + binder_id: lowering::BinderId, +) -> QueryResult +where + Q: ExternalQueries, +{ + state.with_error_crumb(ErrorCrumb::InferringBinder(binder_id), |state| { + binder_core(state, context, binder_id, BinderMode::Infer) + }) +} + +pub fn check_binder( + state: &mut CheckState, + context: &CheckContext, + binder_id: lowering::BinderId, + expected_type: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + state.with_error_crumb(ErrorCrumb::CheckingBinder(binder_id), |state| { + binder_core( + state, + context, + binder_id, + BinderMode::Check { expected_type, elaborating: true }, + ) + }) +} + +pub fn check_argument_binder( + state: &mut CheckState, + context: &CheckContext, + binder_id: lowering::BinderId, + expected_type: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + state.with_error_crumb(ErrorCrumb::CheckingBinder(binder_id), |state| { + binder_core( + state, + context, + binder_id, + BinderMode::Check { expected_type, elaborating: false }, + ) + }) +} + +pub fn requires_instantiation(context: &CheckContext, binder_id: lowering::BinderId) -> bool +where + Q: ExternalQueries, +{ + let Some(kind) = context.lowered.info.get_binder_kind(binder_id) else { + return false; + }; + match kind { + lowering::BinderKind::Variable { .. } | lowering::BinderKind::Wildcard => false, + lowering::BinderKind::Named { binder, .. } => { + binder.is_some_and(|id| requires_instantiation(context, id)) + } + lowering::BinderKind::Parenthesized { parenthesized } => { + parenthesized.is_some_and(|id| requires_instantiation(context, id)) + } + lowering::BinderKind::Typed { binder, .. } => { + binder.is_some_and(|id| requires_instantiation(context, id)) + } + _ => true, + } +} + +fn binder_core( + state: &mut CheckState, + context: &CheckContext, + binder_id: lowering::BinderId, + mode: BinderMode, +) -> QueryResult +where + Q: ExternalQueries, +{ + let unknown = context.unknown("missing binder"); + + let Some(kind) = context.lowered.info.get_binder_kind(binder_id) else { + return Ok(unknown); + }; + + match kind { + lowering::BinderKind::Typed { binder, type_ } => { + let Some(b) = binder else { return Ok(unknown) }; + let Some(t) = type_ else { return Ok(unknown) }; + + let (t, _) = types::infer_kind(state, context, *t)?; + match mode { + BinderMode::Check { elaborating: false, .. } => { + check_argument_binder(state, context, *b, t)?; + } + _ => { + check_binder(state, context, *b, t)?; + } + } + + if let BinderMode::Check { expected_type, elaborating } = mode { + subtype_for_mode(state, context, t, expected_type, elaborating)?; + } + + Ok(t) + } + + lowering::BinderKind::OperatorChain { .. } => { + let (_, inferred_type) = operator::infer_operator_chain(state, context, binder_id)?; + + if let BinderMode::Check { expected_type, elaborating } = mode { + subtype_for_mode(state, context, inferred_type, expected_type, elaborating)?; + } + + Ok(inferred_type) + } + + lowering::BinderKind::Integer { .. } => { + let inferred_type = context.prim.int; + + if let BinderMode::Check { expected_type, .. } = mode { + unification::unify(state, context, inferred_type, expected_type)?; + } + + Ok(inferred_type) + } + + lowering::BinderKind::Number { .. } => { + let inferred_type = context.prim.number; + + if let BinderMode::Check { expected_type, .. } = mode { + unification::unify(state, context, inferred_type, expected_type)?; + } + + Ok(inferred_type) + } + + lowering::BinderKind::Constructor { resolution, arguments } => { + let Some((file_id, term_id)) = resolution else { return Ok(unknown) }; + + let mut constructor_t = toolkit::lookup_file_term(state, context, *file_id, *term_id)?; + + let inferred_type = if arguments.is_empty() { + constructor_t = toolkit::instantiate_unifications(state, context, constructor_t)?; + toolkit::without_constraints(state, context, constructor_t)? + } else { + for &argument in arguments.iter() { + constructor_t = check_constructor_binder_application( + state, + context, + constructor_t, + argument, + )?; + } + constructor_t + }; + + if let BinderMode::Check { expected_type, elaborating } = mode { + subtype_for_mode(state, context, inferred_type, expected_type, elaborating)?; + Ok(expected_type) + } else { + Ok(inferred_type) + } + } + + lowering::BinderKind::Variable { .. } => { + let type_id = match mode { + BinderMode::Infer => state.fresh_unification(context.queries, context.prim.t), + BinderMode::Check { expected_type, .. } => expected_type, + }; + state.checked.nodes.binders.insert(binder_id, type_id); + Ok(type_id) + } + + lowering::BinderKind::Named { binder, .. } => { + let Some(binder) = binder else { return Ok(unknown) }; + + let type_id = match mode { + BinderMode::Infer => infer_binder(state, context, *binder)?, + BinderMode::Check { expected_type, elaborating } => { + if elaborating { + check_binder(state, context, *binder, expected_type)? + } else { + check_argument_binder(state, context, *binder, expected_type)? + } + } + }; + state.checked.nodes.binders.insert(binder_id, type_id); + + Ok(type_id) + } + + lowering::BinderKind::Wildcard => match mode { + BinderMode::Infer => Ok(state.fresh_unification(context.queries, context.prim.t)), + BinderMode::Check { expected_type, .. } => Ok(expected_type), + }, + + lowering::BinderKind::String { .. } => { + let inferred_type = context.prim.string; + + if let BinderMode::Check { expected_type, .. } = mode { + unification::unify(state, context, inferred_type, expected_type)?; + } + + Ok(inferred_type) + } + + lowering::BinderKind::Char { .. } => { + let inferred_type = context.prim.char; + + if let BinderMode::Check { expected_type, .. } = mode { + unification::unify(state, context, inferred_type, expected_type)?; + } + + Ok(inferred_type) + } + + lowering::BinderKind::Boolean { .. } => { + let inferred_type = context.prim.boolean; + + if let BinderMode::Check { expected_type, .. } = mode { + unification::unify(state, context, inferred_type, expected_type)?; + } + + Ok(inferred_type) + } + + lowering::BinderKind::Array { array } => { + let element_type = state.fresh_unification(context.queries, context.prim.t); + + for binder in array.iter() { + let binder_type = infer_binder(state, context, *binder)?; + unification::subtype_with::( + state, + context, + binder_type, + element_type, + )?; + } + + let array_type = context.intern_application(context.prim.array, element_type); + + if let BinderMode::Check { expected_type, elaborating } = mode { + subtype_for_mode(state, context, array_type, expected_type, elaborating)?; + } + + Ok(array_type) + } + + lowering::BinderKind::Record { record } => { + if let BinderMode::Check { expected_type, elaborating } = mode { + check_record_binder(state, context, binder_id, record, expected_type, elaborating) + } else { + infer_record_binder(state, context, binder_id, record) + } + } + + lowering::BinderKind::Parenthesized { parenthesized } => { + let Some(parenthesized) = parenthesized else { return Ok(unknown) }; + binder_core(state, context, *parenthesized, mode) + } + } +} + +fn subtype_for_mode( + state: &mut CheckState, + context: &CheckContext, + t1: TypeId, + t2: TypeId, + elaborating: bool, +) -> QueryResult +where + Q: ExternalQueries, +{ + if elaborating { + unification::subtype(state, context, t1, t2) + } else { + unification::subtype_with::(state, context, t1, t2) + } +} + +fn check_constructor_binder_application( + state: &mut CheckState, + context: &CheckContext, + constructor: TypeId, + binder_id: lowering::BinderId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let Some(application::ApplicationAnalysis { argument, result, .. }) = + application::analyse_function_application(state, context, constructor)? + else { + return Ok(context.unknown("invalid function application")); + }; + + check_binder(state, context, binder_id, argument)?; + + Ok(result) +} + +enum PatternItem { + Field(lowering::BinderId), + Pun(lowering::RecordPunId), +} + +fn collect_pattern_items(record: &[lowering::BinderRecordItem]) -> Vec<(SmolStr, PatternItem)> { + let mut items = vec![]; + for field in record { + match field { + lowering::BinderRecordItem::RecordField { name, value } => { + let Some(name) = name else { continue }; + let Some(value) = value else { continue }; + let name = SmolStr::clone(name); + items.push((name, PatternItem::Field(*value))); + } + lowering::BinderRecordItem::RecordPun { id, name } => { + let Some(name) = name else { continue }; + let name = SmolStr::clone(name); + items.push((name, PatternItem::Pun(*id))); + } + } + } + items.sort_by(|a, b| a.0.cmp(&b.0)); + items +} + +fn check_pattern_item( + state: &mut CheckState, + context: &CheckContext, + item: &PatternItem, + expected_type: TypeId, + elaborating: bool, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + match *item { + PatternItem::Field(binder_id) => { + if elaborating { + check_binder(state, context, binder_id, expected_type)?; + } else { + check_argument_binder(state, context, binder_id, expected_type)?; + } + } + PatternItem::Pun(pun_id) => { + state.checked.nodes.puns.insert(pun_id, expected_type); + } + } + Ok(()) +} + +fn infer_record_binder( + state: &mut CheckState, + context: &CheckContext, + binder_id: lowering::BinderId, + record: &[lowering::BinderRecordItem], +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut fields = vec![]; + + for field in record.iter() { + match field { + lowering::BinderRecordItem::RecordField { name, value } => { + let Some(name) = name else { continue }; + let Some(value) = value else { continue }; + + let label = SmolStr::clone(name); + let id = infer_binder(state, context, *value)?; + fields.push(RowField { label, id }); + } + lowering::BinderRecordItem::RecordPun { id, name } => { + let Some(name) = name else { continue }; + + let label = SmolStr::clone(name); + let field_type = state.fresh_unification(context.queries, context.prim.t); + + state.checked.nodes.puns.insert(*id, field_type); + fields.push(RowField { label, id: field_type }); + } + } + } + + let row_tail = state.fresh_unification(context.queries, context.prim.row_type); + let row_type = RowType::new(fields, Some(row_tail)); + let row_type_id = context.intern_row_type(row_type); + let row_type = context.intern_row(row_type_id); + let record_type = context.intern_application(context.prim.record, row_type); + + state.checked.nodes.binders.insert(binder_id, record_type); + Ok(record_type) +} + +fn extract_expected_row( + state: &mut CheckState, + context: &CheckContext, + expected_type: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let expected_type = normalise::expand(state, context, expected_type)?; + let Type::Application(function, argument) = context.lookup_type(expected_type) else { + return Ok(None); + }; + let function = normalise::expand(state, context, function)?; + if function != context.prim.record { + return Ok(None); + } + let row = normalise::expand(state, context, argument)?; + let Type::Row(row_id) = context.lookup_type(row) else { + return Ok(None); + }; + Ok(Some(context.lookup_row_type(row_id))) +} + +fn check_record_binder( + state: &mut CheckState, + context: &CheckContext, + binder_id: lowering::BinderId, + record: &[lowering::BinderRecordItem], + expected_type: TypeId, + elaborating: bool, +) -> QueryResult +where + Q: ExternalQueries, +{ + let pattern_items = collect_pattern_items(record); + + let expected_type = normalise::expand(state, context, expected_type)?; + + let expected_row = if let Type::Application(function, _) = context.lookup_type(expected_type) { + let function = normalise::expand(state, context, function)?; + if function == context.prim.record { + extract_expected_row(state, context, expected_type)? + } else { + None + } + } else { + None + }; + + let Some(expected_row) = expected_row else { + let result = infer_record_binder(state, context, binder_id, record)?; + unification::unify(state, context, result, expected_type)?; + return Ok(expected_type); + }; + + let mut extra_fields = vec![]; + + let patterns = pattern_items.iter(); + let expected = expected_row.fields.iter(); + + for pair in patterns.merge_join_by(expected, |pattern, expected| pattern.0.cmp(&expected.label)) + { + match pair { + EitherOrBoth::Both((_, item), expected) => { + check_pattern_item(state, context, item, expected.id, elaborating)?; + } + EitherOrBoth::Left((label, item)) => { + let id = state.fresh_unification(context.queries, context.prim.t); + check_pattern_item(state, context, item, id, elaborating)?; + + let label = SmolStr::clone(label); + extra_fields.push(RowField { label, id }); + } + EitherOrBoth::Right(_) => (), + } + } + + if !extra_fields.is_empty() { + if let Some(tail) = expected_row.tail { + let row_tail = state.fresh_unification(context.queries, context.prim.row_type); + + let row_type = RowType::new(extra_fields, Some(row_tail)); + let row_type_id = context.intern_row_type(row_type); + let row_type = context.intern_row(row_type_id); + + unification::unify(state, context, tail, row_type)?; + } else { + let labels = extra_fields.into_iter().map(|field| field.label); + state.insert_error(ErrorKind::AdditionalProperty { labels: Arc::from_iter(labels) }); + } + } + + state.checked.nodes.binders.insert(binder_id, expected_type); + Ok(expected_type) +} diff --git a/compiler-core/checking2/src/source/derive.rs b/compiler-core/checking2/src/source/derive.rs new file mode 100644 index 00000000..c056b87f --- /dev/null +++ b/compiler-core/checking2/src/source/derive.rs @@ -0,0 +1,139 @@ +pub mod head; +pub mod member; + +pub mod contravariant; +pub mod eq1_ord1; +pub mod eq_ord; +pub mod field; +pub mod foldable; +pub mod functor; +pub mod generic; +pub mod newtype; +pub mod tools; +pub mod traversable; +pub mod variance; + +use building_types::QueryResult; +use files::FileId; +use indexing::{TermItemId, TypeItemId}; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::TypeId; +use crate::source::derive::variance::VarianceConfig; +use crate::state::CheckState; + +#[derive(Clone, Copy)] +enum DeriveDispatch { + Eq, + Eq1, + Functor, + Bifunctor, + Contravariant, + Profunctor, + Foldable, + Bifoldable, + Traversable, + Bitraversable, + Newtype, + Generic, + Ord, + Ord1, + Unsupported, +} + +#[derive(Clone, Copy)] +pub(super) enum DeriveStrategy { + FieldConstraints { + data_file: FileId, + data_id: TypeItemId, + derived_type: TypeId, + class: (FileId, TypeItemId), + }, + DelegateConstraint { + derived_type: TypeId, + class: (FileId, TypeItemId), + }, + NewtypeDeriveConstraint { + delegate_constraint: TypeId, + }, + HeadOnly, + VarianceConstraints { + data_file: FileId, + data_id: TypeItemId, + derived_type: TypeId, + config: VarianceConfig, + }, +} + +pub struct DeriveHeadResult { + item_id: TermItemId, + constraints: Vec, + class_file: FileId, + class_id: TypeItemId, + arguments: Vec, + strategy: DeriveStrategy, +} + +fn derive_dispatch( + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, +) -> DeriveDispatch +where + Q: ExternalQueries, +{ + let class = Some((class_file, class_id)); + if class == context.known_types.eq { + DeriveDispatch::Eq + } else if class == context.known_types.eq1 { + DeriveDispatch::Eq1 + } else if class == context.known_types.functor { + DeriveDispatch::Functor + } else if class == context.known_types.bifunctor { + DeriveDispatch::Bifunctor + } else if class == context.known_types.contravariant { + DeriveDispatch::Contravariant + } else if class == context.known_types.profunctor { + DeriveDispatch::Profunctor + } else if class == context.known_types.foldable { + DeriveDispatch::Foldable + } else if class == context.known_types.bifoldable { + DeriveDispatch::Bifoldable + } else if class == context.known_types.traversable { + DeriveDispatch::Traversable + } else if class == context.known_types.bitraversable { + DeriveDispatch::Bitraversable + } else if class == context.known_types.newtype { + DeriveDispatch::Newtype + } else if class == context.known_types.generic { + DeriveDispatch::Generic + } else if class == context.known_types.ord { + DeriveDispatch::Ord + } else if class == context.known_types.ord1 { + DeriveDispatch::Ord1 + } else { + DeriveDispatch::Unsupported + } +} + +pub fn check_derive_declarations( + state: &mut CheckState, + context: &CheckContext, +) -> QueryResult> +where + Q: ExternalQueries, +{ + head::check_derive_declarations(state, context) +} + +pub fn check_derive_members( + state: &mut CheckState, + context: &CheckContext, + derives: &[DeriveHeadResult], +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + member::check_derive_members(state, context, derives) +} diff --git a/compiler-core/checking2/src/source/derive/contravariant.rs b/compiler-core/checking2/src/source/derive/contravariant.rs new file mode 100644 index 00000000..7404d4f2 --- /dev/null +++ b/compiler-core/checking2/src/source/derive/contravariant.rs @@ -0,0 +1,94 @@ +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::toolkit; +use crate::error::ErrorKind; +use crate::state::CheckState; + +use super::DeriveStrategy; +use super::variance::{Variance, VarianceConfig}; + +pub fn check_derive_contravariant( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [derived_type] = arguments else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file, + class_id, + expected: 1, + actual: arguments.len(), + }); + return Ok(None); + }; + + let Some((data_file, data_id)) = + toolkit::extract_type_constructor(state, context, *derived_type)? + else { + let type_message = state.pretty_id(context, *derived_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + }; + + let contravariant = Some((class_file, class_id)); + let config = VarianceConfig::Single((Variance::Contravariant, contravariant)); + + Ok(Some(DeriveStrategy::VarianceConstraints { + data_file, + data_id, + derived_type: *derived_type, + config, + })) +} + +pub fn check_derive_profunctor( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [derived_type] = arguments else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file, + class_id, + expected: 1, + actual: arguments.len(), + }); + return Ok(None); + }; + + let Some((data_file, data_id)) = + toolkit::extract_type_constructor(state, context, *derived_type)? + else { + let type_message = state.pretty_id(context, *derived_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + }; + + let contravariant = context.known_types.contravariant; + let functor = context.known_types.functor; + let config = VarianceConfig::Pair( + (Variance::Contravariant, contravariant), + (Variance::Covariant, functor), + ); + + Ok(Some(DeriveStrategy::VarianceConstraints { + data_file, + data_id, + derived_type: *derived_type, + config, + })) +} diff --git a/compiler-core/checking2/src/source/derive/eq1_ord1.rs b/compiler-core/checking2/src/source/derive/eq1_ord1.rs new file mode 100644 index 00000000..69040ee2 --- /dev/null +++ b/compiler-core/checking2/src/source/derive/eq1_ord1.rs @@ -0,0 +1,77 @@ +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::toolkit; +use crate::error::ErrorKind; +use crate::state::CheckState; + +use super::DeriveStrategy; + +pub fn check_derive_eq1( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let Some(eq) = context.known_types.eq else { + state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); + return Ok(None); + }; + + check_derive_delegate_constraint(state, context, class_file, class_id, arguments, eq) +} + +pub fn check_derive_ord1( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let Some(ord) = context.known_types.ord else { + state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); + return Ok(None); + }; + + check_derive_delegate_constraint(state, context, class_file, class_id, arguments, ord) +} + +fn check_derive_delegate_constraint( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], + class: (FileId, TypeItemId), +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [derived_type] = arguments else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file, + class_id, + expected: 1, + actual: arguments.len(), + }); + return Ok(None); + }; + + if toolkit::extract_type_constructor(state, context, *derived_type)?.is_none() { + let type_message = state.pretty_id(context, *derived_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + } + + Ok(Some(DeriveStrategy::DelegateConstraint { derived_type: *derived_type, class })) +} diff --git a/compiler-core/checking2/src/source/derive/eq_ord.rs b/compiler-core/checking2/src/source/derive/eq_ord.rs new file mode 100644 index 00000000..2eefb392 --- /dev/null +++ b/compiler-core/checking2/src/source/derive/eq_ord.rs @@ -0,0 +1,73 @@ +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::toolkit; +use crate::error::ErrorKind; +use crate::state::CheckState; + +use super::DeriveStrategy; + +pub fn check_derive_eq( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + check_derive_field_constraints(state, context, class_file, class_id, arguments) +} + +pub fn check_derive_ord( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + check_derive_field_constraints(state, context, class_file, class_id, arguments) +} + +fn check_derive_field_constraints( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [derived_type] = arguments else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file, + class_id, + expected: 1, + actual: arguments.len(), + }); + return Ok(None); + }; + + let Some((data_file, data_id)) = + toolkit::extract_type_constructor(state, context, *derived_type)? + else { + let type_message = state.pretty_id(context, *derived_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + }; + + Ok(Some(DeriveStrategy::FieldConstraints { + data_file, + data_id, + derived_type: *derived_type, + class: (class_file, class_id), + })) +} diff --git a/compiler-core/checking2/src/source/derive/field.rs b/compiler-core/checking2/src/source/derive/field.rs new file mode 100644 index 00000000..e83a05f7 --- /dev/null +++ b/compiler-core/checking2/src/source/derive/field.rs @@ -0,0 +1,132 @@ +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::substitute::SubstituteName; +use crate::core::{Type, TypeId, normalise, toolkit}; +use crate::state::CheckState; + +use super::tools; + +pub 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 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_type_application(state, context, derived_type)?; + + for constructor_id in tools::lookup_data_constructors(context, data_file, data_id)? { + let constructor_t = toolkit::lookup_file_term(state, context, data_file, constructor_id)?; + let field_types = + instantiate_constructor_fields(state, context, constructor_t, &arguments)?; + for field_type in field_types { + generate_constraint(state, context, field_type, class, class1)?; + } + } + + Ok(()) +} + +fn instantiate_constructor_fields( + state: &mut CheckState, + context: &CheckContext, + constructor_t: TypeId, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let mut current = constructor_t; + let mut arguments = arguments.iter().copied(); + + loop { + current = normalise::expand(state, context, current)?; + let Type::Forall(binder_id, inner) = context.lookup_type(current) else { + break; + }; + + let binder = context.lookup_forall_binder(binder_id); + let replacement = + arguments.next().unwrap_or_else(|| state.fresh_rigid(context.queries, binder.kind)); + current = SubstituteName::one(state, context, binder.name, replacement, inner)?; + } + + let toolkit::InspectFunction { arguments, .. } = + toolkit::inspect_function(state, context, current)?; + Ok(arguments) +} + +fn generate_constraint( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, + class: (FileId, TypeItemId), + class1: Option<(FileId, TypeItemId)>, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let type_id = normalise::expand(state, context, type_id)?; + + match context.lookup_type(type_id) { + Type::Application(function, argument) => { + let function = normalise::expand(state, context, function)?; + if function == context.prim.record { + generate_constraint(state, context, argument, class, class1)?; + } else if is_type_to_type_variable(state, context, function)? { + if let Some(class1) = class1 { + tools::emit_constraint(context, state, class1, function); + } + tools::emit_constraint(context, state, class, argument); + } else { + tools::emit_constraint(context, state, class, type_id); + } + } + Type::Row(row_id) => { + let row = context.lookup_row_type(row_id); + for field in row.fields.iter() { + generate_constraint(state, context, field.id, class, class1)?; + } + if let Some(tail) = row.tail { + generate_constraint(state, context, tail, class, class1)?; + } + } + _ => tools::emit_constraint(context, state, class, type_id), + } + + Ok(()) +} + +fn is_type_to_type_variable( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let type_id = normalise::expand(state, context, type_id)?; + let kind = match context.lookup_type(type_id) { + Type::Rigid(_, _, kind) => kind, + Type::Unification(unification_id) => state.unifications.get(unification_id).kind, + _ => return Ok(false), + }; + + Ok(normalise::normalise(state, context, kind)? == context.prim.type_to_type) +} diff --git a/compiler-core/checking2/src/source/derive/foldable.rs b/compiler-core/checking2/src/source/derive/foldable.rs new file mode 100644 index 00000000..8f1f382a --- /dev/null +++ b/compiler-core/checking2/src/source/derive/foldable.rs @@ -0,0 +1,91 @@ +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::toolkit; +use crate::error::ErrorKind; +use crate::state::CheckState; + +use super::DeriveStrategy; +use super::variance::{Variance, VarianceConfig}; + +pub fn check_derive_foldable( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [derived_type] = arguments else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file, + class_id, + expected: 1, + actual: arguments.len(), + }); + return Ok(None); + }; + + let Some((data_file, data_id)) = + toolkit::extract_type_constructor(state, context, *derived_type)? + else { + let type_message = state.pretty_id(context, *derived_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + }; + + let foldable = Some((class_file, class_id)); + let config = VarianceConfig::Single((Variance::Covariant, foldable)); + + Ok(Some(DeriveStrategy::VarianceConstraints { + data_file, + data_id, + derived_type: *derived_type, + config, + })) +} + +pub fn check_derive_bifoldable( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [derived_type] = arguments else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file, + class_id, + expected: 1, + actual: arguments.len(), + }); + return Ok(None); + }; + + let Some((data_file, data_id)) = + toolkit::extract_type_constructor(state, context, *derived_type)? + else { + let type_message = state.pretty_id(context, *derived_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + }; + + let wrapper = context.known_types.foldable; + let config = + VarianceConfig::Pair((Variance::Covariant, wrapper), (Variance::Covariant, wrapper)); + + Ok(Some(DeriveStrategy::VarianceConstraints { + data_file, + data_id, + derived_type: *derived_type, + config, + })) +} diff --git a/compiler-core/checking2/src/source/derive/functor.rs b/compiler-core/checking2/src/source/derive/functor.rs new file mode 100644 index 00000000..1674b099 --- /dev/null +++ b/compiler-core/checking2/src/source/derive/functor.rs @@ -0,0 +1,91 @@ +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::toolkit; +use crate::error::ErrorKind; +use crate::state::CheckState; + +use super::DeriveStrategy; +use super::variance::{Variance, VarianceConfig}; + +pub fn check_derive_functor( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [derived_type] = arguments else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file, + class_id, + expected: 1, + actual: arguments.len(), + }); + return Ok(None); + }; + + let Some((data_file, data_id)) = + toolkit::extract_type_constructor(state, context, *derived_type)? + else { + let type_message = state.pretty_id(context, *derived_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + }; + + let functor = Some((class_file, class_id)); + let config = VarianceConfig::Single((Variance::Covariant, functor)); + + Ok(Some(DeriveStrategy::VarianceConstraints { + data_file, + data_id, + derived_type: *derived_type, + config, + })) +} + +pub fn check_derive_bifunctor( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [derived_type] = arguments else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file, + class_id, + expected: 1, + actual: arguments.len(), + }); + return Ok(None); + }; + + let Some((data_file, data_id)) = + toolkit::extract_type_constructor(state, context, *derived_type)? + else { + let type_message = state.pretty_id(context, *derived_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + }; + + let wrapper = context.known_types.functor; + let config = + VarianceConfig::Pair((Variance::Covariant, wrapper), (Variance::Covariant, wrapper)); + + Ok(Some(DeriveStrategy::VarianceConstraints { + data_file, + data_id, + derived_type: *derived_type, + config, + })) +} diff --git a/compiler-core/checking2/src/source/derive/generic.rs b/compiler-core/checking2/src/source/derive/generic.rs new file mode 100644 index 00000000..460745fc --- /dev/null +++ b/compiler-core/checking2/src/source/derive/generic.rs @@ -0,0 +1,193 @@ +use building_types::QueryResult; +use files::FileId; +use indexing::TermItemId; +use lowering::StringKind; +use smol_str::SmolStr; + +use crate::context::{CheckContext, KnownGeneric}; +use crate::core::substitute::SubstituteName; +use crate::core::{Type, TypeId, normalise, toolkit, unification}; +use crate::error::ErrorKind; +use crate::state::CheckState; +use crate::{ExternalQueries, safe_loop}; + +use super::{DeriveStrategy, tools}; + +pub fn check_derive_generic( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: indexing::TypeItemId, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [derived_type, wildcard_type] = arguments else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file, + class_id, + expected: 2, + actual: arguments.len(), + }); + return Ok(None); + }; + + let Some((data_file, data_id)) = + toolkit::extract_type_constructor(state, context, *derived_type)? + else { + let type_message = state.pretty_id(context, *derived_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + }; + + let Some(ref known_generic) = context.known_generic else { + state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); + return Ok(None); + }; + + 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)?; + Ok(Some(DeriveStrategy::HeadOnly)) +} + +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 [rest @ .., last] = constructors else { + return Ok(known_generic.no_constructors); + }; + + let arguments = extract_all_applications(state, context, derived_type)?; + let mut rep = + build_generic_constructor(state, context, known_generic, data_file, &arguments, *last)?; + + for &constructor_id in rest.iter().rev() { + let constructor = build_generic_constructor( + state, + context, + known_generic, + data_file, + &arguments, + constructor_id, + )?; + let applied = context.intern_application(known_generic.sum, constructor); + rep = context.intern_application(applied, rep); + } + + Ok(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 = if data_file == context.id { + context.indexed.items[constructor_id] + .name + .clone() + .unwrap_or_else(|| SmolStr::new_static("")) + } else { + context.queries.indexed(data_file)?.items[constructor_id] + .name + .clone() + .unwrap_or_else(|| SmolStr::new_static("")) + }; + + let constructor_type = toolkit::lookup_file_term(state, context, data_file, constructor_id)?; + let field_types = instantiate_constructor_fields(state, context, constructor_type, arguments)?; + let fields_rep = build_fields_rep(context, known_generic, &field_types); + + let string_id = context.queries.intern_smol_str(constructor_name); + let name = context.queries.intern_type(Type::String(StringKind::String, string_id)); + let constructor = context.intern_application(known_generic.constructor, name); + Ok(context.intern_application(constructor, fields_rep)) +} + +fn build_fields_rep( + context: &CheckContext, + known_generic: &KnownGeneric, + field_types: &[TypeId], +) -> TypeId { + let [rest @ .., last] = field_types else { + return known_generic.no_arguments; + }; + + let mut rep = context.intern_application(known_generic.argument, *last); + for &field in rest.iter().rev() { + let argument = context.intern_application(known_generic.argument, field); + let product = context.intern_application(known_generic.product, argument); + rep = context.intern_application(product, rep); + } + rep +} + +fn extract_all_applications( + state: &mut CheckState, + context: &CheckContext, + mut applied_type: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let mut arguments = vec![]; + safe_loop! { + applied_type = normalise::expand(state, context, applied_type)?; + match context.lookup_type(applied_type) { + Type::Application(function, argument) | Type::KindApplication(function, argument) => { + arguments.push(argument); + applied_type = function; + } + _ => break, + } + } + arguments.reverse(); + Ok(arguments) +} + +fn instantiate_constructor_fields( + state: &mut CheckState, + context: &CheckContext, + constructor_type: TypeId, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let mut current = constructor_type; + let mut arguments = arguments.iter().copied(); + + safe_loop! { + current = normalise::expand(state, context, current)?; + let Type::Forall(binder_id, inner) = context.lookup_type(current) else { + break; + }; + + let binder = context.lookup_forall_binder(binder_id); + let argument_type = + arguments.next().unwrap_or_else(|| context.intern_rigid(binder.name, state.depth, binder.kind)); + current = SubstituteName::one(state, context, binder.name, argument_type, inner)?; + } + + let toolkit::InspectFunction { arguments, .. } = + toolkit::inspect_function(state, context, current)?; + Ok(arguments) +} diff --git a/compiler-core/checking2/src/source/derive/head.rs b/compiler-core/checking2/src/source/derive/head.rs new file mode 100644 index 00000000..2df9e480 --- /dev/null +++ b/compiler-core/checking2/src/source/derive/head.rs @@ -0,0 +1,294 @@ +use building_types::QueryResult; +use files::FileId; +use indexing::{TermItemId, TermItemKind, TypeItemId}; +use lowering::TermItemIr; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{ + CheckedInstance, Type, constraint, generalise, signature, toolkit, unification, zonk, +}; +use crate::error::{ErrorCrumb, ErrorKind}; +use crate::source::types; +use crate::state::CheckState; + +use super::{ + DeriveDispatch, DeriveHeadResult, contravariant, derive_dispatch, eq_ord, eq1_ord1, foldable, + functor, generic, newtype, traversable, +}; + +pub fn check_derive_declarations( + state: &mut CheckState, + context: &CheckContext, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let mut results = vec![]; + + for scc in &context.grouped.term_scc { + let items = scc.as_slice(); + + let items = items.iter().filter_map(|&item_id| { + let item = context.lowered.info.get_term_item(item_id)?; + let TermItemIr::Derive { newtype, constraints, resolution, arguments } = item else { + return None; + }; + let resolution = *resolution; + Some(CheckDeriveDeclaration { + item_id, + newtype: *newtype, + constraints, + resolution, + arguments, + }) + }); + + for item in items { + if let Some(result) = check_derive_declaration(state, context, item)? { + results.push(result); + } + } + } + + Ok(results) +} + +struct CheckDeriveDeclaration<'a> { + item_id: TermItemId, + newtype: bool, + constraints: &'a [lowering::TypeId], + resolution: Option<(FileId, TypeItemId)>, + arguments: &'a [lowering::TypeId], +} + +struct CheckDeriveDeclarationCore<'a> { + derive_id: indexing::DeriveId, + item_id: TermItemId, + newtype: bool, + class_file: FileId, + class_id: TypeItemId, + constraints: &'a [lowering::TypeId], + arguments: &'a [lowering::TypeId], +} + +fn check_derive_declaration( + state: &mut CheckState, + context: &CheckContext, + item: CheckDeriveDeclaration, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let CheckDeriveDeclaration { item_id, newtype, constraints, resolution, arguments } = item; + + let Some((class_file, class_id)) = resolution else { + return Ok(None); + }; + + let TermItemKind::Derive { id: derive_id } = context.indexed.items[item_id].kind else { + return Ok(None); + }; + + state.with_error_crumb(ErrorCrumb::TermDeclaration(item_id), |state| { + check_derive_declaration_core( + state, + context, + CheckDeriveDeclarationCore { + derive_id, + item_id, + newtype, + class_file, + class_id, + constraints, + arguments, + }, + ) + }) +} + +fn check_derive_declaration_core( + state: &mut CheckState, + context: &CheckContext, + item: CheckDeriveDeclarationCore, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let CheckDeriveDeclarationCore { + derive_id, + item_id, + newtype, + class_file, + class_id, + constraints, + arguments, + } = item; + + let class_kind = toolkit::lookup_file_type(state, context, class_file, class_id)?; + + let expected_kinds = { + let signature::DecomposedSignature { arguments, .. } = signature::decompose_signature( + state, + context, + class_kind, + signature::DecomposeSignatureMode::Full, + )?; + arguments + }; + + if expected_kinds.len() != arguments.len() { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file, + class_id, + expected: expected_kinds.len(), + actual: arguments.len(), + }); + } + + let mut class_type = context.queries.intern_type(Type::Constructor(class_file, class_id)); + let mut class_kind = class_kind; + let mut checked_arguments = Vec::with_capacity(arguments.len()); + + for &argument in arguments { + (class_type, class_kind) = + types::infer_application_kind(state, context, (class_type, class_kind), argument)?; + let (_, extracted_arguments) = + toolkit::extract_type_application(state, context, class_type)?; + if let Some(&checked_argument) = extracted_arguments.last() { + checked_arguments.push(checked_argument); + } + } + + unification::subtype(state, context, class_kind, context.prim.constraint)?; + + let mut checked_constraints = Vec::with_capacity(constraints.len()); + for &constraint in constraints { + let (constraint_type, _) = + types::check_kind(state, context, constraint, context.prim.constraint)?; + checked_constraints.push(constraint_type); + } + + let mut canonical = class_type; + for &constraint in checked_constraints.iter().rev() { + canonical = context.intern_constrained(constraint, canonical); + } + + constraint::instances::validate_rows(state, context, class_file, class_id, &checked_arguments)?; + + let strategy = if newtype { + newtype::check_derive_newtype(state, context, class_file, class_id, &checked_arguments)? + } else { + match derive_dispatch(context, class_file, class_id) { + DeriveDispatch::Eq => { + eq_ord::check_derive_eq(state, context, class_file, class_id, &checked_arguments)? + } + DeriveDispatch::Eq1 => eq1_ord1::check_derive_eq1( + state, + context, + class_file, + class_id, + &checked_arguments, + )?, + DeriveDispatch::Functor => functor::check_derive_functor( + state, + context, + class_file, + class_id, + &checked_arguments, + )?, + DeriveDispatch::Bifunctor => functor::check_derive_bifunctor( + state, + context, + class_file, + class_id, + &checked_arguments, + )?, + DeriveDispatch::Contravariant => contravariant::check_derive_contravariant( + state, + context, + class_file, + class_id, + &checked_arguments, + )?, + DeriveDispatch::Profunctor => contravariant::check_derive_profunctor( + state, + context, + class_file, + class_id, + &checked_arguments, + )?, + DeriveDispatch::Foldable => foldable::check_derive_foldable( + state, + context, + class_file, + class_id, + &checked_arguments, + )?, + DeriveDispatch::Bifoldable => foldable::check_derive_bifoldable( + state, + context, + class_file, + class_id, + &checked_arguments, + )?, + DeriveDispatch::Traversable => traversable::check_derive_traversable( + state, + context, + class_file, + class_id, + &checked_arguments, + )?, + DeriveDispatch::Bitraversable => traversable::check_derive_bitraversable( + state, + context, + class_file, + class_id, + &checked_arguments, + )?, + DeriveDispatch::Newtype => newtype::check_derive_newtype_class( + state, + context, + class_file, + class_id, + &checked_arguments, + )?, + DeriveDispatch::Generic => generic::check_derive_generic( + state, + context, + class_file, + class_id, + &checked_arguments, + )?, + DeriveDispatch::Ord => { + eq_ord::check_derive_ord(state, context, class_file, class_id, &checked_arguments)? + } + DeriveDispatch::Ord1 => eq1_ord1::check_derive_ord1( + state, + context, + class_file, + class_id, + &checked_arguments, + )?, + DeriveDispatch::Unsupported => { + state.insert_error(ErrorKind::CannotDeriveClass { class_file, class_id }); + None + } + } + }; + + let resolution = (class_file, class_id); + let canonical = zonk::zonk(state, context, canonical)?; + let canonical = generalise::generalise_implicit(state, context, canonical)?; + + state.checked.derived.insert(derive_id, CheckedInstance { resolution, canonical }); + + Ok(strategy.map(|strategy| DeriveHeadResult { + item_id, + constraints: checked_constraints, + class_file, + class_id, + arguments: checked_arguments, + strategy, + })) +} diff --git a/compiler-core/checking2/src/source/derive/member.rs b/compiler-core/checking2/src/source/derive/member.rs new file mode 100644 index 00000000..c9406aff --- /dev/null +++ b/compiler-core/checking2/src/source/derive/member.rs @@ -0,0 +1,122 @@ +use building_types::QueryResult; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::Type; +use crate::error::ErrorCrumb; +use crate::state::CheckState; + +use super::{DeriveHeadResult, DeriveStrategy, field, tools, variance}; + +pub fn check_derive_members( + state: &mut CheckState, + context: &CheckContext, + derives: &[DeriveHeadResult], +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for result in derives { + state.with_error_crumb(ErrorCrumb::TermDeclaration(result.item_id), |state| { + state.with_implication(|state| check_derive_member(state, context, result)) + })?; + } + Ok(()) +} + +fn check_derive_member( + state: &mut CheckState, + context: &CheckContext, + result: &DeriveHeadResult, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for &constraint in &result.constraints { + state.push_given(constraint); + } + + match result.strategy { + DeriveStrategy::FieldConstraints { data_file, data_id, derived_type, class } => { + tools::emit_superclass_constraints( + state, + context, + result.class_file, + result.class_id, + &result.arguments, + )?; + field::generate_field_constraints( + state, + context, + data_file, + data_id, + derived_type, + class, + )?; + tools::solve_and_report_constraints(state, context)?; + } + DeriveStrategy::DelegateConstraint { derived_type, class } => { + tools::emit_superclass_constraints( + state, + context, + result.class_file, + result.class_id, + &result.arguments, + )?; + generate_delegate_constraint(state, context, derived_type, class); + tools::solve_and_report_constraints(state, context)?; + } + DeriveStrategy::NewtypeDeriveConstraint { delegate_constraint } => { + state.push_wanted(delegate_constraint); + tools::solve_and_report_constraints(state, context)?; + } + DeriveStrategy::HeadOnly => { + tools::emit_superclass_constraints( + state, + context, + result.class_file, + result.class_id, + &result.arguments, + )?; + tools::solve_and_report_constraints(state, context)?; + } + DeriveStrategy::VarianceConstraints { data_file, data_id, derived_type, config } => { + tools::emit_superclass_constraints( + state, + context, + result.class_file, + result.class_id, + &result.arguments, + )?; + variance::generate_variance_constraints( + state, + context, + data_file, + data_id, + derived_type, + config, + )?; + tools::solve_and_report_constraints(state, context)?; + } + } + Ok(()) +} + +fn generate_delegate_constraint( + state: &mut CheckState, + context: &CheckContext, + derived_type: crate::core::TypeId, + class: (files::FileId, indexing::TypeItemId), +) where + Q: ExternalQueries, +{ + let skolem_type = state.fresh_rigid(context.queries, context.prim.t); + let applied_type = context.intern_application(derived_type, skolem_type); + + let class_type = context.queries.intern_type(Type::Constructor(class.0, class.1)); + let given_constraint = context.intern_application(class_type, skolem_type); + let wanted_constraint = context.intern_application(class_type, applied_type); + + state.push_given(given_constraint); + state.push_wanted(wanted_constraint); +} diff --git a/compiler-core/checking2/src/source/derive/newtype.rs b/compiler-core/checking2/src/source/derive/newtype.rs new file mode 100644 index 00000000..0af24263 --- /dev/null +++ b/compiler-core/checking2/src/source/derive/newtype.rs @@ -0,0 +1,147 @@ +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{Type, TypeId, normalise, toolkit, unification}; +use crate::error::ErrorKind; +use crate::state::CheckState; + +use super::DeriveStrategy; + +pub fn check_derive_newtype( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [preceding_arguments @ .., newtype_type] = arguments else { + return Ok(None); + }; + + let Some((newtype_file, newtype_id)) = + toolkit::extract_type_constructor(state, context, *newtype_type)? + else { + let type_message = state.pretty_id(context, *newtype_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + }; + + if newtype_file != context.id { + let type_message = state.pretty_id(context, *newtype_type)?; + state.insert_error(ErrorKind::NonLocalNewtype { type_message }); + return Ok(None); + } + + let Some(toolkit::NewtypeInner { inner, rigids }) = + toolkit::get_newtype_inner(state, context, newtype_file, newtype_id, *newtype_type)? + else { + let type_message = state.pretty_id(context, *newtype_type)?; + state.insert_error(ErrorKind::ExpectedNewtype { type_message }); + return Ok(None); + }; + + let inner = if let Some(inner) = try_peel_trailing_rigids(state, context, inner, &rigids)? { + inner + } else { + state.insert_error(ErrorKind::InvalidNewtypeDeriveSkolemArguments); + return Ok(None); + }; + + let inner = normalise::normalise(state, context, inner)?; + let class = context.queries.intern_type(Type::Constructor(class_file, class_id)); + + let mut delegate_constraint = preceding_arguments + .iter() + .copied() + .fold(class, |function, argument| context.intern_application(function, argument)); + + delegate_constraint = context.intern_application(delegate_constraint, inner); + Ok(Some(DeriveStrategy::NewtypeDeriveConstraint { delegate_constraint })) +} + +pub fn check_derive_newtype_class( + state: &mut CheckState, + context: &CheckContext, + _class_file: FileId, + _class_id: TypeItemId, + arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [newtype_type, inner_type] = arguments else { + return Ok(None); + }; + + let Some((newtype_file, newtype_id)) = + toolkit::extract_type_constructor(state, context, *newtype_type)? + else { + let type_message = state.pretty_id(context, *newtype_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + }; + + if newtype_file != context.id { + let type_message = state.pretty_id(context, *newtype_type)?; + state.insert_error(ErrorKind::NonLocalNewtype { type_message }); + return Ok(None); + } + + let Some(newtype_inner) = + toolkit::get_newtype_inner(state, context, newtype_file, newtype_id, *newtype_type)? + else { + let type_message = state.pretty_id(context, *newtype_type)?; + state.insert_error(ErrorKind::ExpectedNewtype { type_message }); + return Ok(None); + }; + + unification::unify(state, context, *inner_type, newtype_inner.inner)?; + + Ok(Some(DeriveStrategy::HeadOnly)) +} + +fn try_peel_trailing_rigids( + state: &mut CheckState, + context: &CheckContext, + mut type_id: TypeId, + rigids: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + if rigids.is_empty() { + return Ok(Some(type_id)); + } + + for &rigid in rigids.iter().rev() { + type_id = normalise::expand(state, context, type_id)?; + + match context.lookup_type(type_id) { + Type::Application(function, argument) | Type::KindApplication(function, argument) => { + let argument = normalise::expand(state, context, argument)?; + if argument != rigid { + return Ok(None); + } + type_id = function; + } + + Type::Function(argument, result) => { + let result = normalise::expand(state, context, result)?; + if result != rigid { + return Ok(None); + } + type_id = context.intern_application(context.prim.function, argument); + } + + _ => return Ok(None), + } + } + + Ok(Some(type_id)) +} diff --git a/compiler-core/checking2/src/source/derive/tools.rs b/compiler-core/checking2/src/source/derive/tools.rs new file mode 100644 index 00000000..462329da --- /dev/null +++ b/compiler-core/checking2/src/source/derive/tools.rs @@ -0,0 +1,88 @@ +use building_types::QueryResult; +use files::FileId; +use indexing::{TermItemId, TypeItemId}; +use rustc_hash::FxHashMap; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::substitute::SubstituteName; +use crate::core::{CheckedClass, Type, TypeId, toolkit}; +use crate::error::ErrorKind; +use crate::state::CheckState; + +pub fn emit_constraint( + context: &CheckContext, + state: &mut CheckState, + class: (FileId, TypeItemId), + argument: TypeId, +) where + Q: ExternalQueries, +{ + let class_t = context.queries.intern_type(Type::Constructor(class.0, class.1)); + state.push_wanted(context.intern_application(class_t, argument)); +} + +pub fn emit_superclass_constraints( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[TypeId], +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some(CheckedClass { type_parameters, superclasses, .. }) = + toolkit::lookup_file_class(state, context, class_file, class_id)? + else { + return Ok(()); + }; + + if superclasses.is_empty() { + return Ok(()); + } + + let mut bindings = FxHashMap::default(); + for (binder_id, &argument) in type_parameters.iter().zip(arguments.iter()) { + let binder = context.lookup_forall_binder(*binder_id); + bindings.insert(binder.name, argument); + } + + for superclass in superclasses { + let specialised = SubstituteName::many(state, context, &bindings, superclass)?; + state.push_wanted(specialised); + } + + Ok(()) +} + +pub fn lookup_data_constructors( + context: &CheckContext, + data_file: FileId, + data_id: TypeItemId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + if data_file == context.id { + Ok(context.indexed.pairs.data_constructors(data_id).collect()) + } else { + let indexed = context.queries.indexed(data_file)?; + Ok(indexed.pairs.data_constructors(data_id).collect()) + } +} + +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 = state.pretty_id(context, constraint)?; + state.insert_error(ErrorKind::NoInstanceFound { constraint }); + } + Ok(()) +} diff --git a/compiler-core/checking2/src/source/derive/traversable.rs b/compiler-core/checking2/src/source/derive/traversable.rs new file mode 100644 index 00000000..ec40f283 --- /dev/null +++ b/compiler-core/checking2/src/source/derive/traversable.rs @@ -0,0 +1,91 @@ +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::toolkit; +use crate::error::ErrorKind; +use crate::state::CheckState; + +use super::DeriveStrategy; +use super::variance::{Variance, VarianceConfig}; + +pub fn check_derive_traversable( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [derived_type] = arguments else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file, + class_id, + expected: 1, + actual: arguments.len(), + }); + return Ok(None); + }; + + let Some((data_file, data_id)) = + toolkit::extract_type_constructor(state, context, *derived_type)? + else { + let type_message = state.pretty_id(context, *derived_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + }; + + let traversable = Some((class_file, class_id)); + let config = VarianceConfig::Single((Variance::Covariant, traversable)); + + Ok(Some(DeriveStrategy::VarianceConstraints { + data_file, + data_id, + derived_type: *derived_type, + config, + })) +} + +pub fn check_derive_bitraversable( + state: &mut CheckState, + context: &CheckContext, + class_file: FileId, + class_id: TypeItemId, + arguments: &[crate::core::TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let [derived_type] = arguments else { + state.insert_error(ErrorKind::DeriveInvalidArity { + class_file, + class_id, + expected: 1, + actual: arguments.len(), + }); + return Ok(None); + }; + + let Some((data_file, data_id)) = + toolkit::extract_type_constructor(state, context, *derived_type)? + else { + let type_message = state.pretty_id(context, *derived_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + return Ok(None); + }; + + let wrapper = context.known_types.traversable; + let config = + VarianceConfig::Pair((Variance::Covariant, wrapper), (Variance::Covariant, wrapper)); + + Ok(Some(DeriveStrategy::VarianceConstraints { + data_file, + data_id, + derived_type: *derived_type, + config, + })) +} diff --git a/compiler-core/checking2/src/source/derive/variance.rs b/compiler-core/checking2/src/source/derive/variance.rs new file mode 100644 index 00000000..56d45b77 --- /dev/null +++ b/compiler-core/checking2/src/source/derive/variance.rs @@ -0,0 +1,277 @@ +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::substitute::SubstituteName; +use crate::core::{KindOrType, Name, Type, TypeId, normalise, toolkit}; +use crate::error::ErrorKind; +use crate::state::CheckState; + +use super::tools; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub(in crate::source) enum Variance { + Covariant, + Contravariant, +} + +impl Variance { + fn flip(self) -> Variance { + match self { + Variance::Covariant => Variance::Contravariant, + Variance::Contravariant => Variance::Covariant, + } + } +} + +type ParameterConfig = (Variance, Option<(FileId, TypeItemId)>); + +#[derive(Clone, Copy)] +pub(in crate::source) enum VarianceConfig { + Single(ParameterConfig), + Pair(ParameterConfig, ParameterConfig), +} + +struct DerivedParameter { + name: Name, + expected: Variance, + class: Option<(FileId, TypeItemId)>, +} + +enum DerivedRigids { + Invalid, + Single(DerivedParameter), + Pair(DerivedParameter, DerivedParameter), +} + +impl DerivedRigids { + fn get(&self, name: Name) -> Option<&DerivedParameter> { + self.iter().find(|parameter| parameter.name == name) + } + + fn iter(&self) -> impl Iterator { + let (first, second) = match self { + DerivedRigids::Invalid => (None, None), + DerivedRigids::Single(first) => (Some(first), None), + DerivedRigids::Pair(first, second) => (Some(first), Some(second)), + }; + first.into_iter().chain(second) + } +} + +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, +{ + for constructor_id in tools::lookup_data_constructors(context, data_file, data_id)? { + let constructor_t = toolkit::lookup_file_term(state, context, data_file, constructor_id)?; + let (fields, rigids) = + extract_fields_with_rigids(state, context, constructor_t, derived_type, config)?; + + for field in fields { + check_variance_field(state, context, field, Variance::Covariant, &rigids)?; + } + } + + Ok(()) +} + +fn extract_fields_with_rigids( + state: &mut CheckState, + context: &CheckContext, + constructor_t: TypeId, + derived_type: TypeId, + config: VarianceConfig, +) -> QueryResult<(Vec, DerivedRigids)> +where + Q: ExternalQueries, +{ + let (_, arguments) = toolkit::extract_type_application(state, context, derived_type)?; + let mut arguments = arguments.iter().copied(); + let mut current = constructor_t; + let mut names = vec![]; + + loop { + current = normalise::expand(state, context, current)?; + let Type::Forall(binder_id, inner) = context.lookup_type(current) else { + break; + }; + + let binder = context.lookup_forall_binder(binder_id); + let replacement = arguments.next().unwrap_or_else(|| { + let rigid = state.fresh_rigid(context.queries, binder.kind); + let Type::Rigid(name, _, _) = context.lookup_type(rigid) else { + unreachable!("fresh_rigid must create Type::Rigid") + }; + names.push(name); + rigid + }); + + current = SubstituteName::one(state, context, binder.name, replacement, inner)?; + } + + let rigids = match (config, &names[..]) { + (VarianceConfig::Single((expected, class)), [.., a]) => { + DerivedRigids::Single(DerivedParameter { name: *a, expected, class }) + } + ( + VarianceConfig::Pair((first_expected, first_class), (second_expected, second_class)), + [.., a, b], + ) => DerivedRigids::Pair( + DerivedParameter { name: *a, expected: first_expected, class: first_class }, + DerivedParameter { name: *b, expected: second_expected, class: second_class }, + ), + _ => { + let type_message = state.pretty_id(context, derived_type)?; + state.insert_error(ErrorKind::CannotDeriveForType { type_message }); + DerivedRigids::Invalid + } + }; + + let toolkit::InspectFunction { arguments: fields, .. } = + toolkit::inspect_function(state, context, current)?; + + Ok((fields, rigids)) +} + +fn check_variance_field( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, + variance: Variance, + rigids: &DerivedRigids, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let type_id = normalise::expand(state, context, type_id)?; + + match context.lookup_type(type_id) { + Type::Rigid(name, _, _) => { + if let Some(parameter) = rigids.get(name) { + emit_variance_error(state, context, type_id, variance, parameter.expected)?; + } + } + Type::Function(argument, result) => { + check_variance_field(state, context, argument, variance.flip(), rigids)?; + check_variance_field(state, context, result, variance, rigids)?; + } + Type::Application(function, argument) => { + let function = normalise::expand(state, context, function)?; + if function == context.prim.record { + check_variance_field(state, context, argument, variance, rigids)?; + } else { + for parameter in rigids.iter() { + if contains_rigid_name(state, context, argument, parameter.name)? { + emit_variance_error(state, context, type_id, variance, parameter.expected)?; + if variance == parameter.expected { + if let Some(class) = parameter.class { + tools::emit_constraint(context, state, class, function); + } else { + state.insert_error(ErrorKind::DeriveMissingFunctor); + } + } + } + } + check_variance_field(state, context, argument, variance, rigids)?; + } + } + Type::KindApplication(_, argument) => { + check_variance_field(state, context, argument, variance, rigids)?; + } + Type::Row(row_id) => { + let row = context.lookup_row_type(row_id); + for field in row.fields.iter() { + check_variance_field(state, context, field.id, variance, rigids)?; + } + if let Some(tail) = row.tail { + check_variance_field(state, context, tail, variance, rigids)?; + } + } + _ => {} + } + + Ok(()) +} + +fn emit_variance_error( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, + actual: Variance, + expected: Variance, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + if actual == expected { + return Ok(()); + } + + let type_message = state.pretty_id(context, type_id)?; + match actual { + Variance::Covariant => state.insert_error(ErrorKind::CovariantOccurrence { type_message }), + Variance::Contravariant => { + state.insert_error(ErrorKind::ContravariantOccurrence { type_message }) + } + } + Ok(()) +} + +fn contains_rigid_name( + state: &mut CheckState, + context: &CheckContext, + type_id: TypeId, + name: Name, +) -> QueryResult +where + Q: ExternalQueries, +{ + let type_id = normalise::expand(state, context, type_id)?; + Ok(match context.lookup_type(type_id) { + Type::Application(function, argument) | Type::KindApplication(function, argument) => { + contains_rigid_name(state, context, function, name)? + || contains_rigid_name(state, context, argument, name)? + } + Type::SynonymApplication(synonym_id) => { + let synonym = context.lookup_synonym(synonym_id); + let mut contains = false; + for application in synonym.arguments.iter() { + let argument = match application { + KindOrType::Kind(argument) | KindOrType::Type(argument) => *argument, + }; + contains |= contains_rigid_name(state, context, argument, name)?; + } + contains + } + Type::Forall(_, inner) | Type::Constrained(_, inner) | Type::Kinded(inner, _) => { + contains_rigid_name(state, context, inner, name)? + } + Type::Function(argument, result) => { + contains_rigid_name(state, context, argument, name)? + || contains_rigid_name(state, context, result, name)? + } + Type::Row(row_id) => { + let row = context.lookup_row_type(row_id); + let mut contains = false; + for field in row.fields.iter() { + contains |= contains_rigid_name(state, context, field.id, name)?; + } + if let Some(tail) = row.tail { + contains |= contains_rigid_name(state, context, tail, name)?; + } + contains + } + Type::Rigid(rigid_name, _, _) => rigid_name == name, + _ => false, + }) +} diff --git a/compiler-core/checking2/src/source/operator.rs b/compiler-core/checking2/src/source/operator.rs new file mode 100644 index 00000000..a39b8a89 --- /dev/null +++ b/compiler-core/checking2/src/source/operator.rs @@ -0,0 +1,550 @@ +//! Implements surface-generic operator chain inference. + +use building_types::QueryResult; +use files::FileId; +use indexing::TermItemId; +use lowering::IsElement; +use sugar::OperatorTree; +use sugar::bracketing::BracketingResult; + +use crate::context::CheckContext; +use crate::core::substitute::SubstituteName; +use crate::core::{Type, TypeId, normalise, toolkit, unification}; +use crate::source::{binder, synonym, terms, types}; +use crate::state::CheckState; +use crate::{ExternalQueries, OperatorBranchTypes, safe_loop}; + +#[derive(Copy, Clone, Debug)] +enum OperatorKindMode { + Infer, + Check { expected_type: TypeId }, +} + +pub fn infer_operator_chain( + state: &mut CheckState, + context: &CheckContext, + id: E, +) -> QueryResult<(E::Elaborated, TypeId)> +where + Q: ExternalQueries, + E: IsOperator, +{ + let unknown = (E::unknown_elaborated(context), context.unknown("invalid operator chain")); + + let Some(operator_tree) = E::lookup_tree(context, id) else { + return Ok(unknown); + }; + + let Ok(operator_tree) = operator_tree else { + return Ok(unknown); + }; + + traverse_operator_tree(state, context, operator_tree, OperatorKindMode::Infer) +} + +fn traverse_operator_tree( + state: &mut CheckState, + context: &CheckContext, + operator_tree: &OperatorTree, + mode: OperatorKindMode, +) -> QueryResult<(E::Elaborated, TypeId)> +where + Q: ExternalQueries, + E: IsOperator, +{ + let unknown_elaborated = E::unknown_elaborated(context); + + match operator_tree { + OperatorTree::Leaf(None) => match mode { + OperatorKindMode::Infer => { + Ok((unknown_elaborated, context.unknown("missing operator leaf"))) + } + OperatorKindMode::Check { expected_type } => Ok((unknown_elaborated, expected_type)), + }, + + OperatorTree::Leaf(Some(type_id)) => match mode { + OperatorKindMode::Infer => E::infer_surface(state, context, *type_id), + OperatorKindMode::Check { expected_type } => { + // Peel constraints from the expected type as givens, + // so operator arguments like `unsafePartial $ expr` + // can discharge constraints like Partial properly. + let expected_type = toolkit::collect_givens(state, context, expected_type)?; + E::check_surface(state, context, *type_id, expected_type) + } + }, + + OperatorTree::Branch(operator_id, children) => { + let Some((file_id, item_id)) = E::lookup_operator(context, *operator_id) else { + return match mode { + OperatorKindMode::Infer => { + Ok((unknown_elaborated, context.unknown("missing operator resolution"))) + } + OperatorKindMode::Check { expected_type } => { + Ok((unknown_elaborated, expected_type)) + } + }; + }; + + let operator_type = E::lookup_item(state, context, file_id, item_id)?; + + traverse_operator_branch( + state, + context, + *operator_id, + (file_id, item_id), + operator_type, + children, + mode, + ) + } + } +} + +fn traverse_operator_branch( + state: &mut CheckState, + context: &CheckContext, + operator_id: E::OperatorId, + operator: (FileId, E::ItemId), + operator_type: TypeId, + children: &[OperatorTree; 2], + mode: OperatorKindMode, +) -> QueryResult<(E::Elaborated, TypeId)> +where + Q: ExternalQueries, + E: IsOperator, +{ + let unknown_elaborated = E::unknown_elaborated(context); + let unknown = match mode { + OperatorKindMode::Infer => (unknown_elaborated, context.unknown("invalid operator kind")), + OperatorKindMode::Check { expected_type } => (unknown_elaborated, expected_type), + }; + + let operator_type = toolkit::instantiate_unifications(state, context, operator_type)?; + let operator_type = toolkit::collect_wanteds(state, context, operator_type)?; + + let Some((left_type, operator_type)) = + toolkit::decompose_function(state, context, operator_type)? + else { + return Ok(unknown); + }; + + let operator_type = toolkit::instantiate_unifications(state, context, operator_type)?; + let Some((right_type, result_type)) = + toolkit::decompose_function(state, context, operator_type)? + else { + return Ok(unknown); + }; + + E::record_branch_types(state, operator_id, left_type, right_type, result_type); + + if let OperatorKindMode::Check { expected_type } = mode { + // Peel constraints from the expected type as givens, + // so operator result constraints can be discharged. + let expected_type = toolkit::collect_givens(state, context, expected_type)?; + let _ = unification::subtype(state, context, result_type, expected_type)?; + } + + let check_left_right = |state: &mut CheckState| { + let [left_tree, right_tree] = children; + + let (left, _) = traverse_operator_tree( + state, + context, + left_tree, + OperatorKindMode::Check { expected_type: left_type }, + )?; + + let (right, _) = traverse_operator_tree( + state, + context, + right_tree, + OperatorKindMode::Check { expected_type: right_type }, + )?; + + Ok((left, right)) + }; + + let (left, right) = if E::should_defer_expansion(state, context, operator)? { + state.with_defer_expansion(check_left_right)? + } else { + check_left_right(state)? + }; + + E::build(state, context, operator, (left, right), (left_type, right_type), result_type) +} + +pub trait IsOperator: IsElement { + type ItemId: Copy; + type Elaborated: Copy; + + fn unknown_elaborated(context: &CheckContext) -> Self::Elaborated; + + fn lookup_tree<'q>( + context: &'q CheckContext, + id: Self, + ) -> Option<&'q BracketingResult>; + + fn lookup_operator( + context: &CheckContext, + id: Self::OperatorId, + ) -> Option<(FileId, Self::ItemId)>; + + fn lookup_item( + state: &mut CheckState, + context: &CheckContext, + file_id: FileId, + item_id: Self::ItemId, + ) -> QueryResult; + + fn infer_surface( + state: &mut CheckState, + context: &CheckContext, + id: Self, + ) -> QueryResult<(Self::Elaborated, TypeId)>; + + fn check_surface( + state: &mut CheckState, + context: &CheckContext, + id: Self, + expected: TypeId, + ) -> QueryResult<(Self::Elaborated, TypeId)>; + + fn should_defer_expansion( + _state: &CheckState, + _context: &CheckContext, + _operator: (FileId, Self::ItemId), + ) -> QueryResult { + Ok(false) + } + + fn build( + state: &mut CheckState, + context: &CheckContext, + operator: (FileId, Self::ItemId), + result_tree: (Self::Elaborated, Self::Elaborated), + argument_types: (TypeId, TypeId), + result_type: TypeId, + ) -> QueryResult<(Self::Elaborated, TypeId)>; + + fn record_branch_types( + state: &mut CheckState, + operator_id: Self::OperatorId, + left: TypeId, + right: TypeId, + result: TypeId, + ); +} + +impl IsOperator for lowering::ExpressionId { + type ItemId = TermItemId; + type Elaborated = (); + + fn unknown_elaborated(_context: &CheckContext) -> Self::Elaborated {} + + fn lookup_tree<'q>( + context: &'q CheckContext, + id: Self, + ) -> Option<&'q BracketingResult> { + context.bracketed.expressions.get(&id) + } + + fn lookup_operator( + context: &CheckContext, + id: Self::OperatorId, + ) -> Option<(FileId, Self::ItemId)> { + context.lowered.info.get_term_operator(id) + } + + fn lookup_item( + state: &mut CheckState, + context: &CheckContext, + file_id: FileId, + item_id: Self::ItemId, + ) -> QueryResult { + toolkit::lookup_file_term_operator(state, context, file_id, item_id) + } + + fn infer_surface( + state: &mut CheckState, + context: &CheckContext, + id: Self, + ) -> QueryResult<(Self::Elaborated, TypeId)> { + let inferred_type = terms::infer_expression(state, context, id)?; + Ok(((), inferred_type)) + } + + fn check_surface( + state: &mut CheckState, + context: &CheckContext, + id: Self, + expected: TypeId, + ) -> QueryResult<(Self::Elaborated, TypeId)> { + let checked_type = terms::check_expression(state, context, id, expected)?; + Ok(((), checked_type)) + } + + fn build( + _state: &mut CheckState, + _context: &CheckContext, + (_, _): (FileId, Self::ItemId), + (_, _): (Self::Elaborated, Self::Elaborated), + (_, _): (TypeId, TypeId), + result_type: TypeId, + ) -> QueryResult<(Self::Elaborated, TypeId)> { + Ok(((), result_type)) + } + + fn record_branch_types( + state: &mut CheckState, + operator_id: Self::OperatorId, + left: TypeId, + right: TypeId, + result: TypeId, + ) { + state + .checked + .nodes + .term_operator + .insert(operator_id, OperatorBranchTypes { left, right, result }); + } +} + +impl IsOperator for lowering::TypeId { + type ItemId = indexing::TypeItemId; + type Elaborated = TypeId; + + fn unknown_elaborated(context: &CheckContext) -> Self::Elaborated { + context.unknown("invalid operator chain") + } + + fn lookup_tree<'q>( + context: &'q CheckContext, + id: Self, + ) -> Option<&'q BracketingResult> { + context.bracketed.types.get(&id) + } + + fn lookup_operator( + context: &CheckContext, + id: Self::OperatorId, + ) -> Option<(FileId, Self::ItemId)> { + context.lowered.info.get_type_operator(id) + } + + fn lookup_item( + state: &mut CheckState, + context: &CheckContext, + file_id: FileId, + item_id: Self::ItemId, + ) -> QueryResult { + toolkit::lookup_file_type_operator(state, context, file_id, item_id) + } + + fn infer_surface( + state: &mut CheckState, + context: &CheckContext, + id: Self, + ) -> QueryResult<(Self::Elaborated, TypeId)> { + types::infer_kind(state, context, id) + } + + fn check_surface( + state: &mut CheckState, + context: &CheckContext, + id: Self, + expected: TypeId, + ) -> QueryResult<(Self::Elaborated, TypeId)> { + types::check_kind(state, context, id, expected) + } + + fn should_defer_expansion( + state: &CheckState, + context: &CheckContext, + (file_id, item_id): (FileId, Self::ItemId), + ) -> QueryResult { + let Some((target_file_id, target_item_id)) = + toolkit::resolve_type_operator_target(context, file_id, item_id)? + else { + return Ok(false); + }; + Ok(toolkit::lookup_file_synonym(state, context, target_file_id, target_item_id)?.is_some()) + } + + fn build( + state: &mut CheckState, + context: &CheckContext, + (file_id, item_id): (FileId, Self::ItemId), + (left, right): (Self::Elaborated, Self::Elaborated), + (left_kind, right_kind): (TypeId, TypeId), + result_kind: TypeId, + ) -> QueryResult<(Self::Elaborated, TypeId)> { + let Some((target_file_id, target_item_id)) = + toolkit::resolve_type_operator_target(context, file_id, item_id)? + else { + let unknown = context.unknown("missing operator resolution"); + return Ok((unknown, unknown)); + }; + + let operator_kind = toolkit::lookup_file_type(state, context, file_id, item_id)?; + + if let Some((elaborated_type, result_kind)) = synonym::try_check_synonym_application( + state, + context, + (target_file_id, target_item_id), + operator_kind, + &[(left, left_kind), (right, right_kind)], + )? { + let result_kind = normalise::normalise(state, context, result_kind)?; + return Ok((elaborated_type, result_kind)); + } + + let function_type = + context.queries.intern_type(Type::Constructor(target_file_id, target_item_id)); + let (function_type, function_kind) = + apply_type_argument(state, context, (function_type, operator_kind), left, left_kind)?; + let (elaborated_type, _) = + apply_type_argument(state, context, (function_type, function_kind), right, right_kind)?; + let result_kind = normalise::normalise(state, context, result_kind)?; + Ok((elaborated_type, result_kind)) + } + + fn record_branch_types( + state: &mut CheckState, + operator_id: Self::OperatorId, + left: TypeId, + right: TypeId, + result: TypeId, + ) { + state + .checked + .nodes + .type_operator + .insert(operator_id, OperatorBranchTypes { left, right, result }); + } +} + +impl IsOperator for lowering::BinderId { + type ItemId = TermItemId; + type Elaborated = (); + + fn unknown_elaborated(_context: &CheckContext) -> Self::Elaborated {} + + fn lookup_tree<'q>( + context: &'q CheckContext, + id: Self, + ) -> Option<&'q BracketingResult> { + context.bracketed.binders.get(&id) + } + + fn lookup_operator( + context: &CheckContext, + id: Self::OperatorId, + ) -> Option<(FileId, Self::ItemId)> { + context.lowered.info.get_term_operator(id) + } + + fn lookup_item( + state: &mut CheckState, + context: &CheckContext, + file_id: FileId, + item_id: Self::ItemId, + ) -> QueryResult { + toolkit::lookup_file_term_operator(state, context, file_id, item_id) + } + + fn infer_surface( + state: &mut CheckState, + context: &CheckContext, + id: Self, + ) -> QueryResult<(Self::Elaborated, TypeId)> { + let inferred_type = binder::infer_binder(state, context, id)?; + Ok(((), inferred_type)) + } + + fn check_surface( + state: &mut CheckState, + context: &CheckContext, + id: Self, + expected: TypeId, + ) -> QueryResult<(Self::Elaborated, TypeId)> { + let checked_type = binder::check_binder(state, context, id, expected)?; + Ok(((), checked_type)) + } + + fn build( + _state: &mut CheckState, + _context: &CheckContext, + (_, _): (FileId, Self::ItemId), + (_, _): (Self::Elaborated, Self::Elaborated), + (_, _): (TypeId, TypeId), + result_type: TypeId, + ) -> QueryResult<(Self::Elaborated, TypeId)> { + Ok(((), result_type)) + } + + fn record_branch_types( + state: &mut CheckState, + operator_id: Self::OperatorId, + left: TypeId, + right: TypeId, + result: TypeId, + ) { + state + .checked + .nodes + .term_operator + .insert(operator_id, OperatorBranchTypes { left, right, result }); + } +} + +fn apply_type_argument( + state: &mut CheckState, + context: &CheckContext, + (mut function_type, mut function_kind): (TypeId, TypeId), + argument_type: TypeId, + argument_kind: TypeId, +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + safe_loop! { + function_kind = normalise::normalise(state, context, function_kind)?; + match context.lookup_type(function_kind) { + Type::Function(expected_kind, result_kind) => { + unification::subtype(state, context, argument_kind, expected_kind)?; + let result_type = context.intern_application(function_type, argument_type); + let result_kind = normalise::normalise(state, context, result_kind)?; + return Ok((result_type, result_kind)); + } + + Type::Unification(unification_id) => { + let result_kind = state.fresh_unification(context.queries, context.prim.t); + let function_type = context.intern_function(argument_kind, result_kind); + unification::solve(state, context, function_kind, unification_id, function_type)?; + function_kind = result_kind; + } + + Type::Forall(binder_id, inner_kind) => { + let binder = context.lookup_forall_binder(binder_id); + let binder_kind = normalise::normalise(state, context, binder.kind)?; + let kind_argument = state.fresh_unification(context.queries, binder_kind); + function_type = context.intern_kind_application(function_type, kind_argument); + function_kind = + SubstituteName::one(state, context, binder.name, kind_argument, inner_kind)?; + } + + _ => { + let invalid_type = context.intern_application(function_type, argument_type); + toolkit::report_invalid_type_application( + state, + context, + function_type, + function_kind, + argument_type, + )?; + let unknown_kind = context.unknown("cannot apply operator type"); + return Ok((invalid_type, unknown_kind)); + } + } + } +} diff --git a/compiler-core/checking2/src/source/roles.rs b/compiler-core/checking2/src/source/roles.rs new file mode 100644 index 00000000..fdea9303 --- /dev/null +++ b/compiler-core/checking2/src/source/roles.rs @@ -0,0 +1,249 @@ +//! Implements role checking for source type declarations. + +use std::sync::Arc; + +use building_types::QueryResult; +use indexing::{TermItemId, TypeItemId}; +use rustc_hash::FxHashMap; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{ForallBinder, KindOrType, Name, Role, Type, TypeId, normalise, signature}; +use crate::error::{ErrorCrumb, ErrorKind}; +use crate::state::CheckState; + +pub fn infer_data_roles( + state: &mut CheckState, + context: &CheckContext, + parameters: &[ForallBinder], + constructors: &[(TermItemId, Vec)], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let mut inference = RoleInference::new(state, context, parameters); + + for (_, arguments) in constructors { + for &argument in arguments { + infer_roles(&mut inference, argument, RoleInferencePosition::ROOT)?; + } + } + + Ok(inference.finish()) +} + +pub fn count_kind_arguments( + state: &mut CheckState, + context: &CheckContext, + kind: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let signature::DecomposedSignature { arguments, .. } = signature::decompose_signature( + state, + context, + kind, + signature::DecomposeSignatureMode::Full, + )?; + Ok(arguments.len()) +} + +pub fn check_declared_roles( + state: &mut CheckState, + item_id: TypeItemId, + inferred: &[Role], + declared: &[lowering::Role], + is_foreign: bool, +) -> Arc<[Role]> { + let mut validated = inferred.to_vec(); + + for (index, (&inferred_role, declared_role)) in inferred.iter().zip(declared.iter()).enumerate() + { + let Some(declared_role) = lower_role(declared_role) else { + continue; + }; + + if is_foreign || declared_role >= inferred_role { + validated[index] = declared_role; + } else { + state.with_error_crumb(ErrorCrumb::TypeDeclaration(item_id), |state| { + state.insert_error(ErrorKind::InvalidRoleDeclaration { + index, + declared: declared_role, + inferred: inferred_role, + }); + }); + } + } + + Arc::from(validated) +} + +fn lower_role(role: &lowering::Role) -> Option { + match role { + lowering::Role::Nominal => Some(Role::Nominal), + lowering::Role::Representational => Some(Role::Representational), + lowering::Role::Phantom => Some(Role::Phantom), + lowering::Role::Unknown => None, + } +} + +#[derive(Copy, Clone, Debug)] +struct RoleInferencePosition { + under_constraint: bool, + variable_argument: bool, +} + +impl RoleInferencePosition { + const ROOT: RoleInferencePosition = + RoleInferencePosition { under_constraint: false, variable_argument: false }; + + fn child(self) -> RoleInferencePosition { + RoleInferencePosition { under_constraint: self.under_constraint, variable_argument: false } + } + + fn constrained_child(self) -> RoleInferencePosition { + RoleInferencePosition { under_constraint: true, variable_argument: false } + } + + fn argument_child(self, is_variable_argument: bool) -> RoleInferencePosition { + RoleInferencePosition { + under_constraint: self.under_constraint, + variable_argument: is_variable_argument, + } + } + + fn requires_nominal_role(self) -> bool { + self.under_constraint || self.variable_argument + } +} + +struct RoleInference<'a, 'q, Q> +where + Q: ExternalQueries, +{ + state: &'a mut CheckState, + context: &'a CheckContext<'q, Q>, + roles: Vec, + parameters: FxHashMap, +} + +impl<'a, 'q, Q> RoleInference<'a, 'q, Q> +where + Q: ExternalQueries, +{ + fn new( + state: &'a mut CheckState, + context: &'a CheckContext<'q, Q>, + parameters: &[ForallBinder], + ) -> RoleInference<'a, 'q, Q> { + let parameter_count = parameters.len(); + let parameters: FxHashMap = parameters + .iter() + .enumerate() + .map(|(index, parameter)| (parameter.name, index)) + .collect(); + + RoleInference { state, context, roles: vec![Role::Phantom; parameter_count], parameters } + } + + fn finish(self) -> Vec { + self.roles + } +} + +fn infer_roles<'a, 'q, Q>( + inference: &mut RoleInference<'a, 'q, Q>, + type_id: TypeId, + mode: RoleInferencePosition, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let type_id = normalise::expand(inference.state, inference.context, type_id)?; + + match inference.context.lookup_type(type_id) { + Type::Rigid(name, _, kind) => { + if let Some(index) = inference.parameters.get(&name) { + let role = if mode.requires_nominal_role() { + Role::Nominal + } else { + Role::Representational + }; + inference.roles[*index] = inference.roles[*index].max(role); + } + + infer_roles(inference, kind, mode.child())?; + } + + Type::Application(function, argument) => { + let function_id = normalise::expand(inference.state, inference.context, function)?; + + let is_type_variable = + matches!(inference.context.lookup_type(function_id), Type::Rigid(_, _, _)); + + infer_roles(inference, function, mode.child())?; + infer_roles(inference, argument, mode.argument_child(is_type_variable))?; + } + + Type::Constrained(constraint, inner) => { + let mode = mode.constrained_child(); + infer_roles(inference, constraint, mode)?; + infer_roles(inference, inner, mode)?; + } + + Type::Forall(binder_id, inner) => { + let binder = inference.context.lookup_forall_binder(binder_id); + + infer_roles(inference, binder.kind, mode.child())?; + infer_roles(inference, inner, mode.child())?; + } + + Type::Function(argument, result) => { + infer_roles(inference, argument, mode.child())?; + infer_roles(inference, result, mode.child())?; + } + + Type::KindApplication(function, argument) => { + infer_roles(inference, function, mode.child())?; + infer_roles(inference, argument, mode.child())?; + } + + Type::Kinded(inner, kind) => { + infer_roles(inference, inner, mode.child())?; + infer_roles(inference, kind, mode.child())?; + } + + Type::Row(row_id) => { + let row = inference.context.lookup_row_type(row_id); + + for field in row.fields.iter() { + infer_roles(inference, field.id, mode.child())?; + } + + if let Some(tail) = row.tail { + infer_roles(inference, tail, mode.child())?; + } + } + + Type::SynonymApplication(synonym_id) => { + let synonym = inference.context.lookup_synonym(synonym_id); + for application in synonym.arguments.iter() { + let argument = match application { + KindOrType::Kind(argument) | KindOrType::Type(argument) => *argument, + }; + infer_roles(inference, argument, mode.child())?; + } + } + + Type::Constructor(_, _) + | Type::Integer(_) + | Type::String(_, _) + | Type::Unification(_) + | Type::Free(_) + | Type::Unknown(_) => {} + } + + Ok(()) +} diff --git a/compiler-core/checking2/src/source/synonym.rs b/compiler-core/checking2/src/source/synonym.rs new file mode 100644 index 00000000..0d13ebf1 --- /dev/null +++ b/compiler-core/checking2/src/source/synonym.rs @@ -0,0 +1,418 @@ +//! Implements syntax-driven checking rules for synonym detection. +use std::ops::ControlFlow; +use std::sync::Arc; + +use building_types::QueryResult; +use files::FileId; +use indexing::TypeItemId; +use itertools::Itertools; + +use crate::context::CheckContext; +use crate::core::substitute::SubstituteName; +use crate::core::{ + CheckedSynonym, KindOrType, Saturation, Synonym, Type, TypeId, normalise, toolkit, unification, +}; +use crate::error::ErrorKind; +use crate::source::types; +use crate::state::CheckState; +use crate::{ExternalQueries, safe_loop}; + +#[derive(Debug, Clone, Copy)] +pub struct ParsedSynonym { + pub file_id: FileId, + pub type_id: TypeItemId, + pub kind: TypeId, + pub arity: usize, +} + +#[derive(Debug, Clone, Copy)] +enum Argument { + Syntax(lowering::TypeId), + Core(TypeId, TypeId), +} + +pub fn parse_synonym( + state: &mut CheckState, + context: &CheckContext, + function: lowering::TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let Some(lowering::TypeKind::Constructor { resolution }) = + context.lowered.info.get_type_kind(function) + else { + return Ok(None); + }; + + let Some((file_id, type_id)) = *resolution else { + return Ok(None); + }; + + let Some(CheckedSynonym { kind, parameters, .. }) = + toolkit::lookup_file_synonym(state, context, file_id, type_id)? + else { + return Ok(None); + }; + + let arity = parameters.len(); + Ok(Some(ParsedSynonym { file_id, type_id, kind, arity })) +} + +pub fn infer_synonym_constructor( + state: &mut CheckState, + context: &CheckContext, + synonym: ParsedSynonym, + id: lowering::TypeId, +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + let ParsedSynonym { file_id, type_id, kind, arity } = synonym; + + if arity > 0 { + if state.defer_expansion { + let synonym_type = context.queries.intern_type(Type::Constructor(file_id, type_id)); + return Ok((synonym_type, kind)); + } + + state.insert_error(ErrorKind::PartialSynonymApplication { id }); + let unknown = context.unknown("partial synonym application"); + return Ok((unknown, unknown)); + } + + if is_recursive_synonym(context, file_id, type_id)? { + state.insert_error(ErrorKind::RecursiveSynonymExpansion { file_id, type_id }); + } + + let synonym = Synonym { + saturation: Saturation::Full, + reference: (file_id, type_id), + arguments: Arc::default(), + }; + + let synonym_id = context.intern_synonym(synonym); + let synonym_type = context.intern_synonym_application(synonym_id); + + Ok((synonym_type, kind)) +} + +pub fn infer_synonym_application( + state: &mut CheckState, + context: &CheckContext, + id: lowering::TypeId, + synonym: ParsedSynonym, + arguments: &[lowering::TypeId], +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + let ParsedSynonym { file_id, type_id, kind, arity } = synonym; + + if arguments.len() < arity { + if state.defer_expansion { + return infer_partial_synonym_application( + state, + context, + (file_id, type_id), + kind, + arguments, + ); + } + + state.insert_error(ErrorKind::PartialSynonymApplication { id }); + let unknown = context.unknown("partial synonym application"); + return Ok((unknown, unknown)); + } + + let arguments = arguments.iter().copied().map(Argument::Syntax).collect_vec(); + + state.with_defer_expansion(|state| { + check_synonym_application_arguments(state, context, synonym, &arguments) + }) +} + +pub fn check_synonym_application( + state: &mut CheckState, + context: &CheckContext, + synonym: ParsedSynonym, + arguments: &[(TypeId, TypeId)], +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + let arguments = arguments + .iter() + .copied() + .map(|(type_id, kind_id)| Argument::Core(type_id, kind_id)) + .collect_vec(); + + state.with_defer_expansion(|state| { + check_synonym_application_arguments(state, context, synonym, &arguments) + }) +} + +pub fn try_check_synonym_application( + state: &mut CheckState, + context: &CheckContext, + (file_id, type_id): (FileId, TypeItemId), + kind: TypeId, + arguments: &[(TypeId, TypeId)], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let Some(CheckedSynonym { parameters, .. }) = + toolkit::lookup_file_synonym(state, context, file_id, type_id)? + else { + return Ok(None); + }; + + let synonym = ParsedSynonym { file_id, type_id, kind, arity: parameters.len() }; + let checked = check_synonym_application(state, context, synonym, arguments)?; + Ok(Some(checked)) +} + +fn check_synonym_application_arguments( + state: &mut CheckState, + context: &CheckContext, + synonym: ParsedSynonym, + arguments: &[Argument], +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + let ParsedSynonym { file_id, type_id, kind, arity } = synonym; + debug_assert!(arguments.len() >= arity); + + if is_recursive_synonym(context, file_id, type_id)? { + state.insert_error(ErrorKind::RecursiveSynonymExpansion { file_id, type_id }); + } + + let function_type = context.queries.intern_type(Type::Constructor(file_id, type_id)); + let (synonym_arguments, excess_arguments) = arguments.split_at(arity); + + let (applications, (_, synonym_kind)) = + collect_synonym_applications(state, context, (function_type, kind), synonym_arguments)?; + + let synonym = Synonym { + saturation: Saturation::Full, + reference: (file_id, type_id), + arguments: Arc::from(applications), + }; + + let synonym_id = context.intern_synonym(synonym); + let mut synonym_type = context.intern_synonym_application(synonym_id); + let mut synonym_kind = synonym_kind; + + for &argument in excess_arguments { + let mut applications = vec![]; + (synonym_type, synonym_kind) = apply_synonym_argument( + state, + context, + &mut applications, + (synonym_type, synonym_kind), + argument, + )?; + } + + Ok((synonym_type, synonym_kind)) +} + +fn infer_partial_synonym_application( + state: &mut CheckState, + context: &CheckContext, + (file_id, type_id): (FileId, TypeItemId), + kind: TypeId, + arguments: &[lowering::TypeId], +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + let function_type = context.queries.intern_type(Type::Constructor(file_id, type_id)); + let arguments = arguments.iter().copied().map(Argument::Syntax).collect_vec(); + + let (applications, (_, synonym_kind)) = + collect_synonym_applications(state, context, (function_type, kind), &arguments)?; + + let synonym = Synonym { + saturation: Saturation::Partial, + reference: (file_id, type_id), + arguments: Arc::from(applications), + }; + + let synonym_id = context.intern_synonym(synonym); + let synonym_type = context.intern_synonym_application(synonym_id); + + Ok((synonym_type, synonym_kind)) +} + +fn collect_synonym_applications( + state: &mut CheckState, + context: &CheckContext, + mut function: (TypeId, TypeId), + arguments: &[Argument], +) -> QueryResult<(Vec, (TypeId, TypeId))> +where + Q: ExternalQueries, +{ + let mut applications = vec![]; + + for &argument in arguments { + function = apply_synonym_argument(state, context, &mut applications, function, argument)?; + } + + Ok((applications, function)) +} + +fn apply_synonym_argument_step( + state: &mut CheckState, + context: &CheckContext, + applications: &mut Vec, + (function_type, function_kind): (TypeId, TypeId), + argument: Argument, +) -> QueryResult> +where + Q: ExternalQueries, +{ + match context.lookup_type(function_kind) { + Type::Function(expected_kind, result_kind) => { + let argument_type = match argument { + Argument::Syntax(argument_id) => { + let (argument_type, _) = + types::check_kind(state, context, argument_id, expected_kind)?; + argument_type + } + Argument::Core(argument_type, argument_kind) => { + unification::subtype(state, context, argument_kind, expected_kind)?; + argument_type + } + }; + + let result_type = context.intern_application(function_type, argument_type); + let result_kind = normalise::normalise(state, context, result_kind)?; + + applications.push(KindOrType::Type(argument_type)); + Ok(ControlFlow::Break((result_type, result_kind))) + } + + Type::Unification(unification_id) => { + let argument_kind = state.fresh_unification(context.queries, context.prim.t); + let result_kind = state.fresh_unification(context.queries, context.prim.t); + + let function = context.intern_function(argument_kind, result_kind); + unification::solve(state, context, function_kind, unification_id, function)?; + + let argument_type = match argument { + Argument::Syntax(argument_id) => { + let (argument_type, _) = + types::check_kind(state, context, argument_id, argument_kind)?; + argument_type + } + Argument::Core(argument_type, checked_kind) => { + unification::subtype(state, context, checked_kind, argument_kind)?; + argument_type + } + }; + + let result_type = context.intern_application(function_type, argument_type); + let result_kind = normalise::normalise(state, context, result_kind)?; + + applications.push(KindOrType::Type(argument_type)); + Ok(ControlFlow::Break((result_type, result_kind))) + } + + Type::Forall(binder_id, inner_kind) => { + let binder = context.lookup_forall_binder(binder_id); + let binder_kind = normalise::normalise(state, context, binder.kind)?; + + let kind_argument = state.fresh_unification(context.queries, binder_kind); + + let function_type = context.intern_kind_application(function_type, kind_argument); + let function_kind = + SubstituteName::one(state, context, binder.name, kind_argument, inner_kind)?; + + applications.push(KindOrType::Kind(kind_argument)); + Ok(ControlFlow::Continue((function_type, function_kind))) + } + + _ => { + let argument_type = match argument { + Argument::Syntax(argument_id) => { + let (argument_type, _) = types::infer_kind(state, context, argument_id)?; + argument_type + } + Argument::Core(argument_type, _) => argument_type, + }; + + let invalid_type = context.intern_application(function_type, argument_type); + let unknown_kind = context.unknown("cannot apply synonym type"); + + toolkit::report_invalid_type_application( + state, + context, + function_type, + function_kind, + argument_type, + )?; + + applications.push(KindOrType::Type(argument_type)); + Ok(ControlFlow::Break((invalid_type, unknown_kind))) + } + } +} + +fn apply_synonym_argument( + state: &mut CheckState, + context: &CheckContext, + applications: &mut Vec, + (mut function_type, mut function_kind): (TypeId, TypeId), + argument: Argument, +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + safe_loop! { + function_kind = normalise::normalise(state, context, function_kind)?; + match apply_synonym_argument_step( + state, + context, + applications, + (function_type, function_kind), + argument, + )? { + ControlFlow::Break(result) => break Ok(result), + ControlFlow::Continue((next_type, next_kind)) => { + function_type = next_type; + function_kind = next_kind; + } + }; + } +} + +fn is_recursive_synonym( + context: &CheckContext, + file_id: FileId, + type_id: TypeItemId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let has_recursive_error = |grouped: &lowering::GroupedModule| { + grouped.cycle_errors.iter().any(|error| { + if let lowering::LoweringError::RecursiveSynonym(recursive) = error { + recursive.group.contains(&type_id) + } else { + false + } + }) + }; + + let grouped = if file_id == context.id { + Arc::clone(&context.grouped) + } else { + context.queries.grouped(file_id)? + }; + + Ok(has_recursive_error(&grouped)) +} diff --git a/compiler-core/checking2/src/source/term_items.rs b/compiler-core/checking2/src/source/term_items.rs new file mode 100644 index 00000000..414ca952 --- /dev/null +++ b/compiler-core/checking2/src/source/term_items.rs @@ -0,0 +1,768 @@ +use std::mem; + +use building_types::QueryResult; +use files::FileId; +use indexing::{TermItemId, TermItemKind, TypeItemId}; +use lowering::TermItemIr; +use rustc_hash::FxHashMap; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::substitute::{NameToType, SubstituteName}; +use crate::core::{ + CheckedInstance, Type, TypeId, constraint, generalise, normalise, signature, toolkit, + unification, zonk, +}; +use crate::error::{ErrorCrumb, ErrorKind}; +use crate::source::terms::equations; +use crate::source::{derive, types}; +use crate::state::CheckState; + +#[derive(Default)] +struct TermSccState { + operator: Vec, + value_groups: FxHashMap, +} + +enum PendingValueGroup { + Checked { residuals: Vec }, + Inferred { residuals: Vec }, +} + +pub fn check_term_items(state: &mut CheckState, context: &CheckContext) -> QueryResult<()> +where + Q: ExternalQueries, +{ + check_instance_declarations(state, context)?; + let derive_results = derive::check_derive_declarations(state, context)?; + check_value_groups(state, context)?; + check_instance_members(state, context)?; + derive::check_derive_members(state, context, &derive_results)?; + Ok(()) +} + +pub fn check_instance_declarations( + state: &mut CheckState, + context: &CheckContext, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for scc in &context.grouped.term_scc { + let items = scc.as_slice(); + + let items = items.iter().filter_map(|&item_id| { + let item = context.lowered.info.get_term_item(item_id)?; + let TermItemIr::Instance { constraints, resolution, arguments, .. } = item else { + return None; + }; + let resolution = *resolution; + Some(CheckInstanceDeclaration { item_id, constraints, resolution, arguments }) + }); + + for item in items { + check_instance_declaration(state, context, item)?; + } + } + + Ok(()) +} + +struct CheckInstanceDeclaration<'a> { + item_id: TermItemId, + constraints: &'a [lowering::TypeId], + resolution: Option<(FileId, TypeItemId)>, + arguments: &'a [lowering::TypeId], +} + +fn check_instance_declaration( + state: &mut CheckState, + context: &CheckContext, + item: CheckInstanceDeclaration, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let CheckInstanceDeclaration { item_id, constraints, resolution, arguments } = item; + + let Some((class_file, class_id)) = resolution else { + return Ok(()); + }; + + let TermItemKind::Instance { id: instance_id } = context.indexed.items[item_id].kind else { + return Ok(()); + }; + + let class_kind = toolkit::lookup_file_type(state, context, class_file, class_id)?; + + let expected_kinds = { + let signature::DecomposedSignature { arguments, .. } = signature::decompose_signature( + state, + context, + class_kind, + signature::DecomposeSignatureMode::Full, + )?; + arguments + }; + + 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 class_type = context.queries.intern_type(Type::Constructor(class_file, class_id)); + let mut class_kind = class_kind; + let mut checked_arguments = Vec::with_capacity(arguments.len()); + + for &argument in arguments { + (class_type, class_kind) = + types::infer_application_kind(state, context, (class_type, class_kind), argument)?; + let (_, extracted_arguments) = + toolkit::extract_type_application(state, context, class_type)?; + if let Some(&checked_argument) = extracted_arguments.last() { + checked_arguments.push(checked_argument); + } + } + + unification::subtype(state, context, class_kind, context.prim.constraint)?; + + let mut checked_constraints = Vec::with_capacity(constraints.len()); + for &constraint in constraints { + let (constraint_type, _) = + types::check_kind(state, context, constraint, context.prim.constraint)?; + checked_constraints.push(constraint_type); + } + + let mut canonical = class_type; + for &constraint in checked_constraints.iter().rev() { + canonical = context.intern_constrained(constraint, canonical); + } + + constraint::instances::validate_rows(state, context, class_file, class_id, &checked_arguments)?; + + let resolution = (class_file, class_id); + let canonical = zonk::zonk(state, context, canonical)?; + let canonical = generalise::generalise_implicit(state, context, canonical)?; + + state.checked.instances.insert(instance_id, CheckedInstance { resolution, canonical }); + + Ok(()) +} + +fn check_value_groups(state: &mut CheckState, context: &CheckContext) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for scc in &context.grouped.term_scc { + let items = scc.as_slice(); + + for &item in items { + check_term_signature(state, context, item)?; + } + + if scc.is_recursive() { + prepare_binding_group(state, context, items); + } + + let mut term_scc = TermSccState::default(); + + for &item in items { + check_term_equation(state, context, &mut term_scc, item)?; + } + + finalise_term_binding_group(state, context, &mut term_scc, items)?; + finalise_term_operators(state, context, &mut term_scc)?; + } + + Ok(()) +} + +fn check_instance_members(state: &mut CheckState, context: &CheckContext) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for scc in &context.grouped.term_scc { + for &item_id in scc.as_slice() { + let Some(TermItemIr::Instance { members, resolution, .. }) = + context.lowered.info.get_term_item(item_id) + else { + continue; + }; + + let Some((class_file, class_id)) = *resolution else { + continue; + }; + + let TermItemKind::Instance { id: instance_id } = context.indexed.items[item_id].kind + else { + continue; + }; + + let Some(instance) = state.checked.lookup_instance(instance_id) else { + continue; + }; + + let Some(instance) = toolkit::decompose_instance(state, context, &instance)? else { + continue; + }; + + for member in members.iter() { + check_instance_member_group( + state, + context, + item_id, + member, + (class_file, class_id), + &instance, + )?; + } + } + } + + Ok(()) +} + +fn check_instance_member_group( + state: &mut CheckState, + context: &CheckContext, + instance_item_id: TermItemId, + member: &lowering::InstanceMemberGroup, + (class_file, class_id): (FileId, TypeItemId), + instance: &toolkit::DecomposedInstance, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + state.with_error_crumb(ErrorCrumb::TermDeclaration(instance_item_id), |state| { + state.with_implication(|state| { + check_instance_member_group_core( + state, + context, + member, + (class_file, class_id), + instance, + ) + }) + }) +} + +fn check_instance_member_group_core( + state: &mut CheckState, + context: &CheckContext, + member: &lowering::InstanceMemberGroup, + (class_file, class_id): (FileId, TypeItemId), + instance: &toolkit::DecomposedInstance, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let FreshenedInstanceRigids { + constraints: instance_constraints, + arguments: instance_arguments, + substitution, + } = freshen_instance_rigids(state, context, instance)?; + + state.with_implicit(context, &substitution, |state| { + for &constraint in &instance_constraints { + state.push_given(constraint); + } + + let class_member_type = if let Some((member_file, member_id)) = member.resolution { + Some(toolkit::lookup_file_term(state, context, member_file, member_id)?) + } else { + None + }; + + let class_member_type = if let Some(class_member_type) = class_member_type { + specialise_class_member_type( + state, + context, + class_member_type, + (class_file, class_id), + &instance_arguments, + )? + } else { + None + }; + + let residuals = if let Some(signature_id) = member.signature { + let (signature_member_type, _) = + types::check_kind(state, context, signature_id, context.prim.t)?; + + if let Some(class_member_type) = class_member_type { + let unified = + unification::unify(state, context, signature_member_type, class_member_type)?; + if !unified { + let expected = state.pretty_id(context, class_member_type)?; + let actual = state.pretty_id(context, signature_member_type)?; + state.insert_error(ErrorKind::InstanceMemberTypeMismatch { expected, actual }); + } + } + + let equation_set = equations::analyse_equation_set( + state, + context, + equations::EquationMode::Check { + origin: equations::EquationTypeOrigin::Explicit(signature_id), + expected_type: signature_member_type, + }, + &member.equations, + )?; + let exhaustiveness = equations::compute_equation_exhaustiveness( + state, + context, + &equation_set, + &member.equations, + )?; + state.report_exhaustiveness(context, exhaustiveness); + state.solve_constraints(context)? + } else if let Some(specialised_type) = class_member_type { + let equation_set = equations::analyse_equation_set( + state, + context, + equations::EquationMode::Check { + origin: equations::EquationTypeOrigin::Implicit, + expected_type: specialised_type, + }, + &member.equations, + )?; + let exhaustiveness = equations::compute_equation_exhaustiveness( + state, + context, + &equation_set, + &member.equations, + )?; + state.report_exhaustiveness(context, exhaustiveness); + state.solve_constraints(context)? + } else { + vec![] + }; + + for residual in residuals { + let constraint = state.pretty_id(context, residual)?; + state.insert_error(ErrorKind::NoInstanceFound { constraint }); + } + + Ok(()) + }) +} + +struct FreshenedInstanceRigids { + constraints: Vec, + arguments: Vec, + substitution: NameToType, +} + +fn freshen_instance_rigids( + state: &mut CheckState, + context: &CheckContext, + instance: &toolkit::DecomposedInstance, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut substitution = NameToType::default(); + + for binder in &instance.binders { + let kind = SubstituteName::many(state, context, &substitution, binder.kind)?; + let rigid = state.fresh_rigid(context.queries, kind); + substitution.insert(binder.name, rigid); + } + + let constraints = instance + .constraints + .iter() + .map(|&constraint| SubstituteName::many(state, context, &substitution, constraint)) + .collect::>>()?; + + let arguments = instance + .arguments + .iter() + .map(|&argument| SubstituteName::many(state, context, &substitution, argument)) + .collect::>>()?; + + Ok(FreshenedInstanceRigids { constraints, arguments, substitution }) +} + +fn specialise_class_member_type( + state: &mut CheckState, + context: &CheckContext, + class_member_type: TypeId, + (class_file, class_id): (FileId, TypeItemId), + instance_arguments: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let Some(class_info) = toolkit::lookup_file_class(state, context, class_file, class_id)? else { + return Ok(None); + }; + + let signature::DecomposedSignature { binders, constraints, arguments, result } = + signature::decompose_signature( + state, + context, + class_member_type, + signature::DecomposeSignatureMode::Full, + )?; + + let class_binder_count = class_info.kind_binders.len() + class_info.type_parameters.len(); + if binders.len() < class_binder_count + || instance_arguments.len() != class_info.type_parameters.len() + { + return Ok(None); + } + + let (class_binders, member_binders) = binders.split_at(class_binder_count); + let (kind_binders, type_binders) = class_binders.split_at(class_info.kind_binders.len()); + + let mut bindings = NameToType::default(); + for binder in kind_binders { + let replacement = state.fresh_unification(context.queries, binder.kind); + bindings.insert(binder.name, replacement); + } + for (binder, &argument) in type_binders.iter().zip(instance_arguments) { + bindings.insert(binder.name, argument); + } + + let constraints = substitute_normalise_types(state, context, &bindings, &constraints)?; + let arguments = substitute_normalise_types(state, context, &bindings, &arguments)?; + let mut constrained = substitute_normalise_type(state, context, &bindings, result)?; + + let mut constraints = constraints.into_iter(); + let Some(constraint) = constraints.next() else { + return Ok(None); + }; + + let Some(application) = constraint::constraint_application(state, context, constraint)? else { + return Ok(None); + }; + + if (application.file_id, application.item_id) != (class_file, class_id) { + return Ok(None); + } + + constrained = context.intern_function_chain(&arguments, constrained); + for constraint in constraints.rev() { + constrained = context.intern_constrained(constraint, constrained); + } + + for binder in member_binders.iter().rev() { + let binder_id = context.intern_forall_binder(*binder); + constrained = context.intern_forall(binder_id, constrained); + } + + Ok(Some(constrained)) +} + +fn substitute_normalise_type( + state: &mut CheckState, + context: &CheckContext, + bindings: &NameToType, + type_id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let type_id = SubstituteName::many(state, context, bindings, type_id)?; + normalise::normalise(state, context, type_id) +} + +fn substitute_normalise_types( + state: &mut CheckState, + context: &CheckContext, + bindings: &NameToType, + types: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + types + .iter() + .map(|&type_id| substitute_normalise_type(state, context, bindings, type_id)) + .collect() +} + +fn prepare_binding_group(state: &mut CheckState, context: &CheckContext, items: &[TermItemId]) +where + Q: ExternalQueries, +{ + for &item_id in items { + if state.checked.terms.contains_key(&item_id) { + continue; + } + let t = state.fresh_unification(context.queries, context.prim.t); + state.checked.terms.insert(item_id, t); + } +} + +fn check_term_signature( + state: &mut CheckState, + context: &CheckContext, + item_id: TermItemId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some(item) = context.lowered.info.get_term_item(item_id) else { + return Ok(()); + }; + + match item { + TermItemIr::Foreign { signature } => { + let Some(signature) = signature else { return Ok(()) }; + check_signature_type(state, context, item_id, *signature)?; + } + TermItemIr::ValueGroup { signature, .. } => { + let Some(signature) = signature else { return Ok(()) }; + check_signature_type(state, context, item_id, *signature)?; + } + _ => (), + } + + Ok(()) +} + +fn check_signature_type( + state: &mut CheckState, + context: &CheckContext, + item_id: TermItemId, + signature: lowering::TypeId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let (checked_kind, _) = types::check_kind(state, context, signature, context.prim.t)?; + state.checked.terms.insert(item_id, checked_kind); + Ok(()) +} + +fn check_term_equation( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TermSccState, + item_id: TermItemId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some(item) = context.lowered.info.get_term_item(item_id) else { + return Ok(()); + }; + + match item { + TermItemIr::Operator { resolution, .. } => { + check_term_operator(state, context, scc, item_id, *resolution)?; + } + TermItemIr::ValueGroup { signature, equations } => { + let pending = state.with_implication(|state| { + check_value_group(state, context, item_id, *signature, equations) + })?; + if let Some(pending) = pending { + scc.value_groups.insert(item_id, pending); + } + } + _ => (), + } + + Ok(()) +} + +fn check_value_group( + state: &mut CheckState, + context: &CheckContext, + item_id: TermItemId, + signature: Option, + equations: &[lowering::Equation], +) -> QueryResult> +where + Q: ExternalQueries, +{ + state.with_error_crumb(ErrorCrumb::TermDeclaration(item_id), |state| { + check_value_group_core(state, context, item_id, signature, equations) + }) +} + +fn check_value_group_core( + state: &mut CheckState, + context: &CheckContext, + item_id: TermItemId, + signature: Option, + equations: &[lowering::Equation], +) -> QueryResult> +where + Q: ExternalQueries, +{ + if let Some(signature_id) = signature + && let Some(signature_type) = state.checked.lookup_term(item_id) + { + let residuals = + check_value_group_core_check(state, context, signature_id, signature_type, equations)?; + Ok(Some(PendingValueGroup::Checked { residuals })) + } else { + let residuals = check_value_group_core_infer(state, context, item_id, equations)?; + Ok(Some(PendingValueGroup::Inferred { residuals })) + } +} + +fn check_value_group_core_check( + state: &mut CheckState, + context: &CheckContext, + signature_id: lowering::TypeId, + signature_type: TypeId, + equations: &[lowering::Equation], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let equation_set = equations::analyse_equation_set( + state, + context, + equations::EquationMode::Check { + origin: equations::EquationTypeOrigin::Explicit(signature_id), + expected_type: signature_type, + }, + equations, + )?; + let exhaustiveness = + equations::compute_equation_exhaustiveness(state, context, &equation_set, equations)?; + state.report_exhaustiveness(context, exhaustiveness); + state.solve_constraints(context) +} + +fn check_value_group_core_infer( + state: &mut CheckState, + context: &CheckContext, + item_id: TermItemId, + equations: &[lowering::Equation], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let group_type = state.fresh_unification(context.queries, context.prim.t); + state.checked.terms.insert(item_id, group_type); + let equation_set = equations::analyse_equation_set( + state, + context, + equations::EquationMode::Infer { group_type }, + equations, + )?; + let exhaustiveness = + equations::compute_equation_exhaustiveness(state, context, &equation_set, equations)?; + state.report_exhaustiveness(context, exhaustiveness); + + state.solve_constraints(context) +} + +fn finalise_term_binding_group( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TermSccState, + items: &[TermItemId], +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + struct Pending { + t: TypeId, + unsolved: Vec, + errors: generalise::ConstraintErrors, + } + + let mut pending = vec![]; + + for &item_id in items { + let Some(t) = state.checked.terms.get(&item_id).copied() else { + continue; + }; + + let group = scc.value_groups.remove(&item_id); + let t = zonk::zonk(state, context, t)?; + + let mut errors = generalise::ConstraintErrors::default(); + + let t = match group { + Some(PendingValueGroup::Checked { residuals }) => { + errors.unsatisfied.extend(residuals); + t + } + Some(PendingValueGroup::Inferred { residuals }) => { + generalise::insert_inferred_residuals(state, context, t, residuals, &mut errors)? + } + None => t, + }; + + let t = zonk::zonk(state, context, t)?; + let unsolved = generalise::unsolved_unifications(state, context, t)?; + + pending.push((item_id, Pending { t, unsolved, errors })); + } + + for (item_id, Pending { t, unsolved, errors }) in pending { + let t = generalise::generalise_unsolved(state, context, t, &unsolved)?; + state.checked.terms.insert(item_id, t); + + for constraint in errors.ambiguous { + let constraint = zonk::zonk(state, context, constraint)?; + let constraint = state.pretty_id(context, constraint)?; + state.insert_error(ErrorKind::AmbiguousConstraint { constraint }); + } + for constraint in errors.unsatisfied { + let constraint = zonk::zonk(state, context, constraint)?; + let constraint = state.pretty_id(context, constraint)?; + state.insert_error(ErrorKind::NoInstanceFound { constraint }); + } + } + + Ok(()) +} + +fn check_term_operator( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TermSccState, + item_id: TermItemId, + resolution: Option<(FileId, TermItemId)>, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some((file_id, term_id)) = resolution else { return Ok(()) }; + let operator_type = toolkit::lookup_file_term_operator(state, context, file_id, term_id)?; + + if let Some(item_type) = state.checked.lookup_term(item_id) { + unification::subtype(state, context, operator_type, item_type)?; + } else { + state.checked.terms.insert(item_id, operator_type); + } + + scc.operator.push(item_id); + + Ok(()) +} + +fn finalise_term_operators( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TermSccState, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for item_id in mem::take(&mut scc.operator) { + let Some(t) = state.checked.terms.get(&item_id).copied() else { + continue; + }; + + if !toolkit::is_binary_operator_type(state, context, t)? { + let kind_message = state.pretty_id(context, t)?; + state.insert_error(ErrorKind::InvalidTypeOperator { kind_message }); + } + } + + Ok(()) +} diff --git a/compiler-core/checking2/src/source/terms.rs b/compiler-core/checking2/src/source/terms.rs new file mode 100644 index 00000000..fa26db90 --- /dev/null +++ b/compiler-core/checking2/src/source/terms.rs @@ -0,0 +1,330 @@ +pub mod application; +pub mod collections; +pub mod equations; +pub mod form_ado; +pub mod form_do; +pub mod form_let; +pub mod forms; + +use building_types::QueryResult; +use itertools::Itertools; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{TypeId, normalise, toolkit, unification}; +use crate::error::ErrorCrumb; +use crate::source::{operator, types}; +use crate::state::CheckState; + +/// Checks the type of an expression. +pub fn check_expression( + state: &mut CheckState, + context: &CheckContext, + expression: lowering::ExpressionId, + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + state.with_error_crumb(ErrorCrumb::CheckingExpression(expression), |state| { + check_expression_quiet(state, context, expression, expected) + }) +} + +fn check_expression_quiet( + state: &mut CheckState, + context: &CheckContext, + expression: lowering::ExpressionId, + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let expected = normalise::normalise(state, context, expected)?; + let expected = toolkit::skolemise_forall(state, context, expected)?; + let expected = toolkit::collect_givens(state, context, expected)?; + if let Some(section_result) = context.sectioned.expressions.get(&expression) { + check_sectioned_expression(state, context, expression, section_result, expected) + } else { + check_expression_core(state, context, expression, expected) + } +} + +fn check_sectioned_expression( + state: &mut CheckState, + context: &CheckContext, + expression: lowering::ExpressionId, + section_result: &sugar::SectionResult, + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut current = expected; + let mut parameters = vec![]; + + for §ion_id in section_result.iter() { + let decomposed = toolkit::decompose_function(state, context, current)?; + if let Some((argument_type, result_type)) = decomposed { + state.checked.nodes.sections.insert(section_id, argument_type); + parameters.push(argument_type); + current = result_type; + } else { + let parameter = state.fresh_unification(context.queries, context.prim.t); + state.checked.nodes.sections.insert(section_id, parameter); + parameters.push(parameter); + } + } + + let result_type = infer_expression_core(state, context, expression)?; + let result_type = toolkit::instantiate_constrained(state, context, result_type)?; + + unification::subtype(state, context, result_type, current)?; + + let function_type = context.intern_function_chain(¶meters, result_type); + Ok(function_type) +} + +fn check_expression_core( + state: &mut CheckState, + context: &CheckContext, + expression: lowering::ExpressionId, + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let unknown = context.unknown("missing expression"); + + let Some(kind) = context.lowered.info.get_expression_kind(expression) else { + return Ok(unknown); + }; + + match kind { + lowering::ExpressionKind::Lambda { binders, expression } => { + forms::check_lambda(state, context, binders, *expression, expected) + } + lowering::ExpressionKind::IfThenElse { if_, then, else_ } => { + forms::check_if_then_else(state, context, *if_, *then, *else_, expected) + } + lowering::ExpressionKind::CaseOf { trunk, branches } => { + forms::check_case_of(state, context, trunk, branches, expected) + } + lowering::ExpressionKind::LetIn { bindings, expression } => { + forms::check_let_in(state, context, bindings, *expression, expected) + } + lowering::ExpressionKind::Parenthesized { parenthesized } => { + let Some(parenthesized) = parenthesized else { return Ok(unknown) }; + check_expression(state, context, *parenthesized, expected) + } + lowering::ExpressionKind::Array { array } => { + collections::check_array(state, context, array, expected) + } + lowering::ExpressionKind::Record { record } => { + collections::check_record(state, context, record, expected) + } + _ => { + let inferred = infer_expression_quiet(state, context, expression)?; + let inferred = toolkit::instantiate_constrained(state, context, inferred)?; + unification::subtype(state, context, inferred, expected)?; + Ok(inferred) + } + } +} + +/// Infers the type of an expression. +pub fn infer_expression( + state: &mut CheckState, + context: &CheckContext, + expression: lowering::ExpressionId, +) -> QueryResult +where + Q: ExternalQueries, +{ + state.with_error_crumb(ErrorCrumb::InferringExpression(expression), |state| { + infer_expression_quiet(state, context, expression) + }) +} + +fn infer_expression_quiet( + state: &mut CheckState, + context: &CheckContext, + expression: lowering::ExpressionId, +) -> QueryResult +where + Q: ExternalQueries, +{ + if let Some(section_result) = context.sectioned.expressions.get(&expression) { + infer_sectioned_expression(state, context, expression, section_result) + } else { + infer_expression_core(state, context, expression) + } +} + +fn infer_sectioned_expression( + state: &mut CheckState, + context: &CheckContext, + expression: lowering::ExpressionId, + section_result: &sugar::SectionResult, +) -> QueryResult +where + Q: ExternalQueries, +{ + let parameter_types = section_result.iter().map(|§ion_id| { + let parameter_type = state.fresh_unification(context.queries, context.prim.t); + state.checked.nodes.sections.insert(section_id, parameter_type); + parameter_type + }); + + let parameter_types = parameter_types.collect_vec(); + + let result_type = infer_expression_core(state, context, expression)?; + let result_type = toolkit::instantiate_constrained(state, context, result_type)?; + + Ok(context.intern_function_chain(¶meter_types, result_type)) +} + +fn infer_expression_core( + state: &mut CheckState, + context: &CheckContext, + expression: lowering::ExpressionId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let unknown = context.unknown("missing expression"); + + let Some(kind) = context.lowered.info.get_expression_kind(expression) else { + return Ok(unknown); + }; + + match kind { + lowering::ExpressionKind::Typed { expression, type_ } => { + let Some(e) = expression else { return Ok(unknown) }; + let Some(t) = type_ else { return Ok(unknown) }; + + let (t, _) = types::infer_kind(state, context, *t)?; + check_expression(state, context, *e, t)?; + + Ok(t) + } + + lowering::ExpressionKind::OperatorChain { .. } => { + let (_, inferred_type) = operator::infer_operator_chain(state, context, expression)?; + Ok(inferred_type) + } + + lowering::ExpressionKind::InfixChain { head, tail } => { + let Some(head) = *head else { return Ok(unknown) }; + application::infer_infix_chain(state, context, head, tail) + } + + lowering::ExpressionKind::Negate { negate, expression } => { + let Some(negate) = negate else { return Ok(unknown) }; + let Some(expression) = expression else { return Ok(unknown) }; + + let negate_type = toolkit::lookup_term_variable(state, context, *negate)?; + application::check_function_term_application(state, context, negate_type, *expression) + } + + lowering::ExpressionKind::Application { function, arguments } => { + let Some(function) = function else { return Ok(unknown) }; + + let mut function_t = infer_expression(state, context, *function)?; + + for argument in arguments.iter() { + function_t = + application::check_function_application(state, context, function_t, argument)?; + } + + Ok(function_t) + } + + lowering::ExpressionKind::IfThenElse { if_, then, else_ } => { + forms::infer_if_then_else(state, context, *if_, *then, *else_) + } + + lowering::ExpressionKind::LetIn { bindings, expression } => { + form_let::check_let_chunks(state, context, bindings)?; + + let Some(expression) = expression else { return Ok(unknown) }; + + infer_expression(state, context, *expression) + } + + lowering::ExpressionKind::Lambda { binders, expression } => { + forms::infer_lambda(state, context, binders, *expression) + } + + lowering::ExpressionKind::CaseOf { trunk, branches } => { + forms::infer_case_of(state, context, trunk, branches) + } + + lowering::ExpressionKind::Do { bind, discard, statements } => { + form_do::infer_do(state, context, *bind, *discard, statements) + } + + lowering::ExpressionKind::Ado { map, apply, pure, statements, expression } => { + form_ado::infer_ado(state, context, *map, *apply, *pure, statements, *expression) + } + + lowering::ExpressionKind::Constructor { resolution } => { + let Some((file_id, term_id)) = resolution else { return Ok(unknown) }; + toolkit::lookup_file_term(state, context, *file_id, *term_id) + } + + lowering::ExpressionKind::Variable { resolution } => { + let Some(resolution) = *resolution else { return Ok(unknown) }; + toolkit::lookup_term_variable(state, context, resolution) + } + + lowering::ExpressionKind::OperatorName { resolution } => { + let Some((file_id, term_id)) = resolution else { return Ok(unknown) }; + toolkit::lookup_file_term(state, context, *file_id, *term_id) + } + + lowering::ExpressionKind::Section => { + if let Some(type_id) = state.checked.nodes.lookup_section(expression) { + Ok(type_id) + } else { + Ok(unknown) + } + } + + lowering::ExpressionKind::Hole => Ok(unknown), + + lowering::ExpressionKind::String => Ok(context.prim.string), + + lowering::ExpressionKind::Char => Ok(context.prim.char), + + lowering::ExpressionKind::Boolean { .. } => Ok(context.prim.boolean), + + lowering::ExpressionKind::Integer => Ok(context.prim.int), + + lowering::ExpressionKind::Number => Ok(context.prim.number), + + lowering::ExpressionKind::Array { array } => { + collections::infer_array(state, context, array) + } + + lowering::ExpressionKind::Record { record } => { + collections::infer_record(state, context, record) + } + + lowering::ExpressionKind::Parenthesized { parenthesized } => { + let Some(parenthesized) = parenthesized else { return Ok(unknown) }; + infer_expression(state, context, *parenthesized) + } + + lowering::ExpressionKind::RecordAccess { record, labels } => { + let Some(record) = *record else { return Ok(unknown) }; + let Some(labels) = labels else { return Ok(unknown) }; + collections::infer_record_access(state, context, record, labels) + } + + lowering::ExpressionKind::RecordUpdate { record, updates } => { + let Some(record) = *record else { return Ok(unknown) }; + collections::infer_record_update(state, context, record, updates) + } + } +} diff --git a/compiler-core/checking2/src/source/terms/application.rs b/compiler-core/checking2/src/source/terms/application.rs new file mode 100644 index 00000000..bdb87eb5 --- /dev/null +++ b/compiler-core/checking2/src/source/terms/application.rs @@ -0,0 +1,236 @@ +use std::mem; +use std::ops::ControlFlow; + +use building_types::QueryResult; + +use crate::context::CheckContext; +use crate::core::substitute::SubstituteName; +use crate::core::{Type, TypeId, normalise, unification}; +use crate::source::types; +use crate::state::CheckState; +use crate::{ExternalQueries, safe_loop}; + +pub struct ApplicationAnalysis { + pub constraints: Vec, + pub argument: TypeId, + pub result: TypeId, +} + +pub struct GenericApplication { + pub argument: TypeId, + pub result: TypeId, +} + +fn analyse_function_application_step( + state: &mut CheckState, + context: &CheckContext, + function: TypeId, + constraints: &mut Vec, +) -> QueryResult, TypeId>> +where + Q: ExternalQueries, +{ + match context.lookup_type(function) { + Type::Function(argument, result) => { + let analysis = + ApplicationAnalysis { constraints: mem::take(constraints), argument, result }; + Ok(ControlFlow::Break(Some(analysis))) + } + + Type::Unification(unification_id) => { + let argument = state.fresh_unification(context.queries, context.prim.t); + let result = state.fresh_unification(context.queries, context.prim.t); + let function = context.intern_function(argument, result); + + unification::solve(state, context, function, unification_id, function)?; + + let analysis = + ApplicationAnalysis { constraints: mem::take(constraints), argument, result }; + + Ok(ControlFlow::Break(Some(analysis))) + } + + Type::Forall(binder_id, inner) => { + let binder = context.lookup_forall_binder(binder_id); + let binder_kind = normalise::expand(state, context, binder.kind)?; + + let replacement = state.fresh_unification(context.queries, binder_kind); + let function = SubstituteName::one(state, context, binder.name, replacement, inner)?; + Ok(ControlFlow::Continue(function)) + } + + Type::Constrained(constraint, constrained) => { + constraints.push(constraint); + Ok(ControlFlow::Continue(constrained)) + } + + Type::Application(function_argument, result) => { + let function_argument = normalise::expand(state, context, function_argument)?; + + let Type::Application(constructor, argument) = context.lookup_type(function_argument) + else { + return Ok(ControlFlow::Break(None)); + }; + + let constructor = normalise::expand(state, context, constructor)?; + if constructor == context.prim.function { + let analysis = + ApplicationAnalysis { constraints: mem::take(constraints), argument, result }; + return Ok(ControlFlow::Break(Some(analysis))); + } + + if let Type::Unification(unification_id) = context.lookup_type(constructor) { + unification::solve( + state, + context, + constructor, + unification_id, + context.prim.function, + )?; + + let analysis = + ApplicationAnalysis { constraints: mem::take(constraints), argument, result }; + + return Ok(ControlFlow::Break(Some(analysis))); + } + + Ok(ControlFlow::Break(None)) + } + + _ => Ok(ControlFlow::Break(None)), + } +} + +pub fn analyse_function_application( + state: &mut CheckState, + context: &CheckContext, + mut function: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let mut constraints = vec![]; + safe_loop! { + function = normalise::expand(state, context, function)?; + match analyse_function_application_step(state, context, function, &mut constraints)? { + ControlFlow::Continue(next) => function = next, + ControlFlow::Break(analysis) => return Ok(analysis), + }; + } +} + +pub fn check_generic_application( + state: &mut CheckState, + context: &CheckContext, + function: TypeId, +) -> QueryResult> +where + Q: ExternalQueries, +{ + let Some(ApplicationAnalysis { constraints, argument, result }) = + analyse_function_application(state, context, function)? + else { + return Ok(None); + }; + + for constraint in constraints { + state.push_wanted(constraint); + } + + Ok(Some(GenericApplication { argument, result })) +} + +pub fn check_function_application( + state: &mut CheckState, + context: &CheckContext, + function_type: TypeId, + argument: &lowering::ExpressionArgument, +) -> QueryResult +where + Q: ExternalQueries, +{ + match argument { + lowering::ExpressionArgument::Type(type_argument) => { + let Some(type_argument) = type_argument else { + return Ok(context.unknown("missing type argument")); + }; + check_function_type_application(state, context, function_type, *type_argument) + } + lowering::ExpressionArgument::Term(term_argument) => { + let Some(term_argument) = term_argument else { + return Ok(context.unknown("missing term argument")); + }; + check_function_term_application(state, context, function_type, *term_argument) + } + } +} + +pub fn check_function_term_application( + state: &mut CheckState, + context: &CheckContext, + function: TypeId, + expression_id: lowering::ExpressionId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let Some(GenericApplication { argument, result }) = + check_generic_application(state, context, function)? + else { + return Ok(context.unknown("invalid function application")); + }; + super::check_expression(state, context, expression_id, argument)?; + Ok(result) +} + +pub fn check_function_type_application( + state: &mut CheckState, + context: &CheckContext, + function: TypeId, + argument: lowering::TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let function = normalise::expand(state, context, function)?; + match context.lookup_type(function) { + Type::Forall(binder_id, inner) => { + let binder = context.lookup_forall_binder(binder_id); + let binder_kind = normalise::expand(state, context, binder.kind)?; + + let (argument, _) = types::check_kind(state, context, argument, binder_kind)?; + SubstituteName::one(state, context, binder.name, argument, inner) + } + _ => Ok(context.unknown("invalid type application")), + } +} + +pub fn infer_infix_chain( + state: &mut CheckState, + context: &CheckContext, + head: lowering::ExpressionId, + tail: &[lowering::InfixPair], +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut infix_type = super::infer_expression(state, context, head)?; + + for lowering::InfixPair { tick, element } in tail.iter() { + let Some(tick) = tick else { return Ok(context.unknown("missing infix tick")) }; + let Some(element) = element else { return Ok(context.unknown("missing infix element")) }; + + let tick_type = super::infer_expression(state, context, *tick)?; + let Some(GenericApplication { argument, result }) = + check_generic_application(state, context, tick_type)? + else { + return Ok(context.unknown("invalid function application")); + }; + unification::subtype(state, context, infix_type, argument)?; + let applied_tick = result; + + infix_type = check_function_term_application(state, context, applied_tick, *element)?; + } + + Ok(infix_type) +} diff --git a/compiler-core/checking2/src/source/terms/collections.rs b/compiler-core/checking2/src/source/terms/collections.rs new file mode 100644 index 00000000..bdc10d60 --- /dev/null +++ b/compiler-core/checking2/src/source/terms/collections.rs @@ -0,0 +1,292 @@ +use building_types::QueryResult; +use smol_str::SmolStr; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{RowField, RowType, Type, TypeId, normalise, toolkit, unification}; +use crate::state::CheckState; + +pub fn infer_array( + state: &mut CheckState, + context: &CheckContext, + array: &[lowering::ExpressionId], +) -> QueryResult +where + Q: ExternalQueries, +{ + let inferred_type = state.fresh_unification(context.queries, context.prim.t); + + for expression in array.iter() { + let element_type = super::infer_expression(state, context, *expression)?; + unification::subtype(state, context, element_type, inferred_type)?; + } + + let array_type = context.intern_application(context.prim.array, inferred_type); + + Ok(array_type) +} + +pub fn check_array( + state: &mut CheckState, + context: &CheckContext, + array: &[lowering::ExpressionId], + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let normalised = normalise::expand(state, context, expected)?; + if let Type::Application(constructor, element_type) = context.lookup_type(normalised) { + let constructor = normalise::expand(state, context, constructor)?; + if constructor == context.prim.array { + for expression in array.iter() { + super::check_expression(state, context, *expression, element_type)?; + } + return Ok(expected); + } + } + + let inferred = infer_array(state, context, array)?; + unification::subtype(state, context, inferred, expected)?; + Ok(inferred) +} + +pub fn infer_record( + state: &mut CheckState, + context: &CheckContext, + record: &[lowering::ExpressionRecordItem], +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut fields = vec![]; + + for field in record.iter() { + match field { + lowering::ExpressionRecordItem::RecordField { name, value } => { + let Some(name) = name else { continue }; + let Some(value) = value else { continue }; + + let label = SmolStr::clone(name); + let id = super::infer_expression(state, context, *value)?; + let id = toolkit::instantiate_unifications(state, context, id)?; + let id = toolkit::collect_wanteds(state, context, id)?; + + fields.push(RowField { label, id }); + } + lowering::ExpressionRecordItem::RecordPun { name, resolution } => { + let Some(name) = name else { continue }; + let Some(resolution) = resolution else { continue }; + + let label = SmolStr::clone(name); + let id = toolkit::lookup_term_variable(state, context, *resolution)?; + let id = toolkit::instantiate_unifications(state, context, id)?; + let id = toolkit::collect_wanteds(state, context, id)?; + + fields.push(RowField { label, id }); + } + } + } + + let row_type = RowType::new(fields, None); + let row_type_id = context.intern_row_type(row_type); + let row_type = context.intern_row(row_type_id); + let record_type = context.intern_application(context.prim.record, row_type); + + Ok(record_type) +} + +pub fn check_record( + state: &mut CheckState, + context: &CheckContext, + record: &[lowering::ExpressionRecordItem], + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let normalised = normalise::expand(state, context, expected)?; + if let Type::Application(constructor, row_type) = context.lookup_type(normalised) { + let constructor = normalise::expand(state, context, constructor)?; + if constructor == context.prim.record { + let row_type = normalise::expand(state, context, row_type)?; + if let Type::Row(row_id) = context.lookup_type(row_type) { + let expected_fields = context.lookup_row_type(row_id); + + let mut fields = vec![]; + + for field in record.iter() { + match field { + lowering::ExpressionRecordItem::RecordField { name, value } => { + let Some(name) = name else { continue }; + let Some(value) = value else { continue }; + + let label = SmolStr::clone(name); + + let expected_field_type = expected_fields + .fields + .iter() + .find(|f| f.label == label) + .map(|f| f.id); + + let id = if let Some(expected_type) = expected_field_type { + super::check_expression(state, context, *value, expected_type)? + } else { + let id = super::infer_expression(state, context, *value)?; + let id = toolkit::instantiate_unifications(state, context, id)?; + toolkit::collect_wanteds(state, context, id)? + }; + + fields.push(RowField { label, id }); + } + lowering::ExpressionRecordItem::RecordPun { name, resolution } => { + let Some(name) = name else { continue }; + let Some(resolution) = resolution else { continue }; + + let label = SmolStr::clone(name); + + let expected_field_type = expected_fields + .fields + .iter() + .find(|f| f.label == label) + .map(|f| f.id); + + let id = toolkit::lookup_term_variable(state, context, *resolution)?; + + let id = if let Some(expected_type) = expected_field_type { + unification::subtype(state, context, id, expected_type)?; + id + } else { + let id = toolkit::instantiate_unifications(state, context, id)?; + toolkit::collect_wanteds(state, context, id)? + }; + + fields.push(RowField { label, id }); + } + } + } + + let row_type = RowType::new(fields, None); + let row_type_id = context.intern_row_type(row_type); + let row_type = context.intern_row(row_type_id); + let record_type = context.intern_application(context.prim.record, row_type); + + unification::subtype(state, context, record_type, expected)?; + return Ok(record_type); + } + } + } + + let inferred = infer_record(state, context, record)?; + unification::subtype(state, context, inferred, expected)?; + Ok(inferred) +} + +pub fn infer_record_access( + state: &mut CheckState, + context: &CheckContext, + record: lowering::ExpressionId, + labels: &[SmolStr], +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut current_type = super::infer_expression(state, context, record)?; + + for label in labels.iter() { + let label = SmolStr::clone(label); + + let field_type = state.fresh_unification(context.queries, context.prim.t); + let tail_type = state.fresh_unification(context.queries, context.prim.row_type); + + let row_type = RowType::new(vec![RowField { label, id: field_type }], Some(tail_type)); + let row_type_id = context.intern_row_type(row_type); + let row_type = context.intern_row(row_type_id); + let record_type = context.intern_application(context.prim.record, row_type); + + unification::subtype(state, context, current_type, record_type)?; + current_type = field_type; + } + + Ok(current_type) +} + +pub fn infer_record_update( + state: &mut CheckState, + context: &CheckContext, + record: lowering::ExpressionId, + updates: &[lowering::RecordUpdate], +) -> QueryResult +where + Q: ExternalQueries, +{ + let (input_fields, output_fields, tail) = infer_record_updates(state, context, updates)?; + + let input_row = RowType::new(input_fields, Some(tail)); + let input_row_id = context.intern_row_type(input_row); + let input_row = context.intern_row(input_row_id); + let input_record = context.intern_application(context.prim.record, input_row); + + let output_row = RowType::new(output_fields, Some(tail)); + let output_row_id = context.intern_row_type(output_row); + let output_row = context.intern_row(output_row_id); + let output_record = context.intern_application(context.prim.record, output_row); + + super::check_expression(state, context, record, input_record)?; + + Ok(output_record) +} + +pub fn infer_record_updates( + state: &mut CheckState, + context: &CheckContext, + updates: &[lowering::RecordUpdate], +) -> QueryResult<(Vec, Vec, TypeId)> +where + Q: ExternalQueries, +{ + let mut input_fields = vec![]; + let mut output_fields = vec![]; + + for update in updates { + match update { + lowering::RecordUpdate::Leaf { name, expression } => { + let Some(name) = name else { continue }; + let label = SmolStr::clone(name); + + let input_id = state.fresh_unification(context.queries, context.prim.t); + let output_id = if let Some(expression) = expression { + super::infer_expression(state, context, *expression)? + } else { + context.unknown("missing record update expression") + }; + + input_fields.push(RowField { label: label.clone(), id: input_id }); + output_fields.push(RowField { label, id: output_id }); + } + lowering::RecordUpdate::Branch { name, updates } => { + let Some(name) = name else { continue }; + let label = SmolStr::clone(name); + + let (in_f, out_f, tail) = infer_record_updates(state, context, updates)?; + + let in_row = RowType::new(in_f, Some(tail)); + let in_row_id = context.intern_row_type(in_row); + let in_row = context.intern_row(in_row_id); + let in_id = context.intern_application(context.prim.record, in_row); + + let out_row = RowType::new(out_f, Some(tail)); + let out_row_id = context.intern_row_type(out_row); + let out_row = context.intern_row(out_row_id); + let out_id = context.intern_application(context.prim.record, out_row); + + input_fields.push(RowField { label: label.clone(), id: in_id }); + output_fields.push(RowField { label, id: out_id }); + } + } + } + + let tail = state.fresh_unification(context.queries, context.prim.row_type); + + Ok((input_fields, output_fields, tail)) +} diff --git a/compiler-core/checking2/src/source/terms/equations.rs b/compiler-core/checking2/src/source/terms/equations.rs new file mode 100644 index 00000000..7bbcc222 --- /dev/null +++ b/compiler-core/checking2/src/source/terms/equations.rs @@ -0,0 +1,286 @@ +//! Implements equation checking and inference rules for value groups. + +use building_types::QueryResult; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{TypeId, exhaustive, signature, toolkit, unification}; +use crate::error::ErrorKind; +use crate::source::terms::form_let; +use crate::source::{binder, terms}; +use crate::state::CheckState; + +pub enum EquationTypeOrigin { + Explicit(lowering::TypeId), + Implicit, +} + +pub enum EquationMode { + Check { origin: EquationTypeOrigin, expected_type: TypeId }, + Infer { group_type: TypeId }, +} + +pub struct EquationSet { + pub arguments: Vec, +} + +pub fn analyse_equation_set( + state: &mut CheckState, + context: &CheckContext, + mode: EquationMode, + equations: &[lowering::Equation], +) -> QueryResult +where + Q: ExternalQueries, +{ + match mode { + EquationMode::Check { origin, expected_type } => { + let required = + equations.iter().map(|equation| equation.binders.len()).max().unwrap_or(0); + + let signature::DecomposedSignature { arguments, result, .. } = + signature::expect_signature_patterns(state, context, expected_type, required)?; + + let function = context.intern_function_chain(&arguments, result); + check_equations_core(state, context, origin, &arguments, result, function, equations)?; + + Ok(EquationSet { arguments }) + } + EquationMode::Infer { group_type } => { + infer_equation_set(state, context, group_type, equations) + } + } +} + +pub fn compute_equation_exhaustiveness( + state: &mut CheckState, + context: &CheckContext, + set: &EquationSet, + equations: &[lowering::Equation], +) -> QueryResult +where + Q: ExternalQueries, +{ + exhaustive::check_equation_patterns(state, context, &set.arguments, equations) +} + +/// Infers the type of value group equations. +/// +/// For each equation: infer binders, create a result unification variable, +/// build the function type, and subtype against `group_type`. Then infer +/// the guarded expression and subtype against the result type. +fn infer_equation_set( + state: &mut CheckState, + context: &CheckContext, + group_type: TypeId, + equations: &[lowering::Equation], +) -> QueryResult +where + Q: ExternalQueries, +{ + let minimum_equation_arity = + equations.iter().map(|equation| equation.binders.len()).min().unwrap_or(0); + + for equation in equations { + let mut argument_types = vec![]; + for &binder_id in equation.binders.iter() { + let argument_type = binder::infer_binder(state, context, binder_id)?; + argument_types.push(argument_type); + } + + let result_type = state.fresh_unification(context.queries, context.prim.t); + + let argument_types = &argument_types[..minimum_equation_arity]; + let equation_type = context.intern_function_chain(argument_types, result_type); + unification::subtype(state, context, equation_type, group_type)?; + + if let Some(guarded) = &equation.guarded { + let inferred_type = infer_guarded_expression(state, context, guarded)?; + unification::subtype(state, context, inferred_type, result_type)?; + } + } + + let toolkit::InspectFunction { arguments, .. } = + toolkit::inspect_function(state, context, group_type)?; + + Ok(EquationSet { arguments }) +} + +/// Checks value group equations against a signature. +/// +/// For each equation: check binders against signature arguments, compute +/// expected result type based on equation arity vs signature arity, then +/// check the guarded expression. +pub fn check_equations_core( + state: &mut CheckState, + context: &CheckContext, + origin: EquationTypeOrigin, + arguments: &[TypeId], + result: TypeId, + function: TypeId, + equations: &[lowering::Equation], +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let expected_arity = arguments.len(); + + for equation in equations { + let equation_arity = equation.binders.len(); + + if equation_arity > expected_arity { + state.insert_error(ErrorKind::TooManyBinders { + signature: match origin { + EquationTypeOrigin::Explicit(signature_id) => Some(signature_id), + EquationTypeOrigin::Implicit => None, + }, + expected: expected_arity as u32, + actual: equation_arity as u32, + }); + } + + for (&binder_id, &argument_type) in equation.binders.iter().zip(arguments) { + binder::check_argument_binder(state, context, binder_id, argument_type)?; + } + + if equation_arity > expected_arity { + for &binder_id in &equation.binders[expected_arity..] { + binder::infer_binder(state, context, binder_id)?; + } + } + + let expected_type = if equation_arity == 0 { + function + } else if equation_arity >= expected_arity { + result + } else { + let remaining = &arguments[equation_arity..]; + context.intern_function_chain(remaining, result) + }; + + if let Some(guarded) = &equation.guarded { + check_guarded_expression(state, context, guarded, expected_type)?; + } + } + + Ok(()) +} + +pub fn infer_guarded_expression( + state: &mut CheckState, + context: &CheckContext, + guarded: &lowering::GuardedExpression, +) -> QueryResult +where + Q: ExternalQueries, +{ + match guarded { + lowering::GuardedExpression::Unconditional { where_expression } => { + let Some(w) = where_expression else { + return Ok(context.unknown("missing guarded expression")); + }; + infer_where_expression(state, context, w) + } + lowering::GuardedExpression::Conditionals { pattern_guarded } => { + let mut inferred_type = context.unknown("empty conditionals"); + for pattern_guarded in pattern_guarded.iter() { + for pattern_guard in pattern_guarded.pattern_guards.iter() { + check_pattern_guard(state, context, pattern_guard)?; + } + if let Some(w) = &pattern_guarded.where_expression { + inferred_type = infer_where_expression(state, context, w)?; + } + } + Ok(inferred_type) + } + } +} + +pub fn check_guarded_expression( + state: &mut CheckState, + context: &CheckContext, + guarded: &lowering::GuardedExpression, + expected: TypeId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + match guarded { + lowering::GuardedExpression::Unconditional { where_expression } => { + let Some(w) = where_expression else { + return Ok(()); + }; + check_where_expression(state, context, w, expected)?; + Ok(()) + } + lowering::GuardedExpression::Conditionals { pattern_guarded } => { + for pattern_guarded in pattern_guarded.iter() { + for pattern_guard in pattern_guarded.pattern_guards.iter() { + check_pattern_guard(state, context, pattern_guard)?; + } + if let Some(w) = &pattern_guarded.where_expression { + check_where_expression(state, context, w, expected)?; + } + } + Ok(()) + } + } +} + +fn check_pattern_guard( + state: &mut CheckState, + context: &CheckContext, + guard: &lowering::PatternGuard, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some(expression) = guard.expression else { + return Ok(()); + }; + + let expression_type = super::infer_expression(state, context, expression)?; + + let Some(binder) = guard.binder else { + return Ok(()); + }; + + binder::check_binder(state, context, binder, expression_type)?; + + Ok(()) +} + +pub fn infer_where_expression( + state: &mut CheckState, + context: &CheckContext, + where_expression: &lowering::WhereExpression, +) -> QueryResult +where + Q: ExternalQueries, +{ + form_let::check_let_chunks(state, context, &where_expression.bindings)?; + + let Some(expression) = where_expression.expression else { + return Ok(context.unknown("missing where expression")); + }; + + terms::infer_expression(state, context, expression) +} + +fn check_where_expression( + state: &mut CheckState, + context: &CheckContext, + where_expression: &lowering::WhereExpression, + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + form_let::check_let_chunks(state, context, &where_expression.bindings)?; + + let Some(expression) = where_expression.expression else { + return Ok(context.unknown("missing where expression")); + }; + + terms::check_expression(state, context, expression, expected) +} diff --git a/compiler-core/checking2/src/source/terms/form_ado.rs b/compiler-core/checking2/src/source/terms/form_ado.rs new file mode 100644 index 00000000..95c473f2 --- /dev/null +++ b/compiler-core/checking2/src/source/terms/form_ado.rs @@ -0,0 +1,263 @@ +use building_types::QueryResult; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{TypeId, unification}; +use crate::error::{ErrorCrumb, ErrorKind}; +use crate::source::binder; +use crate::source::terms::{application, form_do, form_let}; +use crate::state::CheckState; + +enum AdoStep<'a> { + Action { + statement: lowering::DoStatementId, + binder_type: TypeId, + expression: lowering::ExpressionId, + }, + Let { + statement: lowering::DoStatementId, + statements: &'a [lowering::LetBindingChunk], + }, +} + +pub fn infer_ado( + state: &mut CheckState, + context: &CheckContext, + map: Option, + apply: Option, + pure: Option, + statement_ids: &[lowering::DoStatementId], + expression: Option, +) -> QueryResult +where + Q: ExternalQueries, +{ + // First, perform a forward pass where variable bindings are bound + // to unification variables. Let bindings are not checked here to + // avoid premature solving of unification variables. Instead, they + // are checked inline during the statement checking loop. + let mut steps = vec![]; + for &statement_id in statement_ids.iter() { + let Some(statement) = context.lowered.info.get_do_statement(statement_id) else { + continue; + }; + match statement { + lowering::DoStatement::Bind { binder, expression } => { + let binder_type = if let Some(binder) = binder { + binder::infer_binder(state, context, *binder)? + } else { + state.fresh_unification(context.queries, context.prim.t) + }; + let Some(expression) = *expression else { continue }; + steps.push(AdoStep::Action { statement: statement_id, binder_type, expression }); + } + lowering::DoStatement::Let { statements } => { + steps.push(AdoStep::Let { statement: statement_id, statements }); + } + lowering::DoStatement::Discard { expression } => { + let binder_type = state.fresh_unification(context.queries, context.prim.t); + let Some(expression) = *expression else { continue }; + steps.push(AdoStep::Action { statement: statement_id, binder_type, expression }); + } + } + } + + let binder_types: Vec<_> = steps + .iter() + .filter_map(|step| match step { + AdoStep::Action { binder_type, .. } => Some(*binder_type), + AdoStep::Let { .. } => None, + }) + .collect(); + + // For ado blocks with no bindings, we check let statements and then + // apply pure to the expression. + // + // pure_type := a -> f a + // expression := t + if binder_types.is_empty() { + for step in &steps { + if let AdoStep::Let { statement, statements } = step { + state.with_error_crumb(ErrorCrumb::CheckingAdoLet(*statement), |state| { + form_let::check_let_chunks(state, context, statements) + })?; + } + } + return if let Some(expression) = expression { + let pure_type = form_do::lookup_or_synthesise_pure(state, context, pure)?; + application::check_function_term_application(state, context, pure_type, expression) + } else { + state.insert_error(ErrorKind::EmptyAdoBlock); + Ok(context.unknown("empty ado block")) + }; + } + + // Create a fresh unification variable for the in_expression. + // Inferring expression directly may solve the unification variables + // introduced in the first pass. This is undesirable, because the + // errors would be attributed incorrectly to the ado statements + // rather than the in-expression itself. + // + // ado + // a <- pure "Hello!" + // _ <- pure 42 + // in Message a + // + // in_expression :: Effect Message + // in_expression_type := ?in_expression + // lambda_type := ?a -> ?b -> ?in_expression + let in_expression_type = state.fresh_unification(context.queries, context.prim.t); + let lambda_type = context.intern_function_chain(&binder_types, in_expression_type); + + // The desugared form of an ado-expression is a forward applicative + // pipeline, unlike do-notation which works inside-out. The example + // above desugars to the following expression: + // + // (\a _ -> Message a) <$> (pure "Hello!") <*> (pure 42) + // + // To emulate this, we process steps in source order. Let bindings + // are checked inline between map/apply operations. The first action + // uses infer_ado_map, and subsequent actions use infer_ado_apply. + // + // map_type :: (a -> b) -> f a -> f b + // lambda_type := ?a -> ?b -> ?in_expression + // + // expression_type := Effect String + // map(lambda, expression) := Effect (?b -> ?in_expression) + // >> + // >> ?a := String + // + // continuation_type := Effect (?b -> ?in_expression) + + // Lazily compute map_type and apply_type only when needed. + // - 1 action: only map is needed + // - 2+ actions: map and apply are needed + let action_count = binder_types.len(); + + let map_type = form_do::lookup_or_synthesise_map(state, context, map)?; + + let apply_type = if action_count > 1 { + form_do::lookup_or_synthesise_apply(state, context, apply)? + } else { + context.unknown("unused apply") + }; + + let mut continuation_type = None; + + for step in &steps { + match step { + AdoStep::Let { statement, statements } => { + state.with_error_crumb(ErrorCrumb::CheckingAdoLet(*statement), |state| { + form_let::check_let_chunks(state, context, statements) + })?; + } + AdoStep::Action { statement, expression, .. } => { + let statement_type = if let Some(continuation_type) = continuation_type { + // Then, the infer_ado_apply rule applies `apply` to the inferred + // expression type and the continuation type that is a function + // contained within some container, like Effect. + // + // apply_type := f (x -> y) -> f x -> f y + // continuation_type := Effect (?b -> ?in_expression) + // + // expression_type := Effect Int + // apply(continuation, expression) := Effect ?in_expression + // >> + // >> ?b := Int + // + // continuation_type := Effect ?in_expression + state.with_error_crumb(ErrorCrumb::InferringAdoApply(*statement), |state| { + infer_ado_apply_core( + state, + context, + apply_type, + continuation_type, + *expression, + ) + })? + } else { + state.with_error_crumb(ErrorCrumb::InferringAdoMap(*statement), |state| { + infer_ado_map_core(state, context, map_type, lambda_type, *expression) + })? + }; + continuation_type = Some(statement_type); + } + } + } + + // Finally, check the in-expression against in_expression. + // At this point the binder unification variables have been solved + // to concrete types, so errors are attributed to the in-expression. + // + // in_expression :: Effect Message + // in_expression_type := Effect ?in_expression + // >> + // >> ?in_expression := Message + if let Some(expression) = expression { + super::check_expression(state, context, expression, in_expression_type)?; + } + + let Some(continuation_type) = continuation_type else { + unreachable!("invariant violated: impossible empty steps"); + }; + + Ok(continuation_type) +} + +pub fn infer_ado_map_core( + state: &mut CheckState, + context: &CheckContext, + map_type: TypeId, + lambda_type: TypeId, + expression: lowering::ExpressionId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let expression_type = super::infer_expression(state, context, expression)?; + + let Some(application::GenericApplication { argument, result }) = + application::check_generic_application(state, context, map_type)? + else { + return Ok(context.unknown("invalid function application")); + }; + unification::subtype(state, context, lambda_type, argument)?; + + let Some(application::GenericApplication { argument, result }) = + application::check_generic_application(state, context, result)? + else { + return Ok(context.unknown("invalid function application")); + }; + unification::subtype(state, context, expression_type, argument)?; + + Ok(result) +} + +pub fn infer_ado_apply_core( + state: &mut CheckState, + context: &CheckContext, + apply_type: TypeId, + continuation_type: TypeId, + expression: lowering::ExpressionId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let expression_type = super::infer_expression(state, context, expression)?; + + let Some(application::GenericApplication { argument, result }) = + application::check_generic_application(state, context, apply_type)? + else { + return Ok(context.unknown("invalid function application")); + }; + unification::subtype(state, context, continuation_type, argument)?; + + let Some(application::GenericApplication { argument, result }) = + application::check_generic_application(state, context, result)? + else { + return Ok(context.unknown("invalid function application")); + }; + unification::subtype(state, context, expression_type, argument)?; + + Ok(result) +} diff --git a/compiler-core/checking2/src/source/terms/form_do.rs b/compiler-core/checking2/src/source/terms/form_do.rs new file mode 100644 index 00000000..d12b68ef --- /dev/null +++ b/compiler-core/checking2/src/source/terms/form_do.rs @@ -0,0 +1,427 @@ +use std::iter; + +use building_types::QueryResult; +use itertools::{Itertools, Position}; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{TypeId, toolkit, unification}; +use crate::error::{ErrorCrumb, ErrorKind}; +use crate::source::binder; +use crate::source::terms::{application, form_let}; +use crate::state::CheckState; + +enum DoStep<'a> { + Bind { + statement: lowering::DoStatementId, + binder_type: TypeId, + expression: Option, + }, + Discard { + statement: lowering::DoStatementId, + expression: Option, + }, + Let { + statement: lowering::DoStatementId, + statements: &'a [lowering::LetBindingChunk], + }, +} + +/// Lookup `bind` from resolution, or synthesize `?m ?a -> (?a -> ?m ?b) -> ?m ?b`. +pub fn lookup_or_synthesise_bind( + state: &mut CheckState, + context: &CheckContext, + resolution: Option, +) -> QueryResult +where + Q: ExternalQueries, +{ + if let Some(resolution) = resolution { + toolkit::lookup_term_variable(state, context, resolution) + } else { + let m = state.fresh_unification(context.queries, context.prim.type_to_type); + let a = state.fresh_unification(context.queries, context.prim.t); + let b = state.fresh_unification(context.queries, context.prim.t); + let m_a = context.intern_application(m, a); + let m_b = context.intern_application(m, b); + let a_to_m_b = context.intern_function(a, m_b); + Ok(context.intern_function_chain(&[m_a, a_to_m_b], m_b)) + } +} + +/// Lookup `discard` from resolution, or synthesize `?m ?a -> (?a -> ?m ?b) -> ?m ?b`. +pub fn lookup_or_synthesise_discard( + state: &mut CheckState, + context: &CheckContext, + resolution: Option, +) -> QueryResult +where + Q: ExternalQueries, +{ + // Same shape as bind + lookup_or_synthesise_bind(state, context, resolution) +} + +/// Lookup `map` from resolution, or synthesize `(?a -> ?b) -> ?f ?a -> ?f ?b`. +pub fn lookup_or_synthesise_map( + state: &mut CheckState, + context: &CheckContext, + resolution: Option, +) -> QueryResult +where + Q: ExternalQueries, +{ + if let Some(resolution) = resolution { + toolkit::lookup_term_variable(state, context, resolution) + } else { + let f = state.fresh_unification(context.queries, context.prim.type_to_type); + let a = state.fresh_unification(context.queries, context.prim.t); + let b = state.fresh_unification(context.queries, context.prim.t); + let f_a = context.intern_application(f, a); + let f_b = context.intern_application(f, b); + let a_to_b = context.intern_function(a, b); + Ok(context.intern_function_chain(&[a_to_b, f_a], f_b)) + } +} + +/// Lookup `apply` from resolution, or synthesize `?f (?a -> ?b) -> ?f ?a -> ?f ?b`. +pub fn lookup_or_synthesise_apply( + state: &mut CheckState, + context: &CheckContext, + resolution: Option, +) -> QueryResult +where + Q: ExternalQueries, +{ + if let Some(resolution) = resolution { + toolkit::lookup_term_variable(state, context, resolution) + } else { + let f = state.fresh_unification(context.queries, context.prim.type_to_type); + let a = state.fresh_unification(context.queries, context.prim.t); + let b = state.fresh_unification(context.queries, context.prim.t); + let a_to_b = context.intern_function(a, b); + let f_a_to_b = context.intern_application(f, a_to_b); + let f_a = context.intern_application(f, a); + let f_b = context.intern_application(f, b); + Ok(context.intern_function_chain(&[f_a_to_b, f_a], f_b)) + } +} + +/// Lookup `pure` from resolution, or synthesize `?a -> ?f ?a`. +pub fn lookup_or_synthesise_pure( + state: &mut CheckState, + context: &CheckContext, + resolution: Option, +) -> QueryResult +where + Q: ExternalQueries, +{ + if let Some(resolution) = resolution { + toolkit::lookup_term_variable(state, context, resolution) + } else { + let f = state.fresh_unification(context.queries, context.prim.type_to_type); + let a = state.fresh_unification(context.queries, context.prim.t); + let f_a = context.intern_application(f, a); + Ok(context.intern_function(a, f_a)) + } +} + +pub fn infer_do( + state: &mut CheckState, + context: &CheckContext, + bind: Option, + discard: Option, + statement_ids: &[lowering::DoStatementId], +) -> QueryResult +where + Q: ExternalQueries, +{ + // First, perform a forward pass where variable bindings are bound + // to unification variables. Let bindings are not checked here to + // avoid premature solving of unification variables. Instead, they + // are checked inline during the statement checking loop. + let mut steps = vec![]; + for &statement_id in statement_ids.iter() { + let Some(statement) = context.lowered.info.get_do_statement(statement_id) else { + continue; + }; + match statement { + lowering::DoStatement::Bind { binder, expression } => { + let binder_type = if let Some(binder) = binder { + binder::infer_binder(state, context, *binder)? + } else { + state.fresh_unification(context.queries, context.prim.t) + }; + steps.push(DoStep::Bind { + statement: statement_id, + binder_type, + expression: *expression, + }); + } + lowering::DoStatement::Let { statements } => { + steps.push(DoStep::Let { statement: statement_id, statements }); + } + lowering::DoStatement::Discard { expression } => { + steps.push(DoStep::Discard { statement: statement_id, expression: *expression }); + } + } + } + + let action_count = steps + .iter() + .filter(|step| matches!(step, DoStep::Bind { .. } | DoStep::Discard { .. })) + .count(); + + let (has_bind_step, has_discard_step) = { + let mut has_bind = false; + let mut has_discard = false; + for (position, statement) in steps.iter().with_position() { + let is_final = matches!(position, Position::Last | Position::Only); + match statement { + DoStep::Bind { .. } => has_bind = true, + DoStep::Discard { .. } if !is_final => has_discard = true, + _ => (), + } + } + (has_bind, has_discard) + }; + + let bind_type = if has_bind_step { + lookup_or_synthesise_bind(state, context, bind)? + } else { + context.unknown("unused bind") + }; + + let discard_type = if has_discard_step { + lookup_or_synthesise_discard(state, context, discard)? + } else { + context.unknown("unused discard") + }; + + let pure_expression = match steps.iter().last() { + Some(statement) => match statement { + // Technically valid, syntactically disallowed. This allows + // partially-written do expressions to infer, with a friendly + // warning to nudge the user that `bind` is prohibited. + DoStep::Bind { statement, expression, .. } => { + state.with_error_crumb(ErrorCrumb::InferringDoBind(*statement), |state| { + state.insert_error(ErrorKind::InvalidFinalBind); + }); + expression + } + DoStep::Discard { expression, .. } => expression, + DoStep::Let { statement, .. } => { + state.with_error_crumb(ErrorCrumb::CheckingDoLet(*statement), |state| { + state.insert_error(ErrorKind::InvalidFinalLet); + }); + return Ok(context.unknown("invalid final let")); + } + }, + None => { + state.insert_error(ErrorKind::EmptyDoBlock); + return Ok(context.unknown("empty do block")); + } + }; + + // If either don't actually have expressions, it's empty! + let Some(pure_expression) = *pure_expression else { + state.insert_error(ErrorKind::EmptyDoBlock); + return Ok(context.unknown("empty do block")); + }; + + // Create unification variables that each statement in the do expression + // will unify against. The next section will get into more detail how + // these are used. These unification variables are used to enable GHC-like + // left-to-right checking of do expressions while maintaining the same + // semantics as rebindable `do` in PureScript. + let continuation_types = + iter::repeat_with(|| state.fresh_unification(context.queries, context.prim.t)) + .take(action_count) + .collect_vec(); + + // Let's trace over the following example: + // + // main = do + // a <- effect + // b <- aff + // pure { a, b } + // + // For the first statement, we know the following information. We + // instantiate the `bind` function to prepare it for application. + // The first argument is easy, it's just the expression_type; the + // second argument involves synthesising a function type using the + // `binder_type` and the `next` continuation. The application of + // these arguments creates important unifications, listed below. + // Additionally, we also create a unification to unify the `now` + // type with the result of the `bind` application. + // + // expression_type := Effect Int + // binder_type := ?a + // now := ?0 + // next := ?1 + // lambda_type := ?a -> ?1 + // + // bind_type := m a -> (a -> m b) -> m b + // | + // := apply(expression_type) + // := (Int -> Effect ?r1) -> Effect ?r1 + // | + // := apply(lambda_type) + // := Effect ?r1 + // | + // >> ?a := Int + // >> ?1 := Effect ?r1 + // >> ?0 := Effect ?r1 + // + // For the second statement, we know the following information. + // The `now` type was already solved by the previous statement, + // and an error should surface once we check the inferred type + // of the statement against it. + // + // expression_type := Aff Int + // binder_type := ?b + // now := ?1 := Effect ?r1 + // next := ?2 + // lambda_type := ?b -> ?2 + // + // bind_type := m a -> (a -> m b) -> m b + // | + // := apply(expression_type) + // := (Int -> Aff ?r2) -> Aff ?r2 + // | + // := apply(lambda_type) + // := Aff ?r2 + // | + // >> ?b := Int + // >> ?2 := Aff ?r2 + // | + // >> ?1 ~ Aff ?r2 + // >> Effect ?r1 ~ Aff ?r2 + // | + // >> Oh no! + // + // This unification error is expected, but this left-to-right checking + // approach significantly improves the reported error positions compared + // to the previous approach that emulated desugared checking. + + let mut continuations = continuation_types.iter().tuple_windows::<(_, _)>(); + + for step in &steps { + match step { + DoStep::Let { statement, statements } => { + state.with_error_crumb(ErrorCrumb::CheckingDoLet(*statement), |state| { + form_let::check_let_chunks(state, context, statements) + })?; + } + DoStep::Bind { statement, binder_type, expression } => { + let Some((&now_type, &next_type)) = continuations.next() else { + continue; + }; + let Some(expression) = *expression else { + continue; + }; + state.with_error_crumb(ErrorCrumb::InferringDoBind(*statement), |state| { + let statement_type = infer_do_bind_core( + state, + context, + bind_type, + next_type, + expression, + *binder_type, + )?; + unification::subtype(state, context, statement_type, now_type)?; + Ok(()) + })?; + } + DoStep::Discard { statement, expression } => { + let Some((&now_type, &next_type)) = continuations.next() else { + continue; + }; + let Some(expression) = *expression else { + continue; + }; + state.with_error_crumb(ErrorCrumb::InferringDoDiscard(*statement), |state| { + let statement_type = + infer_do_discard_core(state, context, discard_type, next_type, expression)?; + unification::subtype(state, context, statement_type, now_type)?; + Ok(()) + })?; + } + } + } + + // The `first_continuation` is the overall type of the do expression, + // built iteratively and through solving unification variables. On + // the other hand, the `final_continuation` is the expected type for + // the final statement in the do expression. If there is only a single + // statement in the do expression, then these two bindings are equivalent. + let first_continuation = + *continuation_types.first().expect("invariant violated: empty continuation_types"); + let final_continuation = + *continuation_types.last().expect("invariant violated: empty continuation_types"); + + super::check_expression(state, context, pure_expression, final_continuation)?; + + Ok(first_continuation) +} + +pub fn infer_do_bind_core( + state: &mut CheckState, + context: &CheckContext, + bind_type: TypeId, + continuation_type: TypeId, + expression: lowering::ExpressionId, + binder_type: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let expression_type = super::infer_expression(state, context, expression)?; + let lambda_type = context.intern_function(binder_type, continuation_type); + + let Some(application::GenericApplication { argument, result }) = + application::check_generic_application(state, context, bind_type)? + else { + return Ok(context.unknown("invalid function application")); + }; + unification::subtype(state, context, expression_type, argument)?; + + let Some(application::GenericApplication { argument, result }) = + application::check_generic_application(state, context, result)? + else { + return Ok(context.unknown("invalid function application")); + }; + unification::subtype(state, context, lambda_type, argument)?; + + Ok(result) +} + +pub fn infer_do_discard_core( + state: &mut CheckState, + context: &CheckContext, + discard_type: TypeId, + continuation_type: TypeId, + expression: lowering::ExpressionId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let expression_type = super::infer_expression(state, context, expression)?; + + let Some(application::GenericApplication { argument, result }) = + application::check_generic_application(state, context, discard_type)? + else { + return Ok(context.unknown("invalid function application")); + }; + unification::subtype(state, context, expression_type, argument)?; + + let Some(application::GenericApplication { result, .. }) = + application::check_generic_application(state, context, result)? + else { + return Ok(context.unknown("invalid function application")); + }; + + unification::subtype(state, context, continuation_type, result)?; + + Ok(continuation_type) +} diff --git a/compiler-core/checking2/src/source/terms/form_let.rs b/compiler-core/checking2/src/source/terms/form_let.rs new file mode 100644 index 00000000..d60c937b --- /dev/null +++ b/compiler-core/checking2/src/source/terms/form_let.rs @@ -0,0 +1,185 @@ +use building_types::QueryResult; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{exhaustive, toolkit}; +use crate::error::ErrorCrumb; +use crate::source::terms::equations; +use crate::source::{binder, types}; +use crate::state::CheckState; + +pub fn check_let_chunks( + state: &mut CheckState, + context: &CheckContext, + chunks: &[lowering::LetBindingChunk], +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for chunk in chunks { + match chunk { + lowering::LetBindingChunk::Pattern { binder, where_expression } => { + check_pattern_let_binding(state, context, binder, where_expression)?; + } + lowering::LetBindingChunk::Names { bindings, scc } => { + check_names_chunk(state, context, bindings, scc)?; + } + } + } + Ok(()) +} + +pub fn check_pattern_let_binding( + state: &mut CheckState, + context: &CheckContext, + binder: &Option, + where_expression: &Option, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some(where_expression) = where_expression else { + return Ok(()); + }; + + let expression_type = equations::infer_where_expression(state, context, where_expression)?; + + let Some(binder) = *binder else { + return Ok(()); + }; + + let expression_type = if binder::requires_instantiation(context, binder) { + toolkit::instantiate_unifications(state, context, expression_type)? + } else { + expression_type + }; + + let binder_type = binder::check_binder(state, context, binder, expression_type)?; + + let exhaustiveness = + exhaustive::check_lambda_patterns(state, context, &[binder_type], &[binder])?; + + let has_missing = exhaustiveness.missing.is_some(); + state.report_exhaustiveness(context, exhaustiveness); + + if has_missing { + state.push_wanted(context.prim.partial); + } + + Ok(()) +} + +pub fn check_names_chunk( + state: &mut CheckState, + context: &CheckContext, + bindings: &[lowering::LetBindingNameGroupId], + scc: &[lowering::Scc], +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for &id in bindings { + let Some(name) = context.lowered.info.get_let_binding(id) else { + continue; + }; + if let Some(signature_id) = name.signature { + let (name_type, _) = types::check_kind(state, context, signature_id, context.prim.t)?; + state.checked.nodes.lets.insert(id, name_type); + } else { + let name_type = state.fresh_unification(context.queries, context.prim.t); + state.checked.nodes.lets.insert(id, name_type); + } + } + + for item in scc { + match item { + lowering::Scc::Base(id) | lowering::Scc::Recursive(id) => { + check_let_name_binding(state, context, *id)?; + } + lowering::Scc::Mutual(mutual) => { + for id in mutual { + check_let_name_binding(state, context, *id)?; + } + } + } + } + + Ok(()) +} + +pub fn check_let_name_binding( + state: &mut CheckState, + context: &CheckContext, + id: lowering::LetBindingNameGroupId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + state.with_implication(|state| { + state.with_error_crumb(ErrorCrumb::CheckingLetName(id), |state| { + check_let_name_binding_core(state, context, id) + }) + }) +} + +pub fn check_let_name_binding_core( + state: &mut CheckState, + context: &CheckContext, + id: lowering::LetBindingNameGroupId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some(name) = context.lowered.info.get_let_binding(id) else { + return Ok(()); + }; + + let Some(name_type) = state.checked.nodes.lookup_let(id) else { + return Ok(()); + }; + + if let Some(signature_id) = name.signature { + let equation_set = equations::analyse_equation_set( + state, + context, + equations::EquationMode::Check { + origin: equations::EquationTypeOrigin::Explicit(signature_id), + expected_type: name_type, + }, + &name.equations, + )?; + + let exhaustiveness = equations::compute_equation_exhaustiveness( + state, + context, + &equation_set, + &name.equations, + )?; + state.report_exhaustiveness(context, exhaustiveness); + } else { + // Keep simple let bindings e.g. `bind = ibind` polymorphic. + if let [equation] = name.equations.as_ref() + && equation.binders.is_empty() + && let Some(guarded) = &equation.guarded + { + let inferred_type = equations::infer_guarded_expression(state, context, guarded)?; + state.checked.nodes.lets.insert(id, inferred_type); + } else { + let equation_set = equations::analyse_equation_set( + state, + context, + equations::EquationMode::Infer { group_type: name_type }, + &name.equations, + )?; + let exhaustiveness = equations::compute_equation_exhaustiveness( + state, + context, + &equation_set, + &name.equations, + )?; + state.report_exhaustiveness(context, exhaustiveness); + } + } + + Ok(()) +} diff --git a/compiler-core/checking2/src/source/terms/forms.rs b/compiler-core/checking2/src/source/terms/forms.rs new file mode 100644 index 00000000..4a98d900 --- /dev/null +++ b/compiler-core/checking2/src/source/terms/forms.rs @@ -0,0 +1,266 @@ +use building_types::QueryResult; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{TypeId, exhaustive, toolkit, unification}; +use crate::source::terms::{equations, form_let}; +use crate::source::{binder, terms}; +use crate::state::CheckState; + +pub fn infer_if_then_else( + state: &mut CheckState, + context: &CheckContext, + if_: Option, + then: Option, + else_: Option, +) -> QueryResult +where + Q: ExternalQueries, +{ + if let Some(if_) = if_ { + super::check_expression(state, context, if_, context.prim.boolean)?; + } + + let result_type = state.fresh_unification(context.queries, context.prim.t); + + if let Some(then) = then { + super::check_expression(state, context, then, result_type)?; + } + + if let Some(else_) = else_ { + super::check_expression(state, context, else_, result_type)?; + } + + Ok(result_type) +} + +pub fn check_if_then_else( + state: &mut CheckState, + context: &CheckContext, + if_: Option, + then: Option, + else_: Option, + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + if let Some(if_) = if_ { + super::check_expression(state, context, if_, context.prim.boolean)?; + } + + if let Some(then) = then { + super::check_expression(state, context, then, expected)?; + } + + if let Some(else_) = else_ { + super::check_expression(state, context, else_, expected)?; + } + + Ok(expected) +} + +pub fn infer_lambda( + state: &mut CheckState, + context: &CheckContext, + binders: &[lowering::BinderId], + expression: Option, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut argument_types = vec![]; + + for &binder_id in binders.iter() { + let argument_type = state.fresh_unification(context.queries, context.prim.t); + binder::check_binder(state, context, binder_id, argument_type)?; + argument_types.push(argument_type); + } + + let result_type = if let Some(body) = expression { + let body_type = super::infer_expression(state, context, body)?; + toolkit::instantiate_constrained(state, context, body_type)? + } else { + state.fresh_unification(context.queries, context.prim.t) + }; + + let function_type = context.intern_function_chain(&argument_types, result_type); + + let exhaustiveness = + exhaustive::check_lambda_patterns(state, context, &argument_types, binders)?; + + let has_missing = exhaustiveness.missing.is_some(); + state.report_exhaustiveness(context, exhaustiveness); + + if has_missing { + Ok(context.intern_constrained(context.prim.partial, function_type)) + } else { + Ok(function_type) + } +} + +pub fn check_lambda( + state: &mut CheckState, + context: &CheckContext, + binders: &[lowering::BinderId], + expression: Option, + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut arguments = vec![]; + let mut remaining = expected; + + for &binder_id in binders.iter() { + let decomposed = toolkit::decompose_function(state, context, remaining)?; + if let Some((argument, result)) = decomposed { + binder::check_binder(state, context, binder_id, argument)?; + arguments.push(argument); + remaining = result; + } else { + let argument_type = state.fresh_unification(context.queries, context.prim.t); + binder::check_binder(state, context, binder_id, argument_type)?; + arguments.push(argument_type); + } + } + + let result_type = if let Some(body) = expression { + super::check_expression(state, context, body, remaining)? + } else { + state.fresh_unification(context.queries, context.prim.t) + }; + + let function_type = context.intern_function_chain(&arguments, result_type); + + let exhaustiveness = exhaustive::check_lambda_patterns(state, context, &arguments, binders)?; + + let has_missing = exhaustiveness.missing.is_some(); + state.report_exhaustiveness(context, exhaustiveness); + + if has_missing { + state.push_wanted(context.prim.partial); + } + + Ok(function_type) +} + +pub fn instantiate_trunk_types( + state: &mut CheckState, + context: &CheckContext, + trunk_types: &mut [TypeId], + branches: &[lowering::CaseBranch], +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for (position, trunk_type) in trunk_types.iter_mut().enumerate() { + let should_instantiate = branches.iter().any(|branch| { + let binder = branch.binders.get(position); + binder.is_some_and(|&binder_id| binder::requires_instantiation(context, binder_id)) + }); + if should_instantiate { + *trunk_type = toolkit::instantiate_unifications(state, context, *trunk_type)?; + } + } + Ok(()) +} + +pub fn infer_case_of( + state: &mut CheckState, + context: &CheckContext, + trunk: &[lowering::ExpressionId], + branches: &[lowering::CaseBranch], +) -> QueryResult +where + Q: ExternalQueries, +{ + let result_type = state.fresh_unification(context.queries, context.prim.t); + + let mut trunk_types = vec![]; + for trunk in trunk.iter() { + let trunk_type = super::infer_expression(state, context, *trunk)?; + trunk_types.push(trunk_type); + } + + instantiate_trunk_types(state, context, &mut trunk_types, branches)?; + + for branch in branches.iter() { + for (binder, trunk) in branch.binders.iter().zip(&trunk_types) { + binder::check_binder(state, context, *binder, *trunk)?; + } + if let Some(guarded) = &branch.guarded_expression { + let guarded_type = equations::infer_guarded_expression(state, context, guarded)?; + unification::subtype(state, context, guarded_type, result_type)?; + } + } + + let exhaustiveness = exhaustive::check_case_patterns(state, context, &trunk_types, branches)?; + + let has_missing = exhaustiveness.missing.is_some(); + state.report_exhaustiveness(context, exhaustiveness); + + if has_missing { + Ok(context.intern_constrained(context.prim.partial, result_type)) + } else { + Ok(result_type) + } +} + +pub fn check_case_of( + state: &mut CheckState, + context: &CheckContext, + trunk: &[lowering::ExpressionId], + branches: &[lowering::CaseBranch], + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let mut trunk_types = vec![]; + for trunk in trunk.iter() { + let trunk_type = super::infer_expression(state, context, *trunk)?; + trunk_types.push(trunk_type); + } + + instantiate_trunk_types(state, context, &mut trunk_types, branches)?; + + for branch in branches.iter() { + for (binder, trunk) in branch.binders.iter().zip(&trunk_types) { + binder::check_binder(state, context, *binder, *trunk)?; + } + if let Some(guarded) = &branch.guarded_expression { + equations::check_guarded_expression(state, context, guarded, expected)?; + } + } + + let exhaustiveness = exhaustive::check_case_patterns(state, context, &trunk_types, branches)?; + + let has_missing = exhaustiveness.missing.is_some(); + state.report_exhaustiveness(context, exhaustiveness); + + if has_missing { + state.push_wanted(context.prim.partial); + } + + Ok(expected) +} + +pub fn check_let_in( + state: &mut CheckState, + context: &CheckContext, + bindings: &[lowering::LetBindingChunk], + expression: Option, + expected: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + form_let::check_let_chunks(state, context, bindings)?; + + let Some(expression) = expression else { + return Ok(context.unknown("missing let expression")); + }; + + terms::check_expression(state, context, expression, expected) +} diff --git a/compiler-core/checking2/src/source/type_items.rs b/compiler-core/checking2/src/source/type_items.rs new file mode 100644 index 00000000..23bf44cb --- /dev/null +++ b/compiler-core/checking2/src/source/type_items.rs @@ -0,0 +1,939 @@ +use std::mem; +use std::sync::Arc; + +use building_types::QueryResult; +use files::FileId; +use indexing::{TermItemId, TypeItemId}; +use itertools::Itertools; +use lowering::{ + ClassIr, DataIr, LoweringError, NewtypeIr, RecursiveGroup, Scc, SynonymIr, TermItemIr, + TypeItemIr, TypeVariableBinding, +}; +use smol_str::SmolStr; + +use crate::ExternalQueries; +use crate::context::CheckContext; +use crate::core::{ + CheckedClass, CheckedSynonym, ForallBinder, Role, Type, TypeId, generalise, signature, toolkit, + unification, zonk, +}; +use crate::error::{ErrorCrumb, ErrorKind}; +use crate::source::types; +use crate::state::CheckState; + +struct PendingDataType { + parameters: Vec, + constructors: Vec<(TermItemId, Vec)>, + declared_roles: Arc<[lowering::Role]>, +} + +struct PendingSynonymType { + parameters: Vec, + synonym: TypeId, +} + +struct PendingClassType { + parameters: Vec, + superclasses: Vec, + functional_dependencies: Arc<[lowering::FunctionalDependency]>, + members: Vec<(TermItemId, TypeId)>, +} + +#[derive(Default)] +struct TypeSccState { + data: Vec<(TypeItemId, PendingDataType)>, + synonym: Vec<(TypeItemId, PendingSynonymType)>, + class: Vec<(TypeItemId, PendingClassType)>, + foreign: Vec<(TypeItemId, Arc<[lowering::Role]>)>, + operator: Vec, +} + +/// Checks all type items in topological order. +/// +/// The order is determined by [`GroupedModule::type_scc`] in [`lowering`]. +/// The algorithm accounts for items that appear in [`RecursiveKinds`] by +/// filtering and populating these items with [`Type::Unknown`]. This +/// enables checking of other binding groups, which may be unaffected. +/// +/// [`GroupedModule::type_scc`]: lowering::GroupedModule::type_scc +/// [`RecursiveKinds`]: LoweringError::RecursiveKinds +/// [`Type::Unknown`]: crate::core::Type::Unknown +pub fn check_type_items(state: &mut CheckState, context: &CheckContext) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for scc in &context.grouped.type_scc { + let (items, skipped) = partition_type_items(context, scc); + populate_skipped_items(state, context, &skipped); + + for &item in &items { + check_type_signature(state, context, item)?; + } + + if scc.is_recursive() { + prepare_binding_group(state, context, &items); + } + + let mut scc_state = TypeSccState::default(); + + for &item in &items { + check_type_equation(state, context, &mut scc_state, item)?; + } + + finalise_type_binding_group(state, context, &items)?; + finalise_roles(state, context, &mut scc_state)?; + finalise_data_constructors(state, context, &mut scc_state)?; + finalise_synonym_replacements(state, context, &mut scc_state)?; + finalise_classes(state, context, &mut scc_state)?; + finalise_type_operators(state, context, &mut scc_state)?; + } + Ok(()) +} + +fn prepare_binding_group(state: &mut CheckState, context: &CheckContext, items: &[TypeItemId]) +where + Q: ExternalQueries, +{ + for &item_id in items { + if state.checked.types.contains_key(&item_id) { + continue; + } + let kind = state.fresh_unification(context.queries, context.prim.t); + state.checked.types.insert(item_id, kind); + } +} + +fn finalise_type_binding_group( + state: &mut CheckState, + context: &CheckContext, + items: &[TypeItemId], +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let mut pending = Vec::with_capacity(items.len()); + + for &item_id in items { + let Some(kind) = state.checked.types.get(&item_id).copied() else { + continue; + }; + + let kind = zonk::zonk(state, context, kind)?; + let unsolved = generalise::unsolved_unifications(state, context, kind)?; + + pending.push((item_id, kind, unsolved)); + } + + for (item_id, kind, unsolved) in pending { + let kind = generalise::generalise_unsolved(state, context, kind, &unsolved)?; + state.checked.types.insert(item_id, kind); + } + + Ok(()) +} + +fn partition_type_items( + context: &CheckContext, + scc: &Scc, +) -> (Vec, Vec) +where + Q: ExternalQueries, +{ + let mut checked = vec![]; + let mut skipped = vec![]; + + for &item_id in scc.as_slice() { + if is_recursive_kind(context, item_id) { + skipped.push(item_id); + } else { + checked.push(item_id); + } + } + + (checked, skipped) +} + +fn is_recursive_kind(context: &CheckContext, item_id: TypeItemId) -> bool +where + Q: ExternalQueries, +{ + context.grouped.cycle_errors.iter().any(|error| { + let LoweringError::RecursiveKinds(RecursiveGroup { group }) = error else { + return false; + }; + group.contains(&item_id) + }) +} + +fn populate_skipped_items( + state: &mut CheckState, + context: &CheckContext, + items: &[TypeItemId], +) where + Q: ExternalQueries, +{ + let unknown = context.unknown("invalid recursive type"); + let skipped = items.iter().map(|item| (*item, unknown)); + state.checked.types.extend(skipped); +} + +fn check_type_signature( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some(item) = context.lowered.info.get_type_item(item_id) else { + return Ok(()); + }; + + match item { + TypeItemIr::DataGroup { signature, .. } => { + let Some(signature) = signature else { return Ok(()) }; + check_signature_kind(state, context, item_id, *signature)?; + } + TypeItemIr::NewtypeGroup { signature, .. } => { + let Some(signature) = signature else { return Ok(()) }; + check_signature_kind(state, context, item_id, *signature)?; + } + TypeItemIr::SynonymGroup { signature, .. } => { + let Some(signature) = signature else { return Ok(()) }; + check_signature_kind(state, context, item_id, *signature)?; + } + TypeItemIr::ClassGroup { signature, .. } => { + let Some(signature) = signature else { return Ok(()) }; + check_signature_kind(state, context, item_id, *signature)?; + } + TypeItemIr::Foreign { signature, .. } => { + let Some(signature) = signature else { return Ok(()) }; + check_signature_kind(state, context, item_id, *signature)?; + } + TypeItemIr::Operator { .. } => {} + } + + Ok(()) +} + +fn check_signature_kind( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, + signature: lowering::TypeId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let (checked_kind, _) = types::check_kind(state, context, signature, context.prim.t)?; + state.checked.types.insert(item_id, checked_kind); + Ok(()) +} + +fn check_type_equation( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TypeSccState, + item_id: TypeItemId, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some(item) = context.lowered.info.get_type_item(item_id) else { + return Ok(()); + }; + + match item { + TypeItemIr::DataGroup { signature, data, roles } => { + let Some(DataIr { variables }) = data else { return Ok(()) }; + check_data_equation(state, context, scc, item_id, *signature, variables, roles)?; + } + TypeItemIr::NewtypeGroup { signature, newtype, roles } => { + let Some(NewtypeIr { variables }) = newtype else { return Ok(()) }; + check_data_equation(state, context, scc, item_id, *signature, variables, roles)?; + } + TypeItemIr::SynonymGroup { signature, synonym, .. } => { + let Some(SynonymIr { variables, synonym }) = synonym else { return Ok(()) }; + check_synonym_equation(state, context, scc, item_id, *signature, variables, *synonym)?; + } + TypeItemIr::ClassGroup { signature, class } => { + let Some(class) = class else { return Ok(()) }; + check_class_equation(state, context, scc, item_id, *signature, class)?; + } + TypeItemIr::Foreign { roles, .. } => { + scc.foreign.push((item_id, Arc::clone(roles))); + } + TypeItemIr::Operator { resolution, .. } => { + check_type_operator(state, context, scc, item_id, *resolution)?; + } + } + + Ok(()) +} + +fn check_data_equation( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TypeSccState, + item_id: TypeItemId, + signature: Option, + variables: &[TypeVariableBinding], + declared_roles: &Arc<[lowering::Role]>, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let parameters = if let Some(signature_id) = signature + && let Some(signature_kind) = state.checked.lookup_type(item_id) + { + check_data_equation_check(state, context, (signature_id, signature_kind), variables)? + } else { + check_data_equation_infer(state, context, item_id, variables)? + }; + + let constructors = check_data_constructors(state, context, item_id)?; + let declared_roles = Arc::clone(declared_roles); + + scc.data.push((item_id, PendingDataType { parameters, constructors, declared_roles })); + + Ok(()) +} + +fn check_data_equation_check( + state: &mut CheckState, + context: &CheckContext, + signature: (lowering::TypeId, TypeId), + bindings: &[TypeVariableBinding], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let signature = signature::expect_signature_bindings(state, context, signature, bindings)?; + check_type_variable_bindings(state, context, bindings, &signature.arguments) +} + +fn check_type_variable_bindings( + state: &mut CheckState, + context: &CheckContext, + bindings: &[TypeVariableBinding], + signature: &[TypeId], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let mut binders = vec![]; + + for (index, equation_binding) in bindings.iter().enumerate() { + let signature_kind = signature.get(index).copied(); + + let kind = resolve_type_variable_binding(state, context, signature_kind, equation_binding)?; + + let name = state.names.fresh(); + state.bindings.bind_forall(equation_binding.id, name, kind); + + let text = if let Some(name) = &equation_binding.name { + SmolStr::clone(name) + } else { + name.as_text() + }; + + let text = context.queries.intern_smol_str(text); + state.checked.names.insert(name, text); + let visible = equation_binding.visible; + + binders.push(ForallBinder { visible, name, kind }); + } + + Ok(binders) +} + +fn resolve_type_variable_binding( + state: &mut CheckState, + context: &CheckContext, + signature: Option, + binding: &TypeVariableBinding, +) -> QueryResult +where + Q: ExternalQueries, +{ + match (signature, binding.kind) { + (Some(signature_kind), Some(binding_kind)) => { + let (binding_kind, _) = types::infer_kind(state, context, binding_kind)?; + let valid = unification::subtype(state, context, signature_kind, binding_kind)?; + if valid { Ok(binding_kind) } else { Ok(context.unknown("invalid variable kind")) } + } + (Some(signature_kind), None) => { + // Pure checking + Ok(signature_kind) + } + (None, Some(binding_kind)) => { + let (binding_kind, _) = + types::check_kind(state, context, binding_kind, context.prim.t)?; + Ok(binding_kind) + } + (None, None) => { + // Pure inference + Ok(state.fresh_unification(context.queries, context.prim.t)) + } + } +} + +fn check_data_equation_infer( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, + bindings: &[TypeVariableBinding], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let bindings = check_type_variable_bindings(state, context, bindings, &[])?; + let kinds = bindings.iter().map(|binder| binder.kind); + let inferred = context.intern_function_chain_iter(kinds, context.prim.t); + + if let Some(expected) = state.checked.lookup_type(item_id) { + unification::subtype(state, context, inferred, expected)?; + } else { + state.checked.types.insert(item_id, inferred); + } + + Ok(bindings) +} + +fn check_data_constructors( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, +) -> QueryResult)>> +where + Q: ExternalQueries, +{ + let mut constructors = vec![]; + + for constructor_id in context.indexed.pairs.data_constructors(item_id) { + let Some(TermItemIr::Constructor { arguments }) = + context.lowered.info.get_term_item(constructor_id) + else { + continue; + }; + + let mut checked_arguments = vec![]; + for &argument in arguments.iter() { + state.with_error_crumb(ErrorCrumb::ConstructorArgument(argument), |state| { + let (checked_argument, _) = + types::check_kind(state, context, argument, context.prim.t)?; + checked_arguments.push(checked_argument); + Ok(()) + })?; + } + constructors.push((constructor_id, checked_arguments)); + } + + Ok(constructors) +} + +fn finalise_data_constructors( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TypeSccState, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for (item_id, PendingDataType { parameters, constructors, .. }) in mem::take(&mut scc.data) { + // constructor_kind should have already been generalised by the + // finalise_binding_group function. the kind signature is used + // as the source of truth for constructing the kind applications + let Some(constructor_kind) = state.checked.types.get(&item_id).copied() else { + continue; + }; + + let signature::DecomposedSignature { + binders: kind_binders, + arguments: parameter_kinds, + .. + } = signature::decompose_signature( + state, + context, + constructor_kind, + signature::DecomposeSignatureMode::Full, + )?; + + // parameter_kinds is the post-generalisation kind for each parameter; + // we want to replace pre-generalisation kinds carried by parameters + // before constructing the signature for the constructor. + let get_parameter_kind = |index: usize| { + if let Some(kind) = parameter_kinds.get(index) { + *kind + } else { + context.unknown("invalid kind") + } + }; + + let type_reference = context.queries.intern_type(Type::Constructor(context.id, item_id)); + + // For the following code loop, let's trace through the declaration: + // + // newtype Tagged :: forall k. k -> Type -> Type + // + for (constructor_id, checked_arguments) in constructors { + let mut result = type_reference; + + // Tagged @k + for binder in &kind_binders { + let rigid = context.intern_rigid(binder.name, state.depth, binder.kind); + result = context.intern_kind_application(result, rigid); + } + + // Tagged @k t a + for (index, parameter) in parameters.iter().enumerate() { + let kind = get_parameter_kind(index); + let rigid = context.intern_rigid(parameter.name, state.depth, kind); + result = context.intern_application(result, rigid); + } + + // a -> Tagged @k t a + for argument in checked_arguments.into_iter().rev() { + let argument = zonk::zonk(state, context, argument)?; + result = context.intern_function(argument, result); + } + + // forall (a :: Type). a -> Tagged @k t a + for (index, parameter) in parameters.iter().enumerate().rev() { + let kind = get_parameter_kind(index); + + let binder = ForallBinder { kind, ..*parameter }; + + let binder_id = context.intern_forall_binder(binder); + result = context.intern_forall(binder_id, result); + } + + // forall (k :: Type) (t :: k) (a :: Type). a -> Tagged @k t a + for binder in kind_binders.iter().rev() { + let binder_id = context.intern_forall_binder(*binder); + result = context.intern_forall(binder_id, result); + } + + state.checked.terms.insert(constructor_id, result); + } + } + + Ok(()) +} + +fn finalise_roles( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TypeSccState, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for (item_id, pending) in &scc.data { + let PendingDataType { parameters, constructors, declared_roles } = pending; + let inferred_roles = + super::roles::infer_data_roles(state, context, parameters, constructors)?; + let resolved_roles = super::roles::check_declared_roles( + state, + *item_id, + &inferred_roles, + declared_roles, + false, + ); + state.checked.roles.insert(*item_id, resolved_roles); + } + + for (item_id, declared_roles) in mem::take(&mut scc.foreign) { + let Some(kind) = state.checked.lookup_type(item_id) else { + continue; + }; + + let parameter_count = super::roles::count_kind_arguments(state, context, kind)?; + let inferred_roles = vec![Role::Nominal; parameter_count]; + let resolved_roles = super::roles::check_declared_roles( + state, + item_id, + &inferred_roles, + &declared_roles, + true, + ); + + state.checked.roles.insert(item_id, resolved_roles); + } + + Ok(()) +} + +fn check_synonym_equation( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TypeSccState, + item_id: TypeItemId, + signature: Option, + bindings: &[TypeVariableBinding], + synonym: Option, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let (parameters, result) = if let Some(signature_id) = signature + && let Some(signature_kind) = state.checked.lookup_type(item_id) + { + check_synonym_equation_check(state, context, bindings, (signature_id, signature_kind))? + } else { + check_synonym_equation_infer(state, context, item_id, bindings)? + }; + + let synonym = if let Some(synonym) = synonym { + let (synonym, _) = types::check_kind(state, context, synonym, result)?; + synonym + } else { + context.unknown("invalid synonym type") + }; + + scc.synonym.push((item_id, PendingSynonymType { parameters, synonym })); + + Ok(()) +} + +fn check_synonym_equation_check( + state: &mut CheckState, + context: &CheckContext, + bindings: &[TypeVariableBinding], + (signature_id, signature_kind): (lowering::TypeId, TypeId), +) -> QueryResult<(Vec, TypeId)> +where + Q: ExternalQueries, +{ + let signature = signature::expect_signature_bindings( + state, + context, + (signature_id, signature_kind), + bindings, + )?; + let parameters = check_type_variable_bindings(state, context, bindings, &signature.arguments)?; + Ok((parameters, signature.result)) +} + +fn check_synonym_equation_infer( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, + bindings: &[TypeVariableBinding], +) -> QueryResult<(Vec, TypeId)> +where + Q: ExternalQueries, +{ + let bindings = check_type_variable_bindings(state, context, bindings, &[])?; + let kinds = bindings.iter().map(|binder| binder.kind); + let result = state.fresh_unification(context.queries, context.prim.t); + let inferred = context.intern_function_chain_iter(kinds, result); + + if let Some(expected) = state.checked.lookup_type(item_id) { + unification::subtype(state, context, inferred, expected)?; + } else { + state.checked.types.insert(item_id, inferred); + } + + Ok((bindings, result)) +} + +fn finalise_synonym_replacements( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TypeSccState, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for (item_id, PendingSynonymType { parameters, synonym }) in mem::take(&mut scc.synonym) { + let Some(kind) = state.checked.lookup_type(item_id) else { + continue; + }; + let synonym = zonk::zonk(state, context, synonym)?; + let synonym = CheckedSynonym { kind, parameters, synonym }; + state.checked.synonyms.insert(item_id, synonym); + } + Ok(()) +} + +fn check_class_equation( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TypeSccState, + item_id: TypeItemId, + signature: Option, + class: &ClassIr, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let ClassIr { constraints, variables, functional_dependencies } = class; + + let parameters = if let Some(signature_id) = signature + && let Some(signature_kind) = state.checked.lookup_type(item_id) + { + check_class_equation_check(state, context, variables, (signature_id, signature_kind))? + } else { + check_class_equation_infer(state, context, item_id, variables)? + }; + + let mut superclasses = vec![]; + for &constraint in constraints.iter() { + let (superclass, _) = + types::check_kind(state, context, constraint, context.prim.constraint)?; + superclasses.push(superclass); + } + + let functional_dependencies = Arc::clone(functional_dependencies); + let members = check_class_members(state, context, item_id)?; + + scc.class.push(( + item_id, + PendingClassType { parameters, superclasses, functional_dependencies, members }, + )); + + Ok(()) +} + +fn check_class_equation_check( + state: &mut CheckState, + context: &CheckContext, + bindings: &[TypeVariableBinding], + signature: (lowering::TypeId, TypeId), +) -> QueryResult> +where + Q: ExternalQueries, +{ + let signature = signature::expect_signature_bindings(state, context, signature, bindings)?; + check_type_variable_bindings(state, context, bindings, &signature.arguments) +} + +fn check_class_equation_infer( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, + bindings: &[TypeVariableBinding], +) -> QueryResult> +where + Q: ExternalQueries, +{ + let bindings = check_type_variable_bindings(state, context, bindings, &[])?; + let kinds = bindings.iter().map(|binder| binder.kind); + let inferred = context.intern_function_chain_iter(kinds, context.prim.constraint); + + if let Some(expected) = state.checked.lookup_type(item_id) { + unification::subtype(state, context, inferred, expected)?; + } else { + state.checked.types.insert(item_id, inferred); + } + + Ok(bindings) +} + +fn check_class_members( + state: &mut CheckState, + context: &CheckContext, + item_id: TypeItemId, +) -> 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) + else { + continue; + }; + + let Some(signature_id) = signature else { continue }; + + let (member_type, _) = types::check_kind(state, context, *signature_id, context.prim.t)?; + members.push((member_id, member_type)); + } + + Ok(members) +} + +fn finalise_classes( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TypeSccState, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for (item_id, pending) in mem::take(&mut scc.class) { + let PendingClassType { parameters, superclasses, functional_dependencies, members } = + pending; + + let Some(class_kind) = state.checked.types.get(&item_id).copied() else { + continue; + }; + + let signature::DecomposedSignature { + binders: class_binders, + arguments: class_parameters, + .. + } = signature::decompose_signature( + state, + context, + class_kind, + signature::DecomposeSignatureMode::Full, + )?; + + let get_parameter_kind = |index: usize| { + if let Some(kind) = class_parameters.get(index) { + *kind + } else { + context.unknown("invalid kind") + } + }; + + let kind_binders = class_binders + .iter() + .copied() + .map(|binder| context.intern_forall_binder(binder)) + .collect_vec(); + + let type_parameters = parameters + .iter() + .copied() + .enumerate() + .map(|(index, parameter)| { + let kind = get_parameter_kind(index); + let binder = ForallBinder { kind, ..parameter }; + context.intern_forall_binder(binder) + }) + .collect_vec(); + + let mut class_head = context.queries.intern_type(Type::Constructor(context.id, item_id)); + + for binder in &class_binders { + let rigid = context.intern_rigid(binder.name, state.depth, binder.kind); + class_head = context.intern_kind_application(class_head, rigid); + } + + for (index, parameter) in parameters.iter().enumerate() { + let kind = get_parameter_kind(index); + let rigid = context.intern_rigid(parameter.name, state.depth, kind); + class_head = context.intern_application(class_head, rigid); + } + + let mut canonical = class_head; + for type_parameter in type_parameters.iter().rev() { + canonical = context.intern_forall(*type_parameter, canonical); + } + for kind_binder in kind_binders.iter().rev() { + canonical = context.intern_forall(*kind_binder, canonical); + } + + let superclasses = superclasses + .into_iter() + .map(|superclass| zonk::zonk(state, context, superclass)) + .collect::>>()?; + + for (member_id, member_type) in members.iter() { + let member_type = zonk::zonk(state, context, *member_type)?; + + let signature::DecomposedSignature { + binders: member_binders, + constraints: member_constraints, + arguments: member_arguments, + result: member_result, + } = signature::decompose_signature( + state, + context, + member_type, + signature::DecomposeSignatureMode::Full, + )?; + + let mut result = context.intern_function_chain(&member_arguments, member_result); + + for constraint in member_constraints.into_iter().rev() { + result = context.intern_constrained(constraint, result); + } + + result = context.intern_constrained(class_head, result); + + for member_binder in member_binders.iter().copied().rev() { + let binder_id = context.intern_forall_binder(member_binder); + result = context.intern_forall(binder_id, result); + } + + for type_parameter in type_parameters.iter().rev() { + result = context.intern_forall(*type_parameter, result); + } + + for kind_binder in kind_binders.iter().rev() { + result = context.intern_forall(*kind_binder, result); + } + + state.checked.terms.insert(*member_id, result); + } + + let members = members.into_iter().map(|(item_id, _)| item_id).collect_vec(); + + state.checked.classes.insert( + item_id, + CheckedClass { + kind_binders, + type_parameters, + canonical, + superclasses, + functional_dependencies, + members, + }, + ); + } + + Ok(()) +} + +fn check_type_operator( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TypeSccState, + item_id: TypeItemId, + resolution: Option<(FileId, TypeItemId)>, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + let Some((file_id, type_id)) = resolution else { return Ok(()) }; + let operator_kind = toolkit::lookup_file_type_operator(state, context, file_id, type_id)?; + + if let Some(item_kind) = state.checked.lookup_type(item_id) { + unification::subtype(state, context, operator_kind, item_kind)?; + } else { + state.checked.types.insert(item_id, operator_kind); + } + + scc.operator.push(item_id); + + Ok(()) +} + +fn finalise_type_operators( + state: &mut CheckState, + context: &CheckContext, + scc: &mut TypeSccState, +) -> QueryResult<()> +where + Q: ExternalQueries, +{ + for item_id in mem::take(&mut scc.operator) { + let Some(kind) = state.checked.types.get(&item_id).copied() else { + continue; + }; + + if !toolkit::is_binary_operator_type(state, context, kind)? { + let kind_message = state.pretty_id(context, kind)?; + state.insert_error(ErrorKind::InvalidTypeOperator { kind_message }); + } + } + + Ok(()) +} diff --git a/compiler-core/checking2/src/source/types.rs b/compiler-core/checking2/src/source/types.rs new file mode 100644 index 00000000..3dc50508 --- /dev/null +++ b/compiler-core/checking2/src/source/types.rs @@ -0,0 +1,615 @@ +//! Implements syntax-driven checking rules for types. + +use std::sync::Arc; + +use building_types::QueryResult; +use smol_str::SmolStr; + +use crate::context::CheckContext; +use crate::core::substitute::SubstituteName; +use crate::core::{ + ForallBinder, KindOrType, RowField, RowType, Type, TypeId, normalise, toolkit, unification, +}; +use crate::error::ErrorCrumb; +use crate::source::{operator, synonym}; +use crate::state::CheckState; +use crate::{ExternalQueries, safe_loop}; + +const MISSING_NAME: SmolStr = SmolStr::new_static(""); + +/// Checks the kind of a syntax type against a core type. +/// +/// This function returns the core type and kind. +pub fn check_kind( + state: &mut CheckState, + context: &CheckContext, + source_type: lowering::TypeId, + expected_kind: TypeId, +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + state.with_error_crumb(ErrorCrumb::CheckingKind(source_type), |state| { + check_kind_core(state, context, source_type, expected_kind) + }) +} + +fn check_kind_core( + state: &mut CheckState, + context: &CheckContext, + source_type: lowering::TypeId, + expected_kind: TypeId, +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + let (inferred_type, inferred_kind) = infer_kind(state, context, source_type)?; + let (inferred_type, inferred_kind) = + instantiate_kind_applications(state, context, inferred_type, inferred_kind, expected_kind)?; + + unification::subtype(state, context, inferred_kind, expected_kind)?; + Ok((inferred_type, inferred_kind)) +} + +pub fn infer_kind( + state: &mut CheckState, + context: &CheckContext, + id: lowering::TypeId, +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + state.with_error_crumb(ErrorCrumb::InferringKind(id), |state| { + infer_kind_core(state, context, id) + }) +} + +fn infer_kind_core( + state: &mut CheckState, + context: &CheckContext, + id: lowering::TypeId, +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + let unknown = |message: &str| { + let u = context.unknown(message); + (u, u) + }; + + let Some(kind) = context.lowered.info.get_type_kind(id) else { + return Ok(unknown("missing syntax")); + }; + + match kind { + lowering::TypeKind::ApplicationChain { function, arguments } => { + let Some(function) = function else { + return Ok(unknown("missing application function")); + }; + + if let Some(synonym) = synonym::parse_synonym(state, context, *function)? { + return synonym::infer_synonym_application(state, context, id, synonym, arguments); + } + + let (mut t, mut k) = infer_kind(state, context, *function)?; + + for argument in arguments.iter() { + (t, k) = infer_application_kind(state, context, (t, k), *argument)?; + } + + Ok((t, k)) + } + + lowering::TypeKind::Arrow { argument, result } => { + let argument = if let Some(argument) = argument { + let (argument, _) = check_kind(state, context, *argument, context.prim.t)?; + argument + } else { + context.unknown("missing function argument") + }; + + let result = if let Some(result) = result { + let (result, _) = check_kind(state, context, *result, context.prim.t)?; + result + } else { + context.unknown("missing function result") + }; + + let t = context.intern_function(argument, result); + let k = context.prim.t; + + Ok((t, k)) + } + + lowering::TypeKind::Constrained { constraint, constrained } => { + let constraint = if let Some(constraint) = constraint { + let (constraint, _) = + check_kind(state, context, *constraint, context.prim.constraint)?; + constraint + } else { + context.unknown("missing constraint") + }; + + let constrained = if let Some(constrained) = constrained { + let (constrained, _) = infer_kind(state, context, *constrained)?; + constrained + } else { + context.unknown("missing constrained") + }; + + let t = context.intern_constrained(constraint, constrained); + let k = context.prim.t; + + Ok((t, k)) + } + + lowering::TypeKind::Constructor { resolution } => { + let Some((file_id, type_id)) = *resolution else { + return Ok(unknown("missing constructor")); + }; + + if let Some(synonym) = synonym::parse_synonym(state, context, id)? { + return synonym::infer_synonym_constructor(state, context, synonym, id); + } + + let t = context.queries.intern_type(Type::Constructor(file_id, type_id)); + let k = toolkit::lookup_file_type(state, context, file_id, type_id)?; + + Ok((t, k)) + } + + lowering::TypeKind::Forall { bindings, inner } => { + let binders = bindings + .iter() + .map(|binding| check_type_variable_binding(state, context, binding)) + .collect::>>()?; + + let inner = if let Some(inner) = inner { + let (inner, _) = infer_kind(state, context, *inner)?; + inner + } else { + context.unknown("missing forall inner") + }; + + let t = binders.iter().rfold(inner, |inner, binder| { + let binder_id = context.intern_forall_binder(*binder); + context.intern_forall(binder_id, inner) + }); + + let k = context.prim.t; + + Ok((t, k)) + } + + lowering::TypeKind::Hole => { + let k = state.fresh_unification(context.queries, context.prim.t); + let t = state.fresh_unification(context.queries, k); + Ok((t, k)) + } + + lowering::TypeKind::Integer { value } => { + let t = if let Some(value) = value { + context.queries.intern_type(Type::Integer(*value)) + } else { + context.unknown("missing integer value") + }; + Ok((t, context.prim.int)) + } + + lowering::TypeKind::Kinded { type_, kind } => { + let k = if let Some(kind) = kind { + let (k, _) = infer_kind(state, context, *kind)?; + k + } else { + context.unknown("missing kinded kind") + }; + let t = if let Some(type_) = type_ { + let (t, _) = check_kind(state, context, *type_, k)?; + t + } else { + context.unknown("missing kinded type") + }; + Ok((t, k)) + } + + lowering::TypeKind::Operator { resolution } => { + let Some((file_id, type_id)) = *resolution else { + return Ok(unknown("missing operator")); + }; + + let Some((file_id, type_id)) = + toolkit::resolve_type_operator_target(context, file_id, type_id)? + else { + return Ok(unknown("missing operator")); + }; + + let t = context.queries.intern_type(Type::Constructor(file_id, type_id)); + let k = toolkit::lookup_file_type(state, context, file_id, type_id)?; + + Ok((t, k)) + } + + lowering::TypeKind::OperatorChain { .. } => { + operator::infer_operator_chain(state, context, id) + } + + lowering::TypeKind::String { kind, value } => { + let value = value.clone().unwrap_or(MISSING_NAME); + let id = context.queries.intern_smol_str(value); + + let t = context.queries.intern_type(Type::String(*kind, id)); + let k = context.prim.symbol; + + Ok((t, k)) + } + + lowering::TypeKind::Variable { name, resolution } => match resolution { + Some(lowering::TypeVariableResolution::Forall(forall)) => { + let (n, k) = state + .bindings + .lookup_forall(*forall) + .expect("invariant violated: KindScope::bind_forall"); + + let t = context.intern_rigid(n, state.depth, k); + + Ok((t, k)) + } + + Some(lowering::TypeVariableResolution::Implicit(implicit)) => { + if implicit.binding { + let n = state.names.fresh(); + let k = state.fresh_unification(context.queries, context.prim.t); + + state.bindings.bind_implicit(implicit.node, implicit.id, n, k); + let t = context.intern_rigid(n, state.depth, k); + + Ok((t, k)) + } else { + let (n, k) = state + .bindings + .lookup_implicit(implicit.node, implicit.id) + .expect("invariant violated: KindScope::bind_implicit"); + + let t = context.intern_rigid(n, state.depth, k); + + Ok((t, k)) + } + } + + None => { + let name = name.clone().unwrap_or(MISSING_NAME); + let id = context.queries.intern_smol_str(name); + + let t = context.queries.intern_type(Type::Free(id)); + let k = state.fresh_unification(context.queries, context.prim.t); + + Ok((t, k)) + } + }, + + lowering::TypeKind::Wildcard => { + let k = state.fresh_unification(context.queries, context.prim.t); + let t = state.fresh_unification(context.queries, k); + Ok((t, k)) + } + + lowering::TypeKind::Record { items, tail } => { + let (row_type, row_kind) = infer_row_kind(state, context, items, tail)?; + unification::subtype(state, context, row_kind, context.prim.row_type)?; + + let t = context.intern_application(context.prim.record, row_type); + let k = context.prim.t; + + Ok((t, k)) + } + + lowering::TypeKind::Row { items, tail } => infer_row_kind(state, context, items, tail), + + lowering::TypeKind::Parenthesized { parenthesized } => { + if let Some(parenthesized) = parenthesized { + infer_kind(state, context, *parenthesized) + } else { + Ok(unknown("missing parenthesized")) + } + } + } +} + +/// Instantiates kind-level foralls using [`Type::KindApplication`]. +/// +/// If the inferred kind is polymorphic and the expected kind is monomorphic, +/// this function adds the necessary kind applications to the inferred type. +fn instantiate_kind_applications( + state: &mut CheckState, + context: &CheckContext, + mut t: TypeId, + mut k: TypeId, + expected_kind: TypeId, +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + let expected_kind = normalise::normalise(state, context, expected_kind)?; + + if matches!(context.lookup_type(expected_kind), Type::Forall(_, _)) { + return Ok((t, k)); + } + + safe_loop! { + k = normalise::normalise(state, context, k)?; + + let Type::Forall(binder_id, inner_kind) = context.lookup_type(k) else { + break; + }; + + let binder = context.lookup_forall_binder(binder_id); + let binder_kind = normalise::normalise(state, context, binder.kind)?; + + let argument_type = state.fresh_unification(context.queries, binder_kind); + t = context.intern_kind_application(t, argument_type); + k = SubstituteName::one(state, context, binder.name, argument_type, inner_kind)?; + } + + Ok((t, k)) +} + +fn check_type_variable_binding( + state: &mut CheckState, + context: &CheckContext, + binding: &lowering::TypeVariableBinding, +) -> QueryResult +where + Q: ExternalQueries, +{ + let kind = if let Some(kind_id) = binding.kind { + let (kind, _) = check_kind(state, context, kind_id, context.prim.t)?; + kind + } else { + state.fresh_unification(context.queries, context.prim.t) + }; + + let visible = binding.visible; + let name = state.names.fresh(); + + let text = if let Some(name) = &binding.name { SmolStr::clone(name) } else { name.as_text() }; + let text = context.queries.intern_smol_str(text); + + state.checked.names.insert(name, text); + state.bindings.bind_forall(binding.id, name, kind); + Ok(ForallBinder { visible, name, kind }) +} + +pub fn infer_application_kind( + state: &mut CheckState, + context: &CheckContext, + (function_type, function_kind): (TypeId, TypeId), + argument: lowering::TypeId, +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + let function_kind = normalise::normalise(state, context, function_kind)?; + + match context.lookup_type(function_kind) { + Type::Function(argument_kind, result_kind) => { + let (argument_type, _) = check_kind(state, context, argument, argument_kind)?; + + let result_type = context.intern_application(function_type, argument_type); + let result_kind = normalise::normalise(state, context, result_kind)?; + + Ok((result_type, result_kind)) + } + + Type::Unification(unification_id) => { + let argument_kind = state.fresh_unification(context.queries, context.prim.t); + let result_kind = state.fresh_unification(context.queries, context.prim.t); + + let function = context.intern_function(argument_kind, result_kind); + unification::solve(state, context, function_kind, unification_id, function)?; + + let (argument_type, _) = check_kind(state, context, argument, argument_kind)?; + + let result_type = context.intern_application(function_type, argument_type); + let result_kind = normalise::normalise(state, context, result_kind)?; + + Ok((result_type, result_kind)) + } + + Type::Forall(binder_id, inner_kind) => { + let binder = context.lookup_forall_binder(binder_id); + let binder_kind = normalise::normalise(state, context, binder.kind)?; + + let kind_argument = state.fresh_unification(context.queries, binder_kind); + let function_type = context.intern_kind_application(function_type, kind_argument); + let function_kind = + SubstituteName::one(state, context, binder.name, kind_argument, inner_kind)?; + + infer_application_kind(state, context, (function_type, function_kind), argument) + } + + _ => { + // Even if the function type cannot be applied, the argument must + // still be inferred. For invalid applications on instance heads, + // this ensures that implicit variables are bound. + let (argument_type, _) = infer_kind(state, context, argument)?; + + let result_type = context.intern_application(function_type, argument_type); + let result_kind = context.unknown("cannot apply function type"); + + toolkit::report_invalid_type_application( + state, + context, + function_type, + function_kind, + argument_type, + )?; + + Ok((result_type, result_kind)) + } + } +} + +fn infer_row_kind( + state: &mut CheckState, + context: &CheckContext, + items: &Arc<[lowering::TypeRowItem]>, + tail: &Option, +) -> QueryResult<(TypeId, TypeId)> +where + Q: ExternalQueries, +{ + let field_kind = state.fresh_unification(context.queries, context.prim.t); + let row_kind = context.intern_application(context.prim.row, field_kind); + + let fields = items.iter().map(|item| { + let label = item.name.clone().unwrap_or(MISSING_NAME); + let id = if let Some(t) = item.type_ { + let (t, k) = infer_kind(state, context, t)?; + unification::unify(state, context, field_kind, k)?; + t + } else { + context.unknown("missing field type") + }; + + Ok(RowField { label, id }) + }); + + let fields = fields.collect::>>()?; + + let tail = if let Some(tail) = tail { + let (tail_type, tail_kind) = infer_kind(state, context, *tail)?; + unification::subtype(state, context, tail_kind, row_kind)?; + Some(tail_type) + } else { + None + }; + + let row = RowType::new(fields, tail); + let row_id = context.intern_row_type(row); + let row_type = context.intern_row(row_id); + + Ok((row_type, row_kind)) +} + +pub fn elaborate_kind( + state: &mut CheckState, + context: &CheckContext, + id: TypeId, +) -> QueryResult +where + Q: ExternalQueries, +{ + let unknown = context.unknown("invalid kind"); + let id = normalise::normalise(state, context, id)?; + + let kind = match context.lookup_type(id) { + Type::Application(function, _) => { + let function_kind = elaborate_kind(state, context, function)?; + let function_kind = normalise::normalise(state, context, function_kind)?; + + match context.lookup_type(function_kind) { + Type::Function(_, result_kind) => result_kind, + + Type::Unification(unification_id) => { + let depth = state.unifications.get(unification_id).depth; + + let argument_u = state.unifications.fresh(depth, context.prim.t); + let argument_u = context.queries.intern_type(Type::Unification(argument_u)); + + let result_u = state.unifications.fresh(depth, context.prim.t); + let result_u = context.queries.intern_type(Type::Unification(result_u)); + + let function_u = context.intern_function(argument_u, result_u); + unification::solve(state, context, function_kind, unification_id, function_u)?; + + result_u + } + + _ => unknown, + } + } + + Type::KindApplication(function, argument) => { + let function_kind = elaborate_kind(state, context, function)?; + let function_kind = normalise::normalise(state, context, function_kind)?; + + match context.lookup_type(function_kind) { + Type::Forall(binder_id, inner_kind) => { + let binder = context.lookup_forall_binder(binder_id); + let argument = normalise::normalise(state, context, argument)?; + SubstituteName::one(state, context, binder.name, argument, inner_kind)? + } + _ => unknown, + } + } + + Type::Constrained(_, _) => context.prim.t, + Type::Forall(_, _) => context.prim.t, + Type::Function(_, _) => context.prim.t, + + Type::Constructor(file_id, type_id) => { + toolkit::lookup_file_type(state, context, file_id, type_id)? + } + + Type::Integer(_) => context.prim.int, + Type::String(_, _) => context.prim.symbol, + Type::Kinded(_, kind) => kind, + + Type::Row(row_id) => { + let row = context.lookup_row_type(row_id); + let fields = Arc::clone(&row.fields); + + let field_kind = state.fresh_unification(context.queries, context.prim.t); + let tail_kind = context.intern_application(context.prim.row, field_kind); + + for field in fields.iter() { + let kind = elaborate_kind(state, context, field.id)?; + unification::unify(state, context, field_kind, kind)?; + } + + if let Some(tail) = row.tail { + let kind = elaborate_kind(state, context, tail)?; + unification::unify(state, context, tail_kind, kind)?; + } + + context.intern_application(context.prim.row, field_kind) + } + + Type::SynonymApplication(synonym_id) => { + let synonym = context.lookup_synonym(synonym_id); + let (file_id, type_id) = synonym.reference; + let arguments = Arc::clone(&synonym.arguments); + + let mut synonym_kind = toolkit::lookup_file_type(state, context, file_id, type_id)?; + + for application in arguments.iter() { + synonym_kind = normalise::normalise(state, context, synonym_kind)?; + match (application, context.lookup_type(synonym_kind)) { + (KindOrType::Kind(argument), Type::Forall(binder_id, inner_kind)) => { + let binder = context.lookup_forall_binder(binder_id); + synonym_kind = SubstituteName::one( + state, + context, + binder.name, + *argument, + inner_kind, + )?; + } + (KindOrType::Type(_), Type::Function(_, result_kind)) => { + synonym_kind = result_kind; + } + _ => return Ok(unknown), + } + } + + synonym_kind + } + + Type::Unification(unification_id) => state.unifications.get(unification_id).kind, + Type::Rigid(_, _, kind) => kind, + Type::Free(_) => unknown, + Type::Unknown(_) => unknown, + }; + + Ok(kind) +} diff --git a/compiler-core/checking2/src/state.rs b/compiler-core/checking2/src/state.rs new file mode 100644 index 00000000..383f1679 --- /dev/null +++ b/compiler-core/checking2/src/state.rs @@ -0,0 +1,317 @@ +//! Implements the algorithm's core state structures. + +use std::mem; +use std::sync::Arc; + +use building_types::QueryResult; +use files::FileId; +use rustc_hash::FxHashMap; + +use crate::context::CheckContext; +use crate::core::exhaustive::{ + ExhaustivenessReport, Pattern, PatternConstructor, PatternId, PatternInterner, PatternKind, +}; +use crate::core::substitute::{NameToType, SubstituteName}; +use crate::core::{Depth, Name, SmolStrId, Type, TypeId, constraint, pretty, zonk}; +use crate::error::{CheckError, ErrorCrumb, ErrorKind}; +use crate::implication::Implications; +use crate::{CheckedModule, ExternalQueries}; + +/// Manages [`Name`] values for [`CheckState`]. +pub struct Names { + unique: u32, + file: FileId, +} + +impl Names { + pub fn new(file: FileId) -> Names { + Names { unique: 0, file } + } + + pub fn fresh(&mut self) -> Name { + let unique = self.unique; + self.unique += 1; + Name { file: self.file, unique } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum UnificationState { + Unsolved, + Solved(TypeId), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct UnificationEntry { + pub depth: Depth, + pub kind: TypeId, + pub state: UnificationState, +} + +/// Manages unification variables for [`CheckState`]. +#[derive(Debug, Default)] +pub struct Unifications { + entries: Vec, + unique: u32, +} + +impl Unifications { + pub fn fresh(&mut self, depth: Depth, kind: TypeId) -> u32 { + let unique = self.unique; + + self.unique += 1; + self.entries.push(UnificationEntry { depth, kind, state: UnificationState::Unsolved }); + + unique + } + + pub fn get(&self, index: u32) -> &UnificationEntry { + &self.entries[index as usize] + } + + pub fn get_mut(&mut self, index: u32) -> &mut UnificationEntry { + &mut self.entries[index as usize] + } + + pub fn solve(&mut self, index: u32, solution: TypeId) { + self.get_mut(index).state = UnificationState::Solved(solution); + } + + pub fn iter(&self) -> impl Iterator { + self.entries.iter() + } +} + +/// Tracks type variable bindings during kind inference. +#[derive(Default)] +pub struct Bindings { + forall: FxHashMap, + implicit: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct ImplicitBinding { + node: lowering::GraphNodeId, + id: lowering::ImplicitBindingId, + name: Name, + kind: TypeId, +} + +impl Bindings { + pub fn bind_forall(&mut self, id: lowering::TypeVariableBindingId, name: Name, kind: TypeId) { + self.forall.insert(id, (name, kind)); + } + + pub fn lookup_forall(&self, id: lowering::TypeVariableBindingId) -> Option<(Name, TypeId)> { + self.forall.get(&id).copied() + } + + pub fn bind_implicit( + &mut self, + node: lowering::GraphNodeId, + id: lowering::ImplicitBindingId, + name: Name, + kind: TypeId, + ) { + self.implicit.push(ImplicitBinding { node, id, name, kind }); + } + + fn bind_implicit_substitution( + state: &mut CheckState, + context: &CheckContext, + substitution: &NameToType, + ) -> QueryResult<()> + where + Q: ExternalQueries, + { + let scope = state.bindings.implicit.len(); + + for binding in 0..scope { + let ImplicitBinding { node, id, name, kind } = state.bindings.implicit[binding]; + let Some(&replacement) = substitution.get(&name) else { continue }; + + let Type::Rigid(name, _, _) = context.lookup_type(replacement) else { + unreachable!("invariant violated: expected a rigid variable"); + }; + + let kind = SubstituteName::many(state, context, substitution, kind)?; + state.bindings.implicit.push(ImplicitBinding { node, id, name, kind }); + } + + Ok(()) + } + + pub fn lookup_implicit( + &self, + node: lowering::GraphNodeId, + id: lowering::ImplicitBindingId, + ) -> Option<(Name, TypeId)> { + self.implicit + .iter() + .rev() + .find(|binding| binding.node == node && binding.id == id) + .map(|binding| (binding.name, binding.kind)) + } +} + +/// The core state structure threaded through the algorithm. +pub struct CheckState { + pub checked: CheckedModule, + + pub names: Names, + pub bindings: Bindings, + pub patterns: PatternInterner, + + pub unifications: Unifications, + pub implications: Implications, + + pub defer_expansion: bool, + pub depth: Depth, + + pub crumbs: Vec, +} + +impl CheckState { + pub fn new(file_id: FileId) -> CheckState { + CheckState { + checked: Default::default(), + names: Names::new(file_id), + bindings: Default::default(), + patterns: Default::default(), + unifications: Default::default(), + implications: Default::default(), + defer_expansion: Default::default(), + depth: Depth(0), + crumbs: Default::default(), + } + } + + pub fn with_depth(&mut self, f: impl FnOnce(&mut CheckState) -> T) -> T { + let depth = self.depth.increment(); + + let previous = mem::replace(&mut self.depth, depth); + let result = f(self); + self.depth = previous; + + result + } + + pub fn with_defer_expansion(&mut self, f: impl FnOnce(&mut CheckState) -> T) -> T { + let previous = mem::replace(&mut self.defer_expansion, true); + let result = f(self); + self.defer_expansion = previous; + result + } + + pub fn with_error_crumb(&mut self, crumb: ErrorCrumb, f: F) -> T + where + F: FnOnce(&mut CheckState) -> T, + { + self.crumbs.push(crumb); + let result = f(self); + self.crumbs.pop(); + result + } + + pub fn fresh_unification(&mut self, queries: &impl ExternalQueries, kind: TypeId) -> TypeId { + let unification = self.unifications.fresh(self.depth, kind); + queries.intern_type(Type::Unification(unification)) + } + + pub fn fresh_rigid(&mut self, queries: &impl ExternalQueries, kind: TypeId) -> TypeId { + let name = self.names.fresh(); + queries.intern_type(Type::Rigid(name, self.depth, kind)) + } + + pub fn insert_error(&mut self, kind: ErrorKind) { + let crumbs = self.crumbs.iter().copied().collect(); + self.checked.errors.push(CheckError { kind, crumbs }); + } + + pub fn push_wanted(&mut self, constraint: TypeId) { + self.implications.current_mut().wanted.push_back(constraint); + } + + pub fn push_given(&mut self, constraint: TypeId) { + self.implications.current_mut().given.push(constraint); + } + + pub fn with_implication(&mut self, f: impl FnOnce(&mut CheckState) -> T) -> T { + let id = self.implications.push(); + let result = f(self); + self.implications.pop(id); + result + } + + pub fn with_implicit( + &mut self, + context: &CheckContext, + substitution: &NameToType, + f: impl FnOnce(&mut CheckState) -> QueryResult, + ) -> QueryResult + where + Q: ExternalQueries, + { + let scope = self.bindings.implicit.len(); + Bindings::bind_implicit_substitution(self, context, substitution)?; + let result = f(self); + self.bindings.implicit.truncate(scope); + + result + } + + pub fn solve_constraints(&mut self, context: &CheckContext) -> QueryResult> + where + Q: ExternalQueries, + { + constraint::solve_implication(self, context) + } + + pub fn pretty_id(&mut self, context: &CheckContext, id: TypeId) -> QueryResult + where + Q: ExternalQueries, + { + let id = zonk::zonk(self, context, id)?; + let pretty = pretty::Pretty::new(context.queries, &self.checked).render(id); + Ok(context.queries.intern_smol_str(pretty)) + } + + pub fn report_exhaustiveness( + &mut self, + context: &CheckContext, + exhaustiveness: ExhaustivenessReport, + ) where + Q: ExternalQueries, + { + if let Some(patterns) = exhaustiveness.missing { + let patterns: Vec<_> = patterns + .into_iter() + .map(|pattern| context.queries.intern_smol_str(pattern)) + .collect(); + self.insert_error(ErrorKind::MissingPatterns { patterns: Arc::from(patterns) }); + } + + if !exhaustiveness.redundant.is_empty() { + let patterns = Arc::from(exhaustiveness.redundant); + self.insert_error(ErrorKind::RedundantPatterns { patterns }); + } + } + + pub fn allocate_pattern(&mut self, kind: PatternKind, t: TypeId) -> PatternId { + let pattern = Pattern { kind, t }; + self.patterns.intern(pattern) + } + + pub fn allocate_constructor( + &mut self, + constructor: PatternConstructor, + t: TypeId, + ) -> PatternId { + let kind = PatternKind::Constructor { constructor }; + self.allocate_pattern(kind, t) + } + + pub fn allocate_wildcard(&mut self, t: TypeId) -> PatternId { + self.allocate_pattern(PatternKind::Wildcard, t) + } +} diff --git a/compiler-core/lowering/src/lib.rs b/compiler-core/lowering/src/lib.rs index 190ab229..5d5c3fc0 100644 --- a/compiler-core/lowering/src/lib.rs +++ b/compiler-core/lowering/src/lib.rs @@ -9,6 +9,7 @@ pub mod scope; pub mod source; use std::hash::Hash; +use std::slice; use std::sync::Arc; pub use error::*; @@ -54,6 +55,19 @@ pub enum Scc { Mutual(Vec), } +impl Scc { + pub fn as_slice(&self) -> &[T] { + match self { + Scc::Base(item) | Scc::Recursive(item) => slice::from_ref(item), + Scc::Mutual(items) => items.as_slice(), + } + } + + pub fn is_recursive(&self) -> bool { + matches!(self, Scc::Recursive(..) | Scc::Mutual(..)) + } +} + pub fn lower_module( file_id: FileId, module: &cst::Module, diff --git a/compiler-core/stabilizing/src/id.rs b/compiler-core/stabilizing/src/id.rs index a52389dc..9e805699 100644 --- a/compiler-core/stabilizing/src/id.rs +++ b/compiler-core/stabilizing/src/id.rs @@ -38,6 +38,18 @@ impl> PartialEq for AstId { impl> Eq for AstId {} +impl> PartialOrd for AstId { + fn partial_cmp(&self, other: &AstId) -> Option { + Some(self.cmp(other)) + } +} + +impl> Ord for AstId { + fn cmp(&self, other: &AstId) -> std::cmp::Ordering { + self.id.cmp(&other.id) + } +} + impl> hash::Hash for AstId { fn hash(&self, state: &mut H) { self.id.hash(state); diff --git a/compiler-lsp/analyzer/src/completion/sources.rs b/compiler-lsp/analyzer/src/completion/sources.rs index 4577b11c..d87d8489 100644 --- a/compiler-lsp/analyzer/src/completion/sources.rs +++ b/compiler-lsp/analyzer/src/completion/sources.rs @@ -732,12 +732,8 @@ fn suggestions_candidates_qualified( filter: impl Filter, items: &mut Vec, ) -> Result<(), AnalyzerError> { - let has_prim = context - .resolved - .qualified - .values() - .flatten() - .any(|import| import.file == context.prim_id); + let has_prim = + context.resolved.qualified.values().flatten().any(|import| import.file == context.prim_id); let file_ids = context.files.iter_id().filter(move |&id| { let not_self = id != context.current_file; diff --git a/compiler-scripts/src/lib.rs b/compiler-scripts/src/lib.rs index 0ab89a1a..771fdd5b 100644 --- a/compiler-scripts/src/lib.rs +++ b/compiler-scripts/src/lib.rs @@ -56,6 +56,7 @@ pub mod fixtures { ("LOWERING_FIXTURES_HASH".into(), hash_fixtures(&base.join("lowering"))), ("RESOLVING_FIXTURES_HASH".into(), hash_fixtures(&base.join("resolving"))), ("CHECKING_FIXTURES_HASH".into(), hash_fixtures(&base.join("checking"))), + ("CHECKING2_FIXTURES_HASH".into(), hash_fixtures(&base.join("checking2"))), ]) } } diff --git a/compiler-scripts/src/main.rs b/compiler-scripts/src/main.rs index 89d36188..0d5669da 100644 --- a/compiler-scripts/src/main.rs +++ b/compiler-scripts/src/main.rs @@ -2,14 +2,14 @@ use clap::Parser; use console::style; use compiler_scripts::test_runner::{ - CategoryCommand, DeleteFixtureOutcome, RunArgs, TestCategory, accept_category, create_fixture, - delete_fixture, reject_category, run_category, + DeleteFixtureOutcome, RunArgs, TestCategory, accept_category, create_fixture, delete_fixture, + reject_category, run_category, }; #[derive(Parser)] #[command(about = "Compiler development scripts")] struct Cli { - /// Test category: checking (c), lowering (l), resolving (r), lsp + /// Test category: checking (c), checking2 (c2), lowering (l), resolving (r), lsp category: TestCategory, #[command(flatten)] @@ -78,42 +78,47 @@ fn main() { return; } - match &cli.args.command { - Some(CategoryCommand::Accept(args)) => { - let outcome = match accept_category(cli.category, args) { - Ok(outcome) => outcome, - Err(error) => { - eprintln!("{:#}", error); - std::process::exit(1); - } - }; - if !outcome.success { + if cli.args.accept && cli.args.reject { + eprintln!("--accept and --reject cannot be used together"); + std::process::exit(2); + } + + if cli.args.accept { + let outcome = match accept_category(cli.category, &cli.args.filters, cli.args.confirm) { + Ok(outcome) => outcome, + Err(error) => { + eprintln!("{:#}", error); std::process::exit(1); } + }; + if !outcome.success { + std::process::exit(1); } - Some(CategoryCommand::Reject(args)) => { - let outcome = match reject_category(cli.category, args) { - Ok(outcome) => outcome, - Err(error) => { - eprintln!("{:#}", error); - std::process::exit(1); - } - }; - if !outcome.success { + return; + } + + if cli.args.reject { + let outcome = match reject_category(cli.category, &cli.args.filters) { + Ok(outcome) => outcome, + Err(error) => { + eprintln!("{:#}", error); std::process::exit(1); } + }; + if !outcome.success { + std::process::exit(1); } - None => { - let outcome = match run_category(cli.category, &cli.args) { - Ok(outcome) => outcome, - Err(error) => { - eprintln!("{:#}", error); - std::process::exit(1); - } - }; - if !outcome.tests_passed { - std::process::exit(1); - } + return; + } + + let outcome = match run_category(cli.category, &cli.args) { + Ok(outcome) => outcome, + Err(error) => { + eprintln!("{:#}", error); + std::process::exit(1); } + }; + if !outcome.tests_passed { + std::process::exit(1); } } diff --git a/compiler-scripts/src/test_runner.rs b/compiler-scripts/src/test_runner.rs index 1e983413..a8bc3065 100644 --- a/compiler-scripts/src/test_runner.rs +++ b/compiler-scripts/src/test_runner.rs @@ -8,7 +8,7 @@ mod traces; mod ui; pub use category::TestCategory; -pub use cli::{CategoryCommand, RunArgs, SnapshotArgs}; +pub use cli::RunArgs; use std::path::PathBuf; use std::time::Instant; @@ -61,16 +61,17 @@ pub struct SnapshotOutcome { pub fn accept_category( category: TestCategory, - args: &SnapshotArgs, + filters: &[String], + confirm: bool, ) -> anyhow::Result { - let snapshots = pending::collect_pending_snapshots(category, &args.filters)?; + let snapshots = pending::collect_pending_snapshots(category, filters)?; if snapshots.is_empty() { println!("{}", style("No pending snapshots found.").dim()); return Ok(SnapshotOutcome { success: true, count: 0 }); } - if !args.all && args.filters.is_empty() { + if !confirm && filters.is_empty() { println!("{} pending snapshot(s) in {}", snapshots.len(), style(category.as_str()).cyan()); println!(); for info in &snapshots { @@ -79,7 +80,7 @@ pub fn accept_category( println!(); println!( "To accept all, run: {}", - style(format!("just t {} accept --all", category.as_str())).cyan() + style(format!("just t {} --accept --confirm", category.as_str())).cyan() ); return Ok(SnapshotOutcome { success: false, count: 0 }); } @@ -93,29 +94,15 @@ pub fn accept_category( pub fn reject_category( category: TestCategory, - args: &SnapshotArgs, + filters: &[String], ) -> anyhow::Result { - let snapshots = pending::collect_pending_snapshots(category, &args.filters)?; + let snapshots = pending::collect_pending_snapshots(category, filters)?; if snapshots.is_empty() { println!("{}", style("No pending snapshots found.").dim()); return Ok(SnapshotOutcome { success: true, count: 0 }); } - if !args.all && args.filters.is_empty() { - println!("{} pending snapshot(s) in {}", snapshots.len(), style(category.as_str()).cyan()); - println!(); - for info in &snapshots { - println!(" {}", style(&info.short_path).dim()); - } - println!(); - println!( - "To reject all, run: {}", - style(format!("just t {} reject --all", category.as_str())).cyan() - ); - return Ok(SnapshotOutcome { success: false, count: 0 }); - } - let result = pending::reject_snapshots(&snapshots); println!(); println!("{}", style(format!("Rejected {} snapshot(s)", result.rejected)).red()); diff --git a/compiler-scripts/src/test_runner/category.rs b/compiler-scripts/src/test_runner/category.rs index 932770e2..c3d806c8 100644 --- a/compiler-scripts/src/test_runner/category.rs +++ b/compiler-scripts/src/test_runner/category.rs @@ -6,6 +6,7 @@ use anyhow::bail; #[derive(Copy, Clone, Debug)] pub enum TestCategory { Checking, + Checking2, Lowering, Resolving, Lsp, @@ -15,6 +16,7 @@ impl TestCategory { pub fn as_str(&self) -> &'static str { match self { TestCategory::Checking => "checking", + TestCategory::Checking2 => "checking2", TestCategory::Lowering => "lowering", TestCategory::Resolving => "resolving", TestCategory::Lsp => "lsp", @@ -38,7 +40,7 @@ impl TestCategory { pub fn trace_for_snapshot(&self, snap_path: &Path, trace_paths: &[PathBuf]) -> Option { match self { - TestCategory::Checking => { + TestCategory::Checking | TestCategory::Checking2 => { crate::test_runner::traces::match_checking_trace(snap_path, trace_paths) } _ => None, @@ -52,11 +54,12 @@ impl FromStr for TestCategory { fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "checking" | "c" => Ok(TestCategory::Checking), + "checking2" | "c2" => Ok(TestCategory::Checking2), "lowering" | "l" => Ok(TestCategory::Lowering), "resolving" | "r" => Ok(TestCategory::Resolving), "lsp" => Ok(TestCategory::Lsp), _ => bail!( - "unknown test category '{}', expected: checking (c), lowering (l), resolving (r), lsp", + "unknown test category '{}', expected: checking (c), checking2 (c2), lowering (l), resolving (r), lsp", s ), } diff --git a/compiler-scripts/src/test_runner/cli.rs b/compiler-scripts/src/test_runner/cli.rs index fae93f76..33d4972b 100644 --- a/compiler-scripts/src/test_runner/cli.rs +++ b/compiler-scripts/src/test_runner/cli.rs @@ -1,23 +1,4 @@ -use clap::{Args, Subcommand}; - -#[derive(Subcommand, Clone, Debug)] -pub enum CategoryCommand { - /// Accept pending snapshots for this category - Accept(SnapshotArgs), - /// Reject pending snapshots for this category - Reject(SnapshotArgs), -} - -#[derive(Args, Clone, Debug)] -pub struct SnapshotArgs { - /// Snapshot filters (same as test filters) - #[arg(num_args = 0..)] - pub filters: Vec, - - /// Accept/reject all pending snapshots (required when no filters provided) - #[arg(long)] - pub all: bool, -} +use clap::Args; #[derive(Args, Clone, Debug)] pub struct RunArgs { @@ -29,13 +10,17 @@ pub struct RunArgs { #[arg(long, value_name = "NAME")] pub delete: Option, - /// Confirm deletion for --delete + /// Confirm destructive/global actions (required for unfiltered --accept and --delete) #[arg(long)] pub confirm: bool, - /// Subcommand (accept/reject) or test filters - #[command(subcommand)] - pub command: Option, + /// Accept pending snapshots matching filters + #[arg(long)] + pub accept: bool, + + /// Reject pending snapshots matching filters + #[arg(long)] + pub reject: bool, /// Test name or number filters (passed through to nextest) #[arg(num_args = 0..)] diff --git a/compiler-scripts/src/test_runner/nextest.rs b/compiler-scripts/src/test_runner/nextest.rs index 94db0428..1ec07475 100644 --- a/compiler-scripts/src/test_runner/nextest.rs +++ b/compiler-scripts/src/test_runner/nextest.rs @@ -64,7 +64,8 @@ pub fn run_nextest( create: None, delete: None, confirm: false, - command: None, + accept: false, + reject: false, filters: args.filters.clone(), verbose: true, debug: args.debug, diff --git a/compiler-scripts/src/test_runner/ui.rs b/compiler-scripts/src/test_runner/ui.rs index 74a3c56d..0552f52e 100644 --- a/compiler-scripts/src/test_runner/ui.rs +++ b/compiler-scripts/src/test_runner/ui.rs @@ -245,8 +245,12 @@ fn render_pending( fn format_accept_reject_cmd(category_name: &str, filters_str: &str, action: &str) -> String { if filters_str.is_empty() { - format!("just t {} {} --all", category_name, action) + if action == "accept" { + format!("just t {} --accept --confirm", category_name) + } else { + format!("just t {} --reject", category_name) + } } else { - format!("just t {} {}{}", category_name, action, filters_str) + format!("just t {}{} --{}", category_name, filters_str, action) } } diff --git a/justfile b/justfile index 64388957..cc25d299 100644 --- a/justfile +++ b/justfile @@ -24,7 +24,7 @@ coverage-html: @integration *args="": cargo nextest run -p tests-integration "$@" --status-level=fail --final-status-level=fail --failure-output=final -[doc("Run integration tests with snapshot diffing: checking|lowering|resolving|lsp")] +[doc("Run integration tests with snapshot diffing: checking|checking2|lowering|resolving|lsp")] @t *args="": cargo run -q -p compiler-scripts --release -- "$@" @@ -41,5 +41,5 @@ licenses: cargo bundle-licenses --prefer MIT -o ../THIRDPARTY.toml [doc("Format imports with module granularity")] -format-imports: - cargo +nightly fmt -- --config imports_granularity=Module +@format-imports *args="": + cargo +nightly fmt {{args}} -- --config imports_granularity=Module diff --git a/tests-integration/Cargo.toml b/tests-integration/Cargo.toml index 48952bab..804a8880 100644 --- a/tests-integration/Cargo.toml +++ b/tests-integration/Cargo.toml @@ -8,6 +8,7 @@ build = "build.rs" analyzer = { version = "0.1.0", path = "../compiler-lsp/analyzer" } async-lsp = "0.2.2" checking = { version = "0.1.0", path = "../compiler-core/checking" } +checking2 = { version = "0.1.0", path = "../compiler-core/checking2" } diagnostics = { version = "0.1.0", path = "../compiler-core/diagnostics" } tracing = "0.1.44" tracing-subscriber = { version = "0.3.22", features = ["env-filter", "json"] } diff --git a/tests-integration/build.rs b/tests-integration/build.rs index f76345e3..375ee46b 100644 --- a/tests-integration/build.rs +++ b/tests-integration/build.rs @@ -18,10 +18,12 @@ fn main() { println!("cargo::rerun-if-env-changed=LOWERING_FIXTURES_HASH"); println!("cargo::rerun-if-env-changed=RESOLVING_FIXTURES_HASH"); println!("cargo::rerun-if-env-changed=CHECKING_FIXTURES_HASH"); + println!("cargo::rerun-if-env-changed=CHECKING2_FIXTURES_HASH"); generate_lsp(); generate_lowering(); generate_resolving(); generate_checking(); + generate_checking2(); } fn generate_lsp() { @@ -169,3 +171,50 @@ fn run_test(folder: &str, file: &str) {{ .unwrap(); } } + +fn generate_checking2() { + let mut buffer = fs::File::create("./tests/checking2/generated.rs").unwrap(); + writeln!(buffer, r#"// Do not edit! See build.rs + +#[rustfmt::skip] +fn run_test(folder: &str, file: &str) {{ + let path = std::path::Path::new("fixtures/checking2").join(folder); + let (engine, _) = tests_integration::load_compiler(&path); + let Some(id) = engine.module_file(file) else {{ return }}; + + let level = match std::env::var("TRACE_LEVEL").as_deref() {{ + Ok("debug") => tracing::Level::DEBUG, + _ => tracing::Level::WARN, + }}; + + let target_dir = env!("CARGO_TARGET_TMPDIR"); + let test_name = format!("{{}}_{{}}", folder, file); + let (report, trace_path) = tests_integration::trace::with_file_trace( + level, + target_dir, + &test_name, + || tests_integration::generated::basic::report_checked2(&engine, id) + ); + + println!("trace: {{}}", trace_path.display()); + + let mut settings = insta::Settings::clone_current(); + settings.set_snapshot_path(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("fixtures/checking2").join(folder)); + settings.set_prepend_module_to_snapshot(false); + settings.bind(|| insta::assert_snapshot!(file, report)); +}}"#).unwrap(); + + for folder in read_dir(Path::new("./fixtures/checking2")) { + let Some(stem) = folder.file_stem() else { continue }; + let folder_name = stem.to_os_string().into_string().unwrap().to_snake_case(); + if folder_name == "prelude" { + continue; + } + writeln!( + buffer, + r#" +#[rustfmt::skip] #[test] fn test_{folder_name}_main() {{ run_test("{folder_name}", "Main"); }}"# + ) + .unwrap(); + } +} diff --git a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap index 541f8ed1..53c6ec12 100644 --- a/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap +++ b/tests-integration/fixtures/checking/136_derive_nested_higher_kinded/Main.snap @@ -47,14 +47,14 @@ U = [Representational] V = [Representational, Representational] Derived -derive Eq1 (f :: Type -> Type) => Eq (V @Type (f :: Type -> Type) (g :: Int -> Type) :: Type) -derive Ord1 (f :: Type -> Type) => Ord (V @Type (f :: Type -> Type) (g :: Int -> Type) :: Type) derive Eq1 (f :: Type -> Type) => Eq (T @Type (f :: Type -> Type) (g :: Type -> Type) :: Type) derive Ord1 (f :: Type -> Type) => Ord (T @Type (f :: Type -> Type) (g :: Type -> Type) :: Type) derive Eq (a :: Type) => Eq (Maybe (a :: Type) :: Type) derive Ord (a :: Type) => Ord (Maybe (a :: Type) :: Type) derive Eq1 (f :: Type -> Type) => Eq (U (f :: Type -> Type) :: Type) derive Ord1 (f :: Type -> Type) => Ord (U (f :: Type -> Type) :: Type) +derive Eq1 (f :: Type -> Type) => Eq (V @Type (f :: Type -> Type) (g :: Int -> Type) :: Type) +derive Ord1 (f :: Type -> Type) => Ord (V @Type (f :: Type -> Type) (g :: Int -> Type) :: Type) Diagnostics error[NoInstanceFound]: No instance found for: Eq ((g :: Type -> Type) Int) diff --git a/tests-integration/fixtures/checking/167_derive_eq_1/Main.snap b/tests-integration/fixtures/checking/167_derive_eq_1/Main.snap index 6f001319..df23fb5e 100644 --- a/tests-integration/fixtures/checking/167_derive_eq_1/Main.snap +++ b/tests-integration/fixtures/checking/167_derive_eq_1/Main.snap @@ -81,13 +81,7 @@ Either' = [Representational] NoEq = [Representational] Derived -derive Eq1 (f :: Type -> Type) => Eq1 (Wrap @Type (f :: Type -> Type) :: Type -> Type) -derive forall (t41 :: Type). (Eq1 (f :: Type -> Type), Eq ((g :: (t41 :: Type) -> Type) (a :: (t41 :: Type)))) => Eq (Compose @Type @(t41 :: Type) (f :: Type -> Type) (g :: (t41 :: Type) -> Type) (a :: (t41 :: Type)) :: Type) -derive Eq1 (f :: Type -> Type) => Eq1 (Compose @Type @Type (f :: Type -> Type) (g :: Type -> Type) :: Type -> Type) derive Eq (a :: Type) => Eq (Id (a :: Type) :: Type) -derive Eq (a :: Type) => Eq (Either' (a :: Type) :: Type) -derive Eq1 (Either' :: Type -> Type) -derive Eq1 (NoEq :: Type -> Type) derive Eq1 (Id :: Type -> Type) derive Eq (a :: Type) => Eq (Pair (a :: Type) :: Type) derive Eq1 (Pair :: Type -> Type) @@ -96,6 +90,12 @@ derive Eq1 (Mixed :: Type -> Type) derive Eq (a :: Type) => Eq (Rec (a :: Type) :: Type) derive Eq1 (Rec :: Type -> Type) derive (Eq1 (f :: Type -> Type), Eq (a :: Type)) => Eq (Wrap @Type (f :: Type -> Type) (a :: Type) :: Type) +derive Eq1 (f :: Type -> Type) => Eq1 (Wrap @Type (f :: Type -> Type) :: Type -> Type) +derive forall (t41 :: Type). (Eq1 (f :: Type -> Type), Eq ((g :: (t41 :: Type) -> Type) (a :: (t41 :: Type)))) => Eq (Compose @Type @(t41 :: Type) (f :: Type -> Type) (g :: (t41 :: Type) -> Type) (a :: (t41 :: Type)) :: Type) +derive Eq1 (f :: Type -> Type) => Eq1 (Compose @Type @Type (f :: Type -> Type) (g :: Type -> Type) :: Type -> Type) +derive Eq (a :: Type) => Eq (Either' (a :: Type) :: Type) +derive Eq1 (Either' :: Type -> Type) +derive Eq1 (NoEq :: Type -> Type) Diagnostics error[NoInstanceFound]: No instance found for: Eq ((g :: Type -> Type) (~_ :: Type)) diff --git a/tests-integration/fixtures/checking/168_derive_ord_1/Main.snap b/tests-integration/fixtures/checking/168_derive_ord_1/Main.snap index 080b13c9..dd3484b5 100644 --- a/tests-integration/fixtures/checking/168_derive_ord_1/Main.snap +++ b/tests-integration/fixtures/checking/168_derive_ord_1/Main.snap @@ -52,13 +52,6 @@ Compose = [Representational, Representational, Nominal] NoOrd = [Representational] Derived -derive forall (t40 :: Type). (Eq1 (f :: Type -> Type), Eq ((g :: (t40 :: Type) -> Type) (a :: (t40 :: Type)))) => Eq (Compose @Type @(t40 :: Type) (f :: Type -> Type) (g :: (t40 :: Type) -> Type) (a :: (t40 :: Type)) :: Type) -derive forall (t48 :: Type). (Ord1 (f :: Type -> Type), Ord ((g :: (t48 :: Type) -> Type) (a :: (t48 :: Type)))) => Ord (Compose @Type @(t48 :: Type) (f :: Type -> Type) (g :: (t48 :: Type) -> Type) (a :: (t48 :: Type)) :: Type) -derive Eq1 (f :: Type -> Type) => Eq1 (Compose @Type @Type (f :: Type -> Type) (g :: Type -> Type) :: Type -> Type) -derive Ord1 (f :: Type -> Type) => Ord1 (Compose @Type @Type (f :: Type -> Type) (g :: Type -> Type) :: Type -> Type) -derive Eq (a :: Type) => Eq (NoOrd (a :: Type) :: Type) -derive Eq1 (NoOrd :: Type -> Type) -derive Ord1 (NoOrd :: Type -> Type) derive Eq (a :: Type) => Eq (Id (a :: Type) :: Type) derive Eq1 (Id :: Type -> Type) derive Ord (a :: Type) => Ord (Id (a :: Type) :: Type) @@ -67,6 +60,13 @@ derive (Eq1 (f :: Type -> Type), Eq (a :: Type)) => Eq (Wrap @Type (f :: Type -> derive Eq1 (f :: Type -> Type) => Eq1 (Wrap @Type (f :: Type -> Type) :: Type -> Type) derive (Ord1 (f :: Type -> Type), Ord (a :: Type)) => Ord (Wrap @Type (f :: Type -> Type) (a :: Type) :: Type) derive Ord1 (f :: Type -> Type) => Ord1 (Wrap @Type (f :: Type -> Type) :: Type -> Type) +derive forall (t40 :: Type). (Eq1 (f :: Type -> Type), Eq ((g :: (t40 :: Type) -> Type) (a :: (t40 :: Type)))) => Eq (Compose @Type @(t40 :: Type) (f :: Type -> Type) (g :: (t40 :: Type) -> Type) (a :: (t40 :: Type)) :: Type) +derive forall (t48 :: Type). (Ord1 (f :: Type -> Type), Ord ((g :: (t48 :: Type) -> Type) (a :: (t48 :: Type)))) => Ord (Compose @Type @(t48 :: Type) (f :: Type -> Type) (g :: (t48 :: Type) -> Type) (a :: (t48 :: Type)) :: Type) +derive Eq1 (f :: Type -> Type) => Eq1 (Compose @Type @Type (f :: Type -> Type) (g :: Type -> Type) :: Type -> Type) +derive Ord1 (f :: Type -> Type) => Ord1 (Compose @Type @Type (f :: Type -> Type) (g :: Type -> Type) :: Type -> Type) +derive Eq (a :: Type) => Eq (NoOrd (a :: Type) :: Type) +derive Eq1 (NoOrd :: Type -> Type) +derive Ord1 (NoOrd :: Type -> Type) Diagnostics error[NoInstanceFound]: No instance found for: Eq ((g :: Type -> Type) (~_ :: Type)) diff --git a/tests-integration/fixtures/checking2/001_foreign_check/Main.purs b/tests-integration/fixtures/checking2/001_foreign_check/Main.purs new file mode 100644 index 00000000..afb2e802 --- /dev/null +++ b/tests-integration/fixtures/checking2/001_foreign_check/Main.purs @@ -0,0 +1,5 @@ +module Main where + +foreign import data M :: Type -> Type +foreign import data P :: forall k. k -> Type +foreign import data T :: forall k. P k -> Type diff --git a/tests-integration/fixtures/checking2/001_foreign_check/Main.snap b/tests-integration/fixtures/checking2/001_foreign_check/Main.snap new file mode 100644 index 00000000..3200412e --- /dev/null +++ b/tests-integration/fixtures/checking2/001_foreign_check/Main.snap @@ -0,0 +1,16 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +M :: Type -> Type +P :: forall (k :: Type). (k :: Type) -> Type +T :: forall (t2 :: Type) (k :: (t2 :: Type)). P @(t2 :: Type) (k :: (t2 :: Type)) -> Type + +Roles +M = [Nominal] +P = [Nominal] +T = [Nominal] diff --git a/tests-integration/fixtures/checking2/002_foreign_recursive/Main.purs b/tests-integration/fixtures/checking2/002_foreign_recursive/Main.purs new file mode 100644 index 00000000..96bb69ea --- /dev/null +++ b/tests-integration/fixtures/checking2/002_foreign_recursive/Main.purs @@ -0,0 +1,6 @@ +module Main where + +foreign import data T :: T -> Type + +foreign import data A :: B -> Type +foreign import data B :: A -> Type diff --git a/tests-integration/fixtures/checking2/002_foreign_recursive/Main.snap b/tests-integration/fixtures/checking2/002_foreign_recursive/Main.snap new file mode 100644 index 00000000..9c90d3d4 --- /dev/null +++ b/tests-integration/fixtures/checking2/002_foreign_recursive/Main.snap @@ -0,0 +1,11 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +T :: ?[invalid recursive type] +A :: ?[invalid recursive type] +B :: ?[invalid recursive type] diff --git a/tests-integration/fixtures/checking2/003_data_check/Main.purs b/tests-integration/fixtures/checking2/003_data_check/Main.purs new file mode 100644 index 00000000..95a8f1ea --- /dev/null +++ b/tests-integration/fixtures/checking2/003_data_check/Main.purs @@ -0,0 +1,4 @@ +module Main where + +data Proxy :: forall k. k -> Type +data Proxy a = Proxy diff --git a/tests-integration/fixtures/checking2/003_data_check/Main.snap b/tests-integration/fixtures/checking2/003_data_check/Main.snap new file mode 100644 index 00000000..c70f2d44 --- /dev/null +++ b/tests-integration/fixtures/checking2/003_data_check/Main.snap @@ -0,0 +1,13 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (k :: Type) (a :: (k :: Type)). Proxy @(k :: Type) (a :: (k :: Type)) + +Types +Proxy :: forall (k :: Type). (k :: Type) -> Type + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking2/004_data_infer/Main.purs b/tests-integration/fixtures/checking2/004_data_infer/Main.purs new file mode 100644 index 00000000..24cc27bb --- /dev/null +++ b/tests-integration/fixtures/checking2/004_data_infer/Main.purs @@ -0,0 +1,3 @@ +module Main where + +data Proxy a = Proxy diff --git a/tests-integration/fixtures/checking2/004_data_infer/Main.snap b/tests-integration/fixtures/checking2/004_data_infer/Main.snap new file mode 100644 index 00000000..01b38e4c --- /dev/null +++ b/tests-integration/fixtures/checking2/004_data_infer/Main.snap @@ -0,0 +1,13 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (t1 :: Type) (a :: (t1 :: Type)). Proxy @(t1 :: Type) (a :: (t1 :: Type)) + +Types +Proxy :: forall (t1 :: Type). (t1 :: Type) -> Type + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking2/005_newtype_check/Main.purs b/tests-integration/fixtures/checking2/005_newtype_check/Main.purs new file mode 100644 index 00000000..08652523 --- /dev/null +++ b/tests-integration/fixtures/checking2/005_newtype_check/Main.purs @@ -0,0 +1,4 @@ +module Main where + +newtype Tagged :: forall k. k -> Type -> Type +newtype Tagged t a = Tagged a diff --git a/tests-integration/fixtures/checking2/005_newtype_check/Main.snap b/tests-integration/fixtures/checking2/005_newtype_check/Main.snap new file mode 100644 index 00000000..2f830071 --- /dev/null +++ b/tests-integration/fixtures/checking2/005_newtype_check/Main.snap @@ -0,0 +1,15 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Tagged :: + forall (k :: Type) (t :: (k :: Type)) (a :: Type). + (a :: Type) -> Tagged @(k :: Type) (t :: (k :: Type)) (a :: Type) + +Types +Tagged :: forall (k :: Type). (k :: Type) -> Type -> Type + +Roles +Tagged = [Phantom, Representational] diff --git a/tests-integration/fixtures/checking2/006_newtype_infer/Main.purs b/tests-integration/fixtures/checking2/006_newtype_infer/Main.purs new file mode 100644 index 00000000..e17c2b08 --- /dev/null +++ b/tests-integration/fixtures/checking2/006_newtype_infer/Main.purs @@ -0,0 +1,3 @@ +module Main where + +newtype Tagged t a = Tagged a diff --git a/tests-integration/fixtures/checking2/006_newtype_infer/Main.snap b/tests-integration/fixtures/checking2/006_newtype_infer/Main.snap new file mode 100644 index 00000000..390b958c --- /dev/null +++ b/tests-integration/fixtures/checking2/006_newtype_infer/Main.snap @@ -0,0 +1,15 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Tagged :: + forall (t2 :: Type) (t :: (t2 :: Type)) (a :: Type). + (a :: Type) -> Tagged @(t2 :: Type) (t :: (t2 :: Type)) (a :: Type) + +Types +Tagged :: forall (t2 :: Type). (t2 :: Type) -> Type -> Type + +Roles +Tagged = [Phantom, Representational] diff --git a/tests-integration/fixtures/checking2/007_synonym_check/Main.purs b/tests-integration/fixtures/checking2/007_synonym_check/Main.purs new file mode 100644 index 00000000..af76a778 --- /dev/null +++ b/tests-integration/fixtures/checking2/007_synonym_check/Main.purs @@ -0,0 +1,10 @@ +module Main where + +type NonZeroInt :: Type +type NonZeroInt = Int + +type Identity :: Type -> Type +type Identity a = a + +type FourtyTwo :: Int +type FourtyTwo = 42 diff --git a/tests-integration/fixtures/checking2/007_synonym_check/Main.snap b/tests-integration/fixtures/checking2/007_synonym_check/Main.snap new file mode 100644 index 00000000..294c8deb --- /dev/null +++ b/tests-integration/fixtures/checking2/007_synonym_check/Main.snap @@ -0,0 +1,16 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +NonZeroInt :: Type +Identity :: Type -> Type +FourtyTwo :: Int + +Synonyms +type NonZeroInt = Int +type Identity a = (a :: Type) +type FourtyTwo = 42 diff --git a/tests-integration/fixtures/checking2/008_synonym_infer/Main.purs b/tests-integration/fixtures/checking2/008_synonym_infer/Main.purs new file mode 100644 index 00000000..d41ba126 --- /dev/null +++ b/tests-integration/fixtures/checking2/008_synonym_infer/Main.purs @@ -0,0 +1,7 @@ +module Main where + +type NonZeroInt = Int + +type Identity a = a + +type FourtyTwo = 42 diff --git a/tests-integration/fixtures/checking2/008_synonym_infer/Main.snap b/tests-integration/fixtures/checking2/008_synonym_infer/Main.snap new file mode 100644 index 00000000..0e13593a --- /dev/null +++ b/tests-integration/fixtures/checking2/008_synonym_infer/Main.snap @@ -0,0 +1,16 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +NonZeroInt :: Type +Identity :: forall (t1 :: Type). (t1 :: Type) -> (t1 :: Type) +FourtyTwo :: Int + +Synonyms +type NonZeroInt = Int +type Identity a = (a :: (t1 :: Type)) +type FourtyTwo = 42 diff --git a/tests-integration/fixtures/checking2/009_class_check/Main.purs b/tests-integration/fixtures/checking2/009_class_check/Main.purs new file mode 100644 index 00000000..bcb40a07 --- /dev/null +++ b/tests-integration/fixtures/checking2/009_class_check/Main.purs @@ -0,0 +1,5 @@ +module Main where + +class Eq :: Type -> Constraint +class Eq a where + eq :: a -> a -> Boolean diff --git a/tests-integration/fixtures/checking2/009_class_check/Main.snap b/tests-integration/fixtures/checking2/009_class_check/Main.snap new file mode 100644 index 00000000..6b36e9fe --- /dev/null +++ b/tests-integration/fixtures/checking2/009_class_check/Main.snap @@ -0,0 +1,14 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean + +Types +Eq :: Type -> Constraint + +Classes +class forall (a :: Type). Eq (a :: Type) + eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean diff --git a/tests-integration/fixtures/checking2/010_class_infer/Main.purs b/tests-integration/fixtures/checking2/010_class_infer/Main.purs new file mode 100644 index 00000000..40d61855 --- /dev/null +++ b/tests-integration/fixtures/checking2/010_class_infer/Main.purs @@ -0,0 +1,4 @@ +module Main where + +class Eq a where + eq :: a -> a -> Boolean diff --git a/tests-integration/fixtures/checking2/010_class_infer/Main.snap b/tests-integration/fixtures/checking2/010_class_infer/Main.snap new file mode 100644 index 00000000..6b36e9fe --- /dev/null +++ b/tests-integration/fixtures/checking2/010_class_infer/Main.snap @@ -0,0 +1,14 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean + +Types +Eq :: Type -> Constraint + +Classes +class forall (a :: Type). Eq (a :: Type) + eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean diff --git a/tests-integration/fixtures/checking2/011_class_superclass/Main.purs b/tests-integration/fixtures/checking2/011_class_superclass/Main.purs new file mode 100644 index 00000000..927f73f5 --- /dev/null +++ b/tests-integration/fixtures/checking2/011_class_superclass/Main.purs @@ -0,0 +1,7 @@ +module Main where + +class Eq a where + eq :: a -> a -> Boolean + +class Eq a <= Ord a where + compare :: a -> a -> Int diff --git a/tests-integration/fixtures/checking2/011_class_superclass/Main.snap b/tests-integration/fixtures/checking2/011_class_superclass/Main.snap new file mode 100644 index 00000000..31498ca6 --- /dev/null +++ b/tests-integration/fixtures/checking2/011_class_superclass/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean +compare :: forall (a :: Type). Ord (a :: Type) => (a :: Type) -> (a :: Type) -> Int + +Types +Eq :: Type -> Constraint +Ord :: Type -> Constraint + +Classes +class forall (a :: Type). Eq (a :: Type) + eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean +class forall (a :: Type). Eq (a :: Type) <= Ord (a :: Type) + compare :: forall (a :: Type). Ord (a :: Type) => (a :: Type) -> (a :: Type) -> Int diff --git a/tests-integration/fixtures/checking2/012_class_polykind_check/Main.purs b/tests-integration/fixtures/checking2/012_class_polykind_check/Main.purs new file mode 100644 index 00000000..7ceb2d4c --- /dev/null +++ b/tests-integration/fixtures/checking2/012_class_polykind_check/Main.purs @@ -0,0 +1,5 @@ +module Main where + +class HasKind :: forall k. k -> Constraint +class HasKind a where + reflectKind :: forall (p :: k -> Type). p a -> String diff --git a/tests-integration/fixtures/checking2/012_class_polykind_check/Main.snap b/tests-integration/fixtures/checking2/012_class_polykind_check/Main.snap new file mode 100644 index 00000000..16d6251f --- /dev/null +++ b/tests-integration/fixtures/checking2/012_class_polykind_check/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +reflectKind :: + forall (k :: Type) (a :: (k :: Type)) (p :: (k :: Type) -> Type). + HasKind @(k :: Type) (a :: (k :: Type)) => + (p :: (k :: Type) -> Type) (a :: (k :: Type)) -> String + +Types +HasKind :: forall (k :: Type). (k :: Type) -> Constraint + +Classes +class forall (k :: Type) (a :: (k :: Type)). HasKind @(k :: Type) (a :: (k :: Type)) + reflectKind :: + forall (k :: Type) (a :: (k :: Type)) (p :: (k :: Type) -> Type). + HasKind @(k :: Type) (a :: (k :: Type)) => + (p :: (k :: Type) -> Type) (a :: (k :: Type)) -> String diff --git a/tests-integration/fixtures/checking2/013_class_polykind_infer/Main.purs b/tests-integration/fixtures/checking2/013_class_polykind_infer/Main.purs new file mode 100644 index 00000000..ebb1548f --- /dev/null +++ b/tests-integration/fixtures/checking2/013_class_polykind_infer/Main.purs @@ -0,0 +1,4 @@ +module Main where + +class HasKind' a where + reflectKind' :: forall p. p a -> String diff --git a/tests-integration/fixtures/checking2/013_class_polykind_infer/Main.snap b/tests-integration/fixtures/checking2/013_class_polykind_infer/Main.snap new file mode 100644 index 00000000..304280cc --- /dev/null +++ b/tests-integration/fixtures/checking2/013_class_polykind_infer/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +reflectKind' :: + forall (t2 :: Type) (a :: (t2 :: Type)) (p :: (t2 :: Type) -> Type). + HasKind' @(t2 :: Type) (a :: (t2 :: Type)) => + (p :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) -> String + +Types +HasKind' :: forall (t2 :: Type). (t2 :: Type) -> Constraint + +Classes +class forall (t2 :: Type) (a :: (t2 :: Type)). HasKind' @(t2 :: Type) (a :: (t2 :: Type)) + reflectKind' :: + forall (t2 :: Type) (a :: (t2 :: Type)) (p :: (t2 :: Type) -> Type). + HasKind' @(t2 :: Type) (a :: (t2 :: Type)) => + (p :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) -> String diff --git a/tests-integration/fixtures/checking2/014_operator_alias_kind/Main.purs b/tests-integration/fixtures/checking2/014_operator_alias_kind/Main.purs new file mode 100644 index 00000000..e1704fcd --- /dev/null +++ b/tests-integration/fixtures/checking2/014_operator_alias_kind/Main.purs @@ -0,0 +1,5 @@ +module Main where + +type Add a b = a + +infix 5 type Add as + diff --git a/tests-integration/fixtures/checking2/014_operator_alias_kind/Main.snap b/tests-integration/fixtures/checking2/014_operator_alias_kind/Main.snap new file mode 100644 index 00000000..d4ca4cfd --- /dev/null +++ b/tests-integration/fixtures/checking2/014_operator_alias_kind/Main.snap @@ -0,0 +1,13 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +Add :: forall (t3 :: Type) (t2 :: Type). (t3 :: Type) -> (t2 :: Type) -> (t3 :: Type) ++ :: forall (t3 :: Type) (t2 :: Type). (t3 :: Type) -> (t2 :: Type) -> (t3 :: Type) + +Synonyms +type Add a b = (a :: (t3 :: Type)) diff --git a/tests-integration/fixtures/checking2/015_operator_alias_invalid_kind/Main.purs b/tests-integration/fixtures/checking2/015_operator_alias_invalid_kind/Main.purs new file mode 100644 index 00000000..f4ee3580 --- /dev/null +++ b/tests-integration/fixtures/checking2/015_operator_alias_invalid_kind/Main.purs @@ -0,0 +1,5 @@ +module Main where + +type Identity a = a + +infix 5 type Identity as + diff --git a/tests-integration/fixtures/checking2/015_operator_alias_invalid_kind/Main.snap b/tests-integration/fixtures/checking2/015_operator_alias_invalid_kind/Main.snap new file mode 100644 index 00000000..74e2af52 --- /dev/null +++ b/tests-integration/fixtures/checking2/015_operator_alias_invalid_kind/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +Identity :: forall (t1 :: Type). (t1 :: Type) -> (t1 :: Type) ++ :: forall (t1 :: Type). (t1 :: Type) -> (t1 :: Type) + +Synonyms +type Identity a = (a :: (t1 :: Type)) + +Errors +CheckError { + kind: InvalidTypeOperator { + kind_message: Id(4), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/016_type_operator_chain_infer/Main.purs b/tests-integration/fixtures/checking2/016_type_operator_chain_infer/Main.purs new file mode 100644 index 00000000..6adb93d7 --- /dev/null +++ b/tests-integration/fixtures/checking2/016_type_operator_chain_infer/Main.purs @@ -0,0 +1,9 @@ +module Main where + +type Add a b = a + +infixl 5 type Add as + + +type Chain a b c = a + b + c + +type ChainParen a b c = (a + b) + c diff --git a/tests-integration/fixtures/checking2/016_type_operator_chain_infer/Main.snap b/tests-integration/fixtures/checking2/016_type_operator_chain_infer/Main.snap new file mode 100644 index 00000000..3462e6a7 --- /dev/null +++ b/tests-integration/fixtures/checking2/016_type_operator_chain_infer/Main.snap @@ -0,0 +1,29 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +Add :: forall (t3 :: Type) (t2 :: Type). (t3 :: Type) -> (t2 :: Type) -> (t3 :: Type) ++ :: forall (t3 :: Type) (t2 :: Type). (t3 :: Type) -> (t2 :: Type) -> (t3 :: Type) +Chain :: + forall (t9 :: Type) (t8 :: Type) (t7 :: Type). + (t9 :: Type) -> (t8 :: Type) -> (t7 :: Type) -> (t9 :: Type) +ChainParen :: + forall (t15 :: Type) (t14 :: Type) (t13 :: Type). + (t15 :: Type) -> (t14 :: Type) -> (t13 :: Type) -> (t15 :: Type) + +Synonyms +type Add a b = (a :: (t3 :: Type)) +type Chain a b c = Add + @(t9 :: Type) + @(t7 :: Type) + (Add @(t9 :: Type) @(t8 :: Type) (a :: (t9 :: Type)) (b :: (t8 :: Type))) + (c :: (t7 :: Type)) +type ChainParen a b c = Add + @(t15 :: Type) + @(t13 :: Type) + (Add @(t15 :: Type) @(t14 :: Type) (a :: (t15 :: Type)) (b :: (t14 :: Type))) + (c :: (t13 :: Type)) diff --git a/tests-integration/fixtures/checking2/017_type_operator_chain_check/Main.purs b/tests-integration/fixtures/checking2/017_type_operator_chain_check/Main.purs new file mode 100644 index 00000000..5adc98c5 --- /dev/null +++ b/tests-integration/fixtures/checking2/017_type_operator_chain_check/Main.purs @@ -0,0 +1,12 @@ +module Main where + +type Add :: Type -> Type -> Type +type Add a b = a + +infixl 5 type Add as + + +type Chain :: Type -> Type -> Type -> Type +type Chain a b c = a + b + c + +type ChainParen :: Type -> Type -> Type -> Type +type ChainParen a b c = (a + b) + c diff --git a/tests-integration/fixtures/checking2/017_type_operator_chain_check/Main.snap b/tests-integration/fixtures/checking2/017_type_operator_chain_check/Main.snap new file mode 100644 index 00000000..3e1a8025 --- /dev/null +++ b/tests-integration/fixtures/checking2/017_type_operator_chain_check/Main.snap @@ -0,0 +1,17 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +Add :: Type -> Type -> Type ++ :: Type -> Type -> Type +Chain :: Type -> Type -> Type -> Type +ChainParen :: Type -> Type -> Type -> Type + +Synonyms +type Add a b = (a :: Type) +type Chain a b c = Add (Add (a :: Type) (b :: Type)) (c :: Type) +type ChainParen a b c = Add (Add (a :: Type) (b :: Type)) (c :: Type) diff --git a/tests-integration/fixtures/checking2/018_type_operator_chain_polykind/Main.purs b/tests-integration/fixtures/checking2/018_type_operator_chain_polykind/Main.purs new file mode 100644 index 00000000..61985017 --- /dev/null +++ b/tests-integration/fixtures/checking2/018_type_operator_chain_polykind/Main.purs @@ -0,0 +1,12 @@ +module Main where + +type Add :: forall k. k -> k -> k +type Add a b = a + +infixl 5 type Add as + + +type Chain :: forall k. k -> k -> k -> k +type Chain a b c = a + b + c + +type ChainParen :: forall k. k -> k -> k -> k +type ChainParen a b c = (a + b) + c diff --git a/tests-integration/fixtures/checking2/018_type_operator_chain_polykind/Main.snap b/tests-integration/fixtures/checking2/018_type_operator_chain_polykind/Main.snap new file mode 100644 index 00000000..8c7cf575 --- /dev/null +++ b/tests-integration/fixtures/checking2/018_type_operator_chain_polykind/Main.snap @@ -0,0 +1,17 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +Add :: forall (k :: Type). (k :: Type) -> (k :: Type) -> (k :: Type) ++ :: forall (k :: Type). (k :: Type) -> (k :: Type) -> (k :: Type) +Chain :: forall (k :: Type). (k :: Type) -> (k :: Type) -> (k :: Type) -> (k :: Type) +ChainParen :: forall (k :: Type). (k :: Type) -> (k :: Type) -> (k :: Type) -> (k :: Type) + +Synonyms +type Add a b = (a :: (k :: Type)) +type Chain a b c = Add @(k :: Type) (Add @(k :: Type) (a :: (k :: Type)) (b :: (k :: Type))) (c :: (k :: Type)) +type ChainParen a b c = Add @(k :: Type) (Add @(k :: Type) (a :: (k :: Type)) (b :: (k :: Type))) (c :: (k :: Type)) diff --git a/tests-integration/fixtures/checking2/019_type_operator_chain_precedence/Main.purs b/tests-integration/fixtures/checking2/019_type_operator_chain_precedence/Main.purs new file mode 100644 index 00000000..ae742b36 --- /dev/null +++ b/tests-integration/fixtures/checking2/019_type_operator_chain_precedence/Main.purs @@ -0,0 +1,13 @@ +module Main where + +type Add a b = a + +infixl 5 type Add as :+: + +type Mul a b = a + +infixl 6 type Mul as :*: + +type Chain a b c = a :+: b :*: c + +type ChainParen a b c = (a :+: b) :*: c diff --git a/tests-integration/fixtures/checking2/019_type_operator_chain_precedence/Main.snap b/tests-integration/fixtures/checking2/019_type_operator_chain_precedence/Main.snap new file mode 100644 index 00000000..58c900c1 --- /dev/null +++ b/tests-integration/fixtures/checking2/019_type_operator_chain_precedence/Main.snap @@ -0,0 +1,32 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +Add :: forall (t3 :: Type) (t2 :: Type). (t3 :: Type) -> (t2 :: Type) -> (t3 :: Type) +:+: :: forall (t3 :: Type) (t2 :: Type). (t3 :: Type) -> (t2 :: Type) -> (t3 :: Type) +Mul :: forall (t7 :: Type) (t6 :: Type). (t7 :: Type) -> (t6 :: Type) -> (t7 :: Type) +:*: :: forall (t7 :: Type) (t6 :: Type). (t7 :: Type) -> (t6 :: Type) -> (t7 :: Type) +Chain :: + forall (t13 :: Type) (t12 :: Type) (t11 :: Type). + (t13 :: Type) -> (t12 :: Type) -> (t11 :: Type) -> (t13 :: Type) +ChainParen :: + forall (t19 :: Type) (t18 :: Type) (t17 :: Type). + (t19 :: Type) -> (t18 :: Type) -> (t17 :: Type) -> (t19 :: Type) + +Synonyms +type Add a b = (a :: (t3 :: Type)) +type Mul a b = (a :: (t7 :: Type)) +type Chain a b c = Add + @(t13 :: Type) + @(t12 :: Type) + (a :: (t13 :: Type)) + (Mul @(t12 :: Type) @(t11 :: Type) (b :: (t12 :: Type)) (c :: (t11 :: Type))) +type ChainParen a b c = Mul + @(t19 :: Type) + @(t17 :: Type) + (Add @(t19 :: Type) @(t18 :: Type) (a :: (t19 :: Type)) (b :: (t18 :: Type))) + (c :: (t17 :: Type)) diff --git a/tests-integration/fixtures/checking2/020_type_operator_chain_kind_error/Main.purs b/tests-integration/fixtures/checking2/020_type_operator_chain_kind_error/Main.purs new file mode 100644 index 00000000..4e67bd1b --- /dev/null +++ b/tests-integration/fixtures/checking2/020_type_operator_chain_kind_error/Main.purs @@ -0,0 +1,8 @@ +module Main where + +type Add :: Type -> Type -> Type +type Add a b = a + +infixl 5 type Add as + + +type Bad = Int + "hello" diff --git a/tests-integration/fixtures/checking2/020_type_operator_chain_kind_error/Main.snap b/tests-integration/fixtures/checking2/020_type_operator_chain_kind_error/Main.snap new file mode 100644 index 00000000..06c09f03 --- /dev/null +++ b/tests-integration/fixtures/checking2/020_type_operator_chain_kind_error/Main.snap @@ -0,0 +1,34 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +Add :: Type -> Type -> Type ++ :: Type -> Type -> Type +Bad :: Type + +Synonyms +type Add a b = (a :: Type) +type Bad = Add Int "hello" + +Errors +CheckError { + kind: CannotUnify { + t1: Id(7), + t2: Id(8), + }, + crumbs: [ + CheckingKind( + AstId(17), + ), + InferringKind( + AstId(17), + ), + CheckingKind( + AstId(21), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/021_role_inference_phantom/Main.purs b/tests-integration/fixtures/checking2/021_role_inference_phantom/Main.purs new file mode 100644 index 00000000..24cc27bb --- /dev/null +++ b/tests-integration/fixtures/checking2/021_role_inference_phantom/Main.purs @@ -0,0 +1,3 @@ +module Main where + +data Proxy a = Proxy diff --git a/tests-integration/fixtures/checking2/021_role_inference_phantom/Main.snap b/tests-integration/fixtures/checking2/021_role_inference_phantom/Main.snap new file mode 100644 index 00000000..01b38e4c --- /dev/null +++ b/tests-integration/fixtures/checking2/021_role_inference_phantom/Main.snap @@ -0,0 +1,13 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (t1 :: Type) (a :: (t1 :: Type)). Proxy @(t1 :: Type) (a :: (t1 :: Type)) + +Types +Proxy :: forall (t1 :: Type). (t1 :: Type) -> Type + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking2/022_role_inference_representational/Main.purs b/tests-integration/fixtures/checking2/022_role_inference_representational/Main.purs new file mode 100644 index 00000000..e9dace92 --- /dev/null +++ b/tests-integration/fixtures/checking2/022_role_inference_representational/Main.purs @@ -0,0 +1,3 @@ +module Main where + +data Maybe a = Nothing | Just a diff --git a/tests-integration/fixtures/checking2/022_role_inference_representational/Main.snap b/tests-integration/fixtures/checking2/022_role_inference_representational/Main.snap new file mode 100644 index 00000000..0c24dbcf --- /dev/null +++ b/tests-integration/fixtures/checking2/022_role_inference_representational/Main.snap @@ -0,0 +1,14 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe (a :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) + +Types +Maybe :: Type -> Type + +Roles +Maybe = [Representational] diff --git a/tests-integration/fixtures/checking2/023_role_inference_nominal_parametric/Main.purs b/tests-integration/fixtures/checking2/023_role_inference_nominal_parametric/Main.purs new file mode 100644 index 00000000..2e491825 --- /dev/null +++ b/tests-integration/fixtures/checking2/023_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/checking2/023_role_inference_nominal_parametric/Main.snap b/tests-integration/fixtures/checking2/023_role_inference_nominal_parametric/Main.snap new file mode 100644 index 00000000..71ab8526 --- /dev/null +++ b/tests-integration/fixtures/checking2/023_role_inference_nominal_parametric/Main.snap @@ -0,0 +1,16 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +F :: + forall (t2 :: Type) (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)). + (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) -> + F @(t2 :: Type) (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) + +Types +F :: forall (t2 :: Type). ((t2 :: Type) -> Type) -> (t2 :: Type) -> Type + +Roles +F = [Representational, Nominal] diff --git a/tests-integration/fixtures/checking2/024_role_declaration_strengthen/Main.purs b/tests-integration/fixtures/checking2/024_role_declaration_strengthen/Main.purs new file mode 100644 index 00000000..500516d1 --- /dev/null +++ b/tests-integration/fixtures/checking2/024_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/checking2/024_role_declaration_strengthen/Main.snap b/tests-integration/fixtures/checking2/024_role_declaration_strengthen/Main.snap new file mode 100644 index 00000000..2251a842 --- /dev/null +++ b/tests-integration/fixtures/checking2/024_role_declaration_strengthen/Main.snap @@ -0,0 +1,14 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe (a :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) + +Types +Maybe :: Type -> Type + +Roles +Maybe = [Nominal] diff --git a/tests-integration/fixtures/checking2/025_role_declaration_loosen_error/Main.purs b/tests-integration/fixtures/checking2/025_role_declaration_loosen_error/Main.purs new file mode 100644 index 00000000..ad53bad9 --- /dev/null +++ b/tests-integration/fixtures/checking2/025_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/checking2/025_role_declaration_loosen_error/Main.snap b/tests-integration/fixtures/checking2/025_role_declaration_loosen_error/Main.snap new file mode 100644 index 00000000..c411ac39 --- /dev/null +++ b/tests-integration/fixtures/checking2/025_role_declaration_loosen_error/Main.snap @@ -0,0 +1,30 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +F :: + forall (t2 :: Type) (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)). + (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) -> + F @(t2 :: Type) (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) + +Types +F :: forall (t2 :: Type). ((t2 :: Type) -> Type) -> (t2 :: Type) -> Type + +Roles +F = [Representational, Nominal] + +Errors +CheckError { + kind: InvalidRoleDeclaration { + index: 1, + declared: Phantom, + inferred: Nominal, + }, + crumbs: [ + TypeDeclaration( + Idx::(0), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/026_role_declaration_foreign/Main.purs b/tests-integration/fixtures/checking2/026_role_declaration_foreign/Main.purs new file mode 100644 index 00000000..6c59361e --- /dev/null +++ b/tests-integration/fixtures/checking2/026_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/checking2/026_role_declaration_foreign/Main.snap b/tests-integration/fixtures/checking2/026_role_declaration_foreign/Main.snap new file mode 100644 index 00000000..c2f6ffa7 --- /dev/null +++ b/tests-integration/fixtures/checking2/026_role_declaration_foreign/Main.snap @@ -0,0 +1,12 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +Effect :: Type -> Type + +Roles +Effect = [Representational] diff --git a/tests-integration/fixtures/checking2/027_foreign_check/Main.purs b/tests-integration/fixtures/checking2/027_foreign_check/Main.purs new file mode 100644 index 00000000..b1b58614 --- /dev/null +++ b/tests-integration/fixtures/checking2/027_foreign_check/Main.purs @@ -0,0 +1,3 @@ +module Main where + +foreign import unsafeCoerce :: forall a b. a -> b diff --git a/tests-integration/fixtures/checking2/027_foreign_check/Main.snap b/tests-integration/fixtures/checking2/027_foreign_check/Main.snap new file mode 100644 index 00000000..31cbaaf3 --- /dev/null +++ b/tests-integration/fixtures/checking2/027_foreign_check/Main.snap @@ -0,0 +1,9 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +unsafeCoerce :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) + +Types diff --git a/tests-integration/fixtures/checking2/028_value_check/Main.purs b/tests-integration/fixtures/checking2/028_value_check/Main.purs new file mode 100644 index 00000000..f6fc4ba0 --- /dev/null +++ b/tests-integration/fixtures/checking2/028_value_check/Main.purs @@ -0,0 +1,4 @@ +module Main where + +const :: forall a b. a -> b -> a +const a _ = a diff --git a/tests-integration/fixtures/checking2/028_value_check/Main.snap b/tests-integration/fixtures/checking2/028_value_check/Main.snap new file mode 100644 index 00000000..583b2585 --- /dev/null +++ b/tests-integration/fixtures/checking2/028_value_check/Main.snap @@ -0,0 +1,9 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +const :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> (a :: Type) + +Types diff --git a/tests-integration/fixtures/checking2/029_operator_check/Main.purs b/tests-integration/fixtures/checking2/029_operator_check/Main.purs new file mode 100644 index 00000000..c54653e0 --- /dev/null +++ b/tests-integration/fixtures/checking2/029_operator_check/Main.purs @@ -0,0 +1,10 @@ +module Main where + +infix 5 const as <: + +const :: forall a b. a -> b -> a +const a _ = a + +infixl 5 add as + + +foreign import add :: Int -> Int -> Int diff --git a/tests-integration/fixtures/checking2/029_operator_check/Main.snap b/tests-integration/fixtures/checking2/029_operator_check/Main.snap new file mode 100644 index 00000000..25e281da --- /dev/null +++ b/tests-integration/fixtures/checking2/029_operator_check/Main.snap @@ -0,0 +1,12 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +<: :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> (a :: Type) +const :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> (a :: Type) ++ :: Int -> Int -> Int +add :: Int -> Int -> Int + +Types diff --git a/tests-integration/fixtures/checking2/030_exhaustive_case_infer/Main.purs b/tests-integration/fixtures/checking2/030_exhaustive_case_infer/Main.purs new file mode 100644 index 00000000..95a569e2 --- /dev/null +++ b/tests-integration/fixtures/checking2/030_exhaustive_case_infer/Main.purs @@ -0,0 +1,6 @@ +module Main where + +data Maybe a = Just a | Nothing + +test = case _ of + Just _ -> 1 diff --git a/tests-integration/fixtures/checking2/030_exhaustive_case_infer/Main.snap b/tests-integration/fixtures/checking2/030_exhaustive_case_infer/Main.snap new file mode 100644 index 00000000..aee46711 --- /dev/null +++ b/tests-integration/fixtures/checking2/030_exhaustive_case_infer/Main.snap @@ -0,0 +1,32 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +test :: forall (t1 :: Type). Partial => Maybe (t1 :: Type) -> Int + +Types +Maybe :: Type -> Type + +Roles +Maybe = [Representational] + +Errors +CheckError { + kind: MissingPatterns { + patterns: [ + Id(7), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + InferringExpression( + AstId(13), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/031_exhaustive_case_redundant/Main.purs b/tests-integration/fixtures/checking2/031_exhaustive_case_redundant/Main.purs new file mode 100644 index 00000000..7c32a831 --- /dev/null +++ b/tests-integration/fixtures/checking2/031_exhaustive_case_redundant/Main.purs @@ -0,0 +1,8 @@ +module Main where + +data Maybe a = Just a | Nothing + +test = case _ of + Just _ -> 1 + Just _ -> 2 + Nothing -> 3 diff --git a/tests-integration/fixtures/checking2/031_exhaustive_case_redundant/Main.snap b/tests-integration/fixtures/checking2/031_exhaustive_case_redundant/Main.snap new file mode 100644 index 00000000..2966fee2 --- /dev/null +++ b/tests-integration/fixtures/checking2/031_exhaustive_case_redundant/Main.snap @@ -0,0 +1,32 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +test :: forall (t1 :: Type). Maybe (t1 :: Type) -> Int + +Types +Maybe :: Type -> Type + +Roles +Maybe = [Representational] + +Errors +CheckError { + kind: RedundantPatterns { + patterns: [ + "Just _", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + InferringExpression( + AstId(13), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/032_exhaustive_equation_signature/Main.purs b/tests-integration/fixtures/checking2/032_exhaustive_equation_signature/Main.purs new file mode 100644 index 00000000..eea38f36 --- /dev/null +++ b/tests-integration/fixtures/checking2/032_exhaustive_equation_signature/Main.purs @@ -0,0 +1,6 @@ +module Main where + +data Maybe a = Just a | Nothing + +test :: Maybe Int -> Int +test (Just _) = 1 diff --git a/tests-integration/fixtures/checking2/032_exhaustive_equation_signature/Main.snap b/tests-integration/fixtures/checking2/032_exhaustive_equation_signature/Main.snap new file mode 100644 index 00000000..9791e2b7 --- /dev/null +++ b/tests-integration/fixtures/checking2/032_exhaustive_equation_signature/Main.snap @@ -0,0 +1,29 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +test :: Maybe Int -> Int + +Types +Maybe :: Type -> Type + +Roles +Maybe = [Representational] + +Errors +CheckError { + kind: MissingPatterns { + patterns: [ + Id(7), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/033_exhaustive_guards_otherwise/Main.purs b/tests-integration/fixtures/checking2/033_exhaustive_guards_otherwise/Main.purs new file mode 100644 index 00000000..a1286577 --- /dev/null +++ b/tests-integration/fixtures/checking2/033_exhaustive_guards_otherwise/Main.purs @@ -0,0 +1,11 @@ +module Main where + +import Data.Boolean (otherwise) + +data Maybe a = Just a | Nothing + +test1 = case _ of + Just _ | otherwise -> 1 + +test2 = case _ of + Nothing | true -> 2 diff --git a/tests-integration/fixtures/checking2/033_exhaustive_guards_otherwise/Main.snap b/tests-integration/fixtures/checking2/033_exhaustive_guards_otherwise/Main.snap new file mode 100644 index 00000000..c3bdc674 --- /dev/null +++ b/tests-integration/fixtures/checking2/033_exhaustive_guards_otherwise/Main.snap @@ -0,0 +1,48 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +test1 :: forall (t1 :: Type). Partial => Maybe (t1 :: Type) -> Int +test2 :: forall (t2 :: Type). Partial => Maybe (t2 :: Type) -> Int + +Types +Maybe :: Type -> Type + +Roles +Maybe = [Representational] + +Errors +CheckError { + kind: MissingPatterns { + patterns: [ + Id(8), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + InferringExpression( + AstId(19), + ), + ], +} +CheckError { + kind: MissingPatterns { + patterns: [ + Id(9), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + InferringExpression( + AstId(36), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/034_exhaustive_let_pattern/Main.purs b/tests-integration/fixtures/checking2/034_exhaustive_let_pattern/Main.purs new file mode 100644 index 00000000..05cc6527 --- /dev/null +++ b/tests-integration/fixtures/checking2/034_exhaustive_let_pattern/Main.purs @@ -0,0 +1,9 @@ +module Main where + +data Maybe a = Just a | Nothing + +test = + let + Just x = Just 1 + in + x diff --git a/tests-integration/fixtures/checking2/034_exhaustive_let_pattern/Main.snap b/tests-integration/fixtures/checking2/034_exhaustive_let_pattern/Main.snap new file mode 100644 index 00000000..d89ac40d --- /dev/null +++ b/tests-integration/fixtures/checking2/034_exhaustive_let_pattern/Main.snap @@ -0,0 +1,32 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +test :: Partial => Int + +Types +Maybe :: Type -> Type + +Roles +Maybe = [Representational] + +Errors +CheckError { + kind: MissingPatterns { + patterns: [ + Id(7), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + InferringExpression( + AstId(13), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/035_exhaustive_operator_constructor/Main.purs b/tests-integration/fixtures/checking2/035_exhaustive_operator_constructor/Main.purs new file mode 100644 index 00000000..562b493e --- /dev/null +++ b/tests-integration/fixtures/checking2/035_exhaustive_operator_constructor/Main.purs @@ -0,0 +1,8 @@ +module Main where + +data List a = Cons a (List a) | Nil + +infixr 5 Cons as : + +head :: forall a. List a -> a +head (x : _) = x diff --git a/tests-integration/fixtures/checking2/035_exhaustive_operator_constructor/Main.snap b/tests-integration/fixtures/checking2/035_exhaustive_operator_constructor/Main.snap new file mode 100644 index 00000000..e1ad071c --- /dev/null +++ b/tests-integration/fixtures/checking2/035_exhaustive_operator_constructor/Main.snap @@ -0,0 +1,30 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Cons :: forall (a :: Type). (a :: Type) -> List (a :: Type) -> List (a :: Type) +Nil :: forall (a :: Type). List (a :: Type) +: :: forall (a :: Type). (a :: Type) -> List (a :: Type) -> List (a :: Type) +head :: forall (a :: Type). List (a :: Type) -> (a :: Type) + +Types +List :: Type -> Type + +Roles +List = [Representational] + +Errors +CheckError { + kind: MissingPatterns { + patterns: [ + Id(9), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/036_synonym_partial_defer/Main.purs b/tests-integration/fixtures/checking2/036_synonym_partial_defer/Main.purs new file mode 100644 index 00000000..21f54fc5 --- /dev/null +++ b/tests-integration/fixtures/checking2/036_synonym_partial_defer/Main.purs @@ -0,0 +1,13 @@ +module Main where + +data Identity a = Identity a + +type ReaderT :: Type -> (Type -> Type) -> Type -> Type +type ReaderT r m a = r -> m a + +type Apply :: forall k1 k2. (k1 -> k2) -> k1 -> k2 +type Apply f a = f a + +type Good = Apply (Apply (ReaderT Int) Identity) String + +type Bad = ReaderT Int diff --git a/tests-integration/fixtures/checking2/036_synonym_partial_defer/Main.snap b/tests-integration/fixtures/checking2/036_synonym_partial_defer/Main.snap new file mode 100644 index 00000000..84213d80 --- /dev/null +++ b/tests-integration/fixtures/checking2/036_synonym_partial_defer/Main.snap @@ -0,0 +1,50 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Identity :: forall (a :: Type). (a :: Type) -> Identity (a :: Type) + +Types +Identity :: Type -> Type +ReaderT :: Type -> (Type -> Type) -> Type -> Type +Apply :: + forall (k1 :: Type) (k2 :: Type). ((k1 :: Type) -> (k2 :: Type)) -> (k1 :: Type) -> (k2 :: Type) +Good :: Type +Bad :: ?[partial synonym application] + +Synonyms +type ReaderT r m a = (r :: Type) -> (m :: Type -> Type) (a :: Type) +type Apply f a = (f :: (k1 :: Type) -> (k2 :: Type)) (a :: (k1 :: Type)) +type Good = Apply @Type @Type (Apply @(Type -> Type) @(Type -> Type) (ReaderT Int) Identity) String +type Bad = ?[partial synonym application] + +Roles +Identity = [Representational] + +Errors +CheckError { + kind: PartialSynonymApplication { + id: AstId(60), + }, + crumbs: [ + CheckingKind( + AstId(60), + ), + InferringKind( + AstId(60), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(10), + t2: Id(11), + }, + crumbs: [ + CheckingKind( + AstId(60), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/037_value_recursive_check/Main.purs b/tests-integration/fixtures/checking2/037_value_recursive_check/Main.purs new file mode 100644 index 00000000..4dc32cc7 --- /dev/null +++ b/tests-integration/fixtures/checking2/037_value_recursive_check/Main.purs @@ -0,0 +1,4 @@ +module Main where + +id :: forall a. a -> a +id a = id a diff --git a/tests-integration/fixtures/checking2/037_value_recursive_check/Main.snap b/tests-integration/fixtures/checking2/037_value_recursive_check/Main.snap new file mode 100644 index 00000000..67d59fe6 --- /dev/null +++ b/tests-integration/fixtures/checking2/037_value_recursive_check/Main.snap @@ -0,0 +1,9 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +id :: forall (a :: Type). (a :: Type) -> (a :: Type) + +Types diff --git a/tests-integration/fixtures/checking2/038_value_recursive_infer/Main.purs b/tests-integration/fixtures/checking2/038_value_recursive_infer/Main.purs new file mode 100644 index 00000000..1c95e3b5 --- /dev/null +++ b/tests-integration/fixtures/checking2/038_value_recursive_infer/Main.purs @@ -0,0 +1,3 @@ +module Main where + +id a = id a diff --git a/tests-integration/fixtures/checking2/038_value_recursive_infer/Main.snap b/tests-integration/fixtures/checking2/038_value_recursive_infer/Main.snap new file mode 100644 index 00000000..469b9c4e --- /dev/null +++ b/tests-integration/fixtures/checking2/038_value_recursive_infer/Main.snap @@ -0,0 +1,9 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +id :: forall (t1 :: Type) (t0 :: Type). (t1 :: Type) -> (t0 :: Type) + +Types diff --git a/tests-integration/fixtures/checking2/039_value_mutual_check/Main.purs b/tests-integration/fixtures/checking2/039_value_mutual_check/Main.purs new file mode 100644 index 00000000..5a9680fa --- /dev/null +++ b/tests-integration/fixtures/checking2/039_value_mutual_check/Main.purs @@ -0,0 +1,7 @@ +module Main where + +f :: forall a. a -> a +f a = g a + +g :: forall a. a -> a +g a = f a diff --git a/tests-integration/fixtures/checking2/039_value_mutual_check/Main.snap b/tests-integration/fixtures/checking2/039_value_mutual_check/Main.snap new file mode 100644 index 00000000..0b4fe448 --- /dev/null +++ b/tests-integration/fixtures/checking2/039_value_mutual_check/Main.snap @@ -0,0 +1,10 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +f :: forall (a :: Type). (a :: Type) -> (a :: Type) +g :: forall (a :: Type). (a :: Type) -> (a :: Type) + +Types diff --git a/tests-integration/fixtures/checking2/040_value_mutual_infer/Main.purs b/tests-integration/fixtures/checking2/040_value_mutual_infer/Main.purs new file mode 100644 index 00000000..aa56cab5 --- /dev/null +++ b/tests-integration/fixtures/checking2/040_value_mutual_infer/Main.purs @@ -0,0 +1,5 @@ +module Main where + +f a = g a + +g a = f a diff --git a/tests-integration/fixtures/checking2/040_value_mutual_infer/Main.snap b/tests-integration/fixtures/checking2/040_value_mutual_infer/Main.snap new file mode 100644 index 00000000..1145a8a6 --- /dev/null +++ b/tests-integration/fixtures/checking2/040_value_mutual_infer/Main.snap @@ -0,0 +1,10 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +f :: forall (t1 :: Type) (t0 :: Type). (t1 :: Type) -> (t0 :: Type) +g :: forall (t1 :: Type) (t0 :: Type). (t1 :: Type) -> (t0 :: Type) + +Types diff --git a/tests-integration/fixtures/checking2/041_data_mutual_check/Main.purs b/tests-integration/fixtures/checking2/041_data_mutual_check/Main.purs new file mode 100644 index 00000000..296fb613 --- /dev/null +++ b/tests-integration/fixtures/checking2/041_data_mutual_check/Main.purs @@ -0,0 +1,7 @@ +module Main where + +data F :: forall k. k -> Type +data F a = F (G a) + +data G :: forall k. k -> Type +data G a = G (F a) diff --git a/tests-integration/fixtures/checking2/041_data_mutual_check/Main.snap b/tests-integration/fixtures/checking2/041_data_mutual_check/Main.snap new file mode 100644 index 00000000..c5ac4c1c --- /dev/null +++ b/tests-integration/fixtures/checking2/041_data_mutual_check/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +F :: + forall (k :: Type) (a :: (k :: Type)). + G @(k :: Type) (a :: (k :: Type)) -> F @(k :: Type) (a :: (k :: Type)) +G :: + forall (k :: Type) (a :: (k :: Type)). + F @(k :: Type) (a :: (k :: Type)) -> G @(k :: Type) (a :: (k :: Type)) + +Types +F :: forall (k :: Type). (k :: Type) -> Type +G :: forall (k :: Type). (k :: Type) -> Type + +Roles +F = [Representational] +G = [Representational] diff --git a/tests-integration/fixtures/checking2/042_data_mutual_infer/Main.purs b/tests-integration/fixtures/checking2/042_data_mutual_infer/Main.purs new file mode 100644 index 00000000..105e9ec2 --- /dev/null +++ b/tests-integration/fixtures/checking2/042_data_mutual_infer/Main.purs @@ -0,0 +1,5 @@ +module Main where + +data F a = F (G a) + +data G a = G (F a) diff --git a/tests-integration/fixtures/checking2/042_data_mutual_infer/Main.snap b/tests-integration/fixtures/checking2/042_data_mutual_infer/Main.snap new file mode 100644 index 00000000..6f8626c6 --- /dev/null +++ b/tests-integration/fixtures/checking2/042_data_mutual_infer/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +F :: + forall (t2 :: Type) (a :: (t2 :: Type)). + G (a :: (t2 :: Type)) -> F @(t2 :: Type) (a :: (t2 :: Type)) +G :: + forall (t2 :: Type) (a :: (t2 :: Type)). + F (a :: (t2 :: Type)) -> G @(t2 :: Type) (a :: (t2 :: Type)) + +Types +F :: forall (t2 :: Type). (t2 :: Type) -> Type +G :: forall (t2 :: Type). (t2 :: Type) -> Type + +Roles +F = [Representational] +G = [Representational] diff --git a/tests-integration/fixtures/checking2/043_instance_check/Main.purs b/tests-integration/fixtures/checking2/043_instance_check/Main.purs new file mode 100644 index 00000000..8a03919b --- /dev/null +++ b/tests-integration/fixtures/checking2/043_instance_check/Main.purs @@ -0,0 +1,11 @@ +module Main where + +class Eq :: Type -> Constraint +class Eq a + +instance eqArray :: Eq a => Eq (Array a) + +class TypeEq :: forall k. k -> k -> Constraint +class TypeEq a b | a -> b, b -> a + +instance TypeEq a a diff --git a/tests-integration/fixtures/checking2/043_instance_check/Main.snap b/tests-integration/fixtures/checking2/043_instance_check/Main.snap new file mode 100644 index 00000000..4e93d4fd --- /dev/null +++ b/tests-integration/fixtures/checking2/043_instance_check/Main.snap @@ -0,0 +1,19 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +Eq :: Type -> Constraint +TypeEq :: forall (k :: Type). (k :: Type) -> (k :: Type) -> Constraint + +Classes +class forall (a :: Type). Eq (a :: Type) +class forall (k :: Type) (a :: (k :: Type)) (b :: (k :: Type)). TypeEq @(k :: Type) (a :: (k :: Type)) (b :: (k :: Type)) + +Instances +instance forall (t4 :: Type). Eq (t4 :: Type) => Eq (Array (t4 :: Type)) +instance forall (t6 :: Type) (t5 :: (t6 :: Type)). + TypeEq @(t6 :: Type) (t5 :: (t6 :: Type)) (t5 :: (t6 :: Type)) diff --git a/tests-integration/fixtures/checking2/044_instance_constraint_solving/Main.purs b/tests-integration/fixtures/checking2/044_instance_constraint_solving/Main.purs new file mode 100644 index 00000000..7a12ea78 --- /dev/null +++ b/tests-integration/fixtures/checking2/044_instance_constraint_solving/Main.purs @@ -0,0 +1,19 @@ +module Main where + +class Eq :: Type -> Constraint +class Eq a where + eq :: a -> a -> Boolean + +instance Eq Int where + eq _ _ = true + +instance Eq a => Eq (Array a) where + eq _ _ = false + +test :: Boolean +test = eq 1 2 + +test2 :: Boolean +test2 = eq [1] [2] + +test3 x y = eq x y diff --git a/tests-integration/fixtures/checking2/044_instance_constraint_solving/Main.snap b/tests-integration/fixtures/checking2/044_instance_constraint_solving/Main.snap new file mode 100644 index 00000000..a9b23664 --- /dev/null +++ b/tests-integration/fixtures/checking2/044_instance_constraint_solving/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean +test :: Boolean +test2 :: Boolean +test3 :: forall (t2 :: Type). Eq (t2 :: Type) => (t2 :: Type) -> (t2 :: Type) -> Boolean + +Types +Eq :: Type -> Constraint + +Classes +class forall (a :: Type). Eq (a :: Type) + eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean + +Instances +instance Eq Int +instance forall (t1 :: Type). Eq (t1 :: Type) => Eq (Array (t1 :: Type)) diff --git a/tests-integration/fixtures/checking2/045_instance_functional_dependency/Main.purs b/tests-integration/fixtures/checking2/045_instance_functional_dependency/Main.purs new file mode 100644 index 00000000..1208a6ba --- /dev/null +++ b/tests-integration/fixtures/checking2/045_instance_functional_dependency/Main.purs @@ -0,0 +1,15 @@ +module Main where + +class Convert :: Type -> Type -> Constraint +class Convert a b | a -> b where + convert :: a -> b + +instance Convert Int String where + convert _ = "int" + +test = convert 42 + +class TypeEq :: forall k. k -> k -> Constraint +class TypeEq a b | a -> b, b -> a + +instance TypeEq a a diff --git a/tests-integration/fixtures/checking2/045_instance_functional_dependency/Main.snap b/tests-integration/fixtures/checking2/045_instance_functional_dependency/Main.snap new file mode 100644 index 00000000..a80d1fd8 --- /dev/null +++ b/tests-integration/fixtures/checking2/045_instance_functional_dependency/Main.snap @@ -0,0 +1,24 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +convert :: + forall (a :: Type) (b :: Type). Convert (a :: Type) (b :: Type) => (a :: Type) -> (b :: Type) +test :: String + +Types +Convert :: Type -> Type -> Constraint +TypeEq :: forall (k :: Type). (k :: Type) -> (k :: Type) -> Constraint + +Classes +class forall (a :: Type) (b :: Type). Convert (a :: Type) (b :: Type) + convert :: + forall (a :: Type) (b :: Type). Convert (a :: Type) (b :: Type) => (a :: Type) -> (b :: Type) +class forall (k :: Type) (a :: (k :: Type)) (b :: (k :: Type)). TypeEq @(k :: Type) (a :: (k :: Type)) (b :: (k :: Type)) + +Instances +instance Convert Int String +instance forall (t6 :: Type) (t5 :: (t6 :: Type)). + TypeEq @(t6 :: Type) (t5 :: (t6 :: Type)) (t5 :: (t6 :: Type)) diff --git a/tests-integration/fixtures/checking2/046_instance_given_constraint/Main.purs b/tests-integration/fixtures/checking2/046_instance_given_constraint/Main.purs new file mode 100644 index 00000000..6393608f --- /dev/null +++ b/tests-integration/fixtures/checking2/046_instance_given_constraint/Main.purs @@ -0,0 +1,14 @@ +module Main where + +class Eq :: Type -> Constraint +class Eq a where + eq :: a -> a -> Boolean + +instance Eq Int where + eq _ _ = true + +instance Eq a => Eq (Array a) where + eq _ _ = false + +test :: Boolean +test = eq [[1]] [[2]] diff --git a/tests-integration/fixtures/checking2/046_instance_given_constraint/Main.snap b/tests-integration/fixtures/checking2/046_instance_given_constraint/Main.snap new file mode 100644 index 00000000..fda8a49c --- /dev/null +++ b/tests-integration/fixtures/checking2/046_instance_given_constraint/Main.snap @@ -0,0 +1,19 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean +test :: Boolean + +Types +Eq :: Type -> Constraint + +Classes +class forall (a :: Type). Eq (a :: Type) + eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean + +Instances +instance Eq Int +instance forall (t1 :: Type). Eq (t1 :: Type) => Eq (Array (t1 :: Type)) diff --git a/tests-integration/fixtures/checking2/047_instance_constraint_generalization/Main.purs b/tests-integration/fixtures/checking2/047_instance_constraint_generalization/Main.purs new file mode 100644 index 00000000..3d5f07fd --- /dev/null +++ b/tests-integration/fixtures/checking2/047_instance_constraint_generalization/Main.purs @@ -0,0 +1,12 @@ +module Main where + +class Eq :: Type -> Constraint +class Eq a where + eq :: a -> a -> Boolean + +class Ord :: Type -> Constraint +class Ord a where + compare :: a -> a -> Int + +test1 x = if eq x x then eq x x else false +test2 x = if eq x x then compare x x else compare x x diff --git a/tests-integration/fixtures/checking2/047_instance_constraint_generalization/Main.snap b/tests-integration/fixtures/checking2/047_instance_constraint_generalization/Main.snap new file mode 100644 index 00000000..ebf481f3 --- /dev/null +++ b/tests-integration/fixtures/checking2/047_instance_constraint_generalization/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean +compare :: forall (a :: Type). Ord (a :: Type) => (a :: Type) -> (a :: Type) -> Int +test1 :: forall (t2 :: Type). Eq (t2 :: Type) => (t2 :: Type) -> Boolean +test2 :: forall (t3 :: Type). Ord (t3 :: Type) => Eq (t3 :: Type) => (t3 :: Type) -> Int + +Types +Eq :: Type -> Constraint +Ord :: Type -> Constraint + +Classes +class forall (a :: Type). Eq (a :: Type) + eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean +class forall (a :: Type). Ord (a :: Type) + compare :: forall (a :: Type). Ord (a :: Type) => (a :: Type) -> (a :: Type) -> Int diff --git a/tests-integration/fixtures/checking2/048_instance_superclass_elaboration/Main.purs b/tests-integration/fixtures/checking2/048_instance_superclass_elaboration/Main.purs new file mode 100644 index 00000000..72ceded3 --- /dev/null +++ b/tests-integration/fixtures/checking2/048_instance_superclass_elaboration/Main.purs @@ -0,0 +1,10 @@ +module Main where + +class Eq :: Type -> Constraint +class Eq a where + eq :: a -> a -> Boolean + +class Eq a <= Ord a where + compare :: a -> a -> Int + +test x = if eq x x then compare x x else compare x x diff --git a/tests-integration/fixtures/checking2/048_instance_superclass_elaboration/Main.snap b/tests-integration/fixtures/checking2/048_instance_superclass_elaboration/Main.snap new file mode 100644 index 00000000..32e67be2 --- /dev/null +++ b/tests-integration/fixtures/checking2/048_instance_superclass_elaboration/Main.snap @@ -0,0 +1,19 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean +compare :: forall (a :: Type). Ord (a :: Type) => (a :: Type) -> (a :: Type) -> Int +test :: forall (t2 :: Type). Ord (t2 :: Type) => (t2 :: Type) -> Int + +Types +Eq :: Type -> Constraint +Ord :: Type -> Constraint + +Classes +class forall (a :: Type). Eq (a :: Type) + eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean +class forall (a :: Type). Eq (a :: Type) <= Ord (a :: Type) + compare :: forall (a :: Type). Ord (a :: Type) => (a :: Type) -> (a :: Type) -> Int diff --git a/tests-integration/fixtures/checking2/049_given_constraint_check/Main.purs b/tests-integration/fixtures/checking2/049_given_constraint_check/Main.purs new file mode 100644 index 00000000..4e240c92 --- /dev/null +++ b/tests-integration/fixtures/checking2/049_given_constraint_check/Main.purs @@ -0,0 +1,7 @@ +module Main where + +class Eq a where + eq :: a -> a -> Boolean + +test :: forall a. Eq a => a -> Boolean +test a = eq a a diff --git a/tests-integration/fixtures/checking2/049_given_constraint_check/Main.snap b/tests-integration/fixtures/checking2/049_given_constraint_check/Main.snap new file mode 100644 index 00000000..8fafd85d --- /dev/null +++ b/tests-integration/fixtures/checking2/049_given_constraint_check/Main.snap @@ -0,0 +1,15 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean +test :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> Boolean + +Types +Eq :: Type -> Constraint + +Classes +class forall (a :: Type). Eq (a :: Type) + eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean diff --git a/tests-integration/fixtures/checking2/050_given_constraint_let_check/Main.purs b/tests-integration/fixtures/checking2/050_given_constraint_let_check/Main.purs new file mode 100644 index 00000000..7b48edbe --- /dev/null +++ b/tests-integration/fixtures/checking2/050_given_constraint_let_check/Main.purs @@ -0,0 +1,10 @@ +module Main where + +class Eq a where + eq :: a -> a -> Boolean + +test :: Int +test = 42 + where + impl :: forall a. Eq a => a -> Boolean + impl a = eq a a diff --git a/tests-integration/fixtures/checking2/050_given_constraint_let_check/Main.snap b/tests-integration/fixtures/checking2/050_given_constraint_let_check/Main.snap new file mode 100644 index 00000000..128a5aaf --- /dev/null +++ b/tests-integration/fixtures/checking2/050_given_constraint_let_check/Main.snap @@ -0,0 +1,15 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean +test :: Int + +Types +Eq :: Type -> Constraint + +Classes +class forall (a :: Type). Eq (a :: Type) + eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean diff --git a/tests-integration/fixtures/checking2/051_given_constraint_operator_check/Main.purs b/tests-integration/fixtures/checking2/051_given_constraint_operator_check/Main.purs new file mode 100644 index 00000000..829fcaf5 --- /dev/null +++ b/tests-integration/fixtures/checking2/051_given_constraint_operator_check/Main.purs @@ -0,0 +1,6 @@ +module Main where + +class Eq a where + eq :: a -> a -> Boolean + +infix 5 eq as == diff --git a/tests-integration/fixtures/checking2/051_given_constraint_operator_check/Main.snap b/tests-integration/fixtures/checking2/051_given_constraint_operator_check/Main.snap new file mode 100644 index 00000000..3d9a4cdd --- /dev/null +++ b/tests-integration/fixtures/checking2/051_given_constraint_operator_check/Main.snap @@ -0,0 +1,15 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean +== :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean + +Types +Eq :: Type -> Constraint + +Classes +class forall (a :: Type). Eq (a :: Type) + eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean diff --git a/tests-integration/fixtures/checking2/052_prim_int/Main.purs b/tests-integration/fixtures/checking2/052_prim_int/Main.purs new file mode 100644 index 00000000..22f910c9 --- /dev/null +++ b/tests-integration/fixtures/checking2/052_prim_int/Main.purs @@ -0,0 +1,44 @@ +module Main where + +import Prim.Int (class Add, class Compare, class Mul, class ToString) +import Prim.Ordering (EQ, GT, LT) +import Type.Proxy (Proxy(..)) + +deriveSum :: forall sum. Add 1 2 sum => Proxy sum +deriveSum = Proxy + +deriveRight :: forall right. Add 1 right 3 => Proxy right +deriveRight = Proxy + +deriveLeft :: forall left. Add left 2 3 => Proxy left +deriveLeft = Proxy + +stuckAdd :: forall left sum. Add left 1 sum => Proxy left -> Proxy sum +stuckAdd _ = Proxy + +deriveMul :: forall product. Mul 3 4 product => Proxy product +deriveMul = Proxy + +compareLT :: forall ord. Compare 1 2 ord => Proxy ord +compareLT = Proxy + +compareEQ :: forall ord. Compare 5 5 ord => Proxy ord +compareEQ = Proxy + +compareGT :: forall ord. Compare 10 3 ord => Proxy ord +compareGT = Proxy + +deriveString :: forall s. ToString 42 s => Proxy s +deriveString = Proxy + +forceSolve = + { deriveSum + , deriveRight + , deriveLeft + , deriveMul + , compareLT + , compareEQ + , compareGT + , deriveString + , keepStuck: stuckAdd (Proxy :: Proxy 0) + } diff --git a/tests-integration/fixtures/checking2/052_prim_int/Main.snap b/tests-integration/fixtures/checking2/052_prim_int/Main.snap new file mode 100644 index 00000000..88ecaf19 --- /dev/null +++ b/tests-integration/fixtures/checking2/052_prim_int/Main.snap @@ -0,0 +1,33 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +deriveSum :: forall (sum :: Int). Add 1 2 (sum :: Int) => Proxy @Int (sum :: Int) +deriveRight :: forall (right :: Int). Add 1 (right :: Int) 3 => Proxy @Int (right :: Int) +deriveLeft :: forall (left :: Int). Add (left :: Int) 2 3 => Proxy @Int (left :: Int) +stuckAdd :: + forall (left :: Int) (sum :: Int). + Add (left :: Int) 1 (sum :: Int) => Proxy @Int (left :: Int) -> Proxy @Int (sum :: Int) +deriveMul :: forall (product :: Int). Mul 3 4 (product :: Int) => Proxy @Int (product :: Int) +compareLT :: + forall (ord :: Ordering). Compare 1 2 (ord :: Ordering) => Proxy @Ordering (ord :: Ordering) +compareEQ :: + forall (ord :: Ordering). Compare 5 5 (ord :: Ordering) => Proxy @Ordering (ord :: Ordering) +compareGT :: + forall (ord :: Ordering). Compare 10 3 (ord :: Ordering) => Proxy @Ordering (ord :: Ordering) +deriveString :: forall (s :: Symbol). ToString 42 (s :: Symbol) => Proxy @Symbol (s :: Symbol) +forceSolve :: + { compareEQ :: Proxy @Ordering EQ + , compareGT :: Proxy @Ordering GT + , compareLT :: Proxy @Ordering LT + , deriveLeft :: Proxy @Int 1 + , deriveMul :: Proxy @Int 12 + , deriveRight :: Proxy @Int 2 + , deriveString :: Proxy @Symbol "42" + , deriveSum :: Proxy @Int 3 + , keepStuck :: Proxy @Int 1 + } + +Types diff --git a/tests-integration/fixtures/checking2/053_prim_int_compare_transitive/Main.purs b/tests-integration/fixtures/checking2/053_prim_int_compare_transitive/Main.purs new file mode 100644 index 00000000..4ef2952a --- /dev/null +++ b/tests-integration/fixtures/checking2/053_prim_int_compare_transitive/Main.purs @@ -0,0 +1,26 @@ +module Main where + +import Prim.Int (class Compare) +import Prim.Ordering (EQ, GT, LT) +import Type.Proxy (Proxy(..)) + +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 + +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 + +transEqGt :: forall m n p. Compare m n EQ => Compare n p GT => Proxy n -> Proxy ( left :: m, right :: p ) +transEqGt _ = 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/checking2/053_prim_int_compare_transitive/Main.snap b/tests-integration/fixtures/checking2/053_prim_int_compare_transitive/Main.snap new file mode 100644 index 00000000..32ad64d9 --- /dev/null +++ b/tests-integration/fixtures/checking2/053_prim_int_compare_transitive/Main.snap @@ -0,0 +1,37 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +assertLesser :: + forall (l :: Int) (r :: Int). + Compare (l :: Int) (r :: Int) LT => Proxy @(Row Int) ( left :: (l :: Int), right :: (r :: Int) ) +assertGreater :: + forall (l :: Int) (r :: Int). + Compare (l :: Int) (r :: Int) GT => Proxy @(Row Int) ( left :: (l :: Int), right :: (r :: Int) ) +assertEqual :: + forall (l :: Int) (r :: Int). + Compare (l :: Int) (r :: Int) EQ => Proxy @(Row Int) ( left :: (l :: Int), right :: (r :: Int) ) +transLt :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare (m :: Int) (n :: Int) LT => + Compare (n :: Int) (p :: Int) LT => + Proxy @Int (n :: Int) -> Proxy @(Row Int) ( left :: (m :: Int), right :: (p :: Int) ) +transLtEq :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare (m :: Int) (n :: Int) LT => + Compare (n :: Int) (p :: Int) EQ => + Proxy @Int (n :: Int) -> Proxy @(Row Int) ( left :: (m :: Int), right :: (p :: Int) ) +transEqGt :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare (m :: Int) (n :: Int) EQ => + Compare (n :: Int) (p :: Int) GT => + Proxy @Int (n :: Int) -> Proxy @(Row Int) ( left :: (m :: Int), right :: (p :: Int) ) +transSymmEq :: + forall (m :: Int) (n :: Int) (p :: Int). + Compare (n :: Int) (m :: Int) EQ => + Compare (n :: Int) (p :: Int) EQ => + Proxy @Int (n :: Int) -> Proxy @(Row Int) ( left :: (m :: Int), right :: (p :: Int) ) + +Types diff --git a/tests-integration/fixtures/checking2/054_prim_symbol/Main.purs b/tests-integration/fixtures/checking2/054_prim_symbol/Main.purs new file mode 100644 index 00000000..ac723674 --- /dev/null +++ b/tests-integration/fixtures/checking2/054_prim_symbol/Main.purs @@ -0,0 +1,47 @@ +module Main where + +import Data.Symbol (class IsSymbol, reflectSymbol) +import Prim.Symbol (class Append, class Compare, class Cons) +import Type.Proxy (Proxy(..)) + +deriveAppended :: forall appended. Append "Hello" "World" appended => Proxy appended +deriveAppended = Proxy + +deriveLeft :: forall left. Append left "World" "HelloWorld" => Proxy left +deriveLeft = Proxy + +deriveRight :: forall right. Append "Hello" right "HelloWorld" => Proxy right +deriveRight = Proxy + +compareLT :: forall ord. Compare "a" "b" ord => Proxy ord +compareLT = Proxy + +compareEQ :: forall ord. Compare "hello" "hello" ord => Proxy ord +compareEQ = Proxy + +compareGT :: forall ord. Compare "z" "a" ord => Proxy ord +compareGT = Proxy + +deriveCons :: forall symbol. Cons "H" "ello" symbol => Proxy symbol +deriveCons = Proxy + +deriveHeadTail :: forall head tail. Cons head tail "World" => Proxy head +deriveHeadTail = Proxy + +symbolValue :: String +symbolValue = reflectSymbol (Proxy :: Proxy "hello") + +symbolValue' = reflectSymbol (Proxy :: Proxy "") + +forceSolve = + { deriveAppended + , deriveLeft + , deriveRight + , compareLT + , compareEQ + , compareGT + , deriveCons + , deriveHeadTail + , symbolValue + , symbolValue' + } diff --git a/tests-integration/fixtures/checking2/054_prim_symbol/Main.snap b/tests-integration/fixtures/checking2/054_prim_symbol/Main.snap new file mode 100644 index 00000000..856f329c --- /dev/null +++ b/tests-integration/fixtures/checking2/054_prim_symbol/Main.snap @@ -0,0 +1,43 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +deriveAppended :: + forall (appended :: Symbol). + Append "Hello" "World" (appended :: Symbol) => Proxy @Symbol (appended :: Symbol) +deriveLeft :: + forall (left :: Symbol). + Append (left :: Symbol) "World" "HelloWorld" => Proxy @Symbol (left :: Symbol) +deriveRight :: + forall (right :: Symbol). + Append "Hello" (right :: Symbol) "HelloWorld" => Proxy @Symbol (right :: Symbol) +compareLT :: + forall (ord :: Ordering). Compare "a" "b" (ord :: Ordering) => Proxy @Ordering (ord :: Ordering) +compareEQ :: + forall (ord :: Ordering). + Compare "hello" "hello" (ord :: Ordering) => Proxy @Ordering (ord :: Ordering) +compareGT :: + forall (ord :: Ordering). Compare "z" "a" (ord :: Ordering) => Proxy @Ordering (ord :: Ordering) +deriveCons :: + forall (symbol :: Symbol). Cons "H" "ello" (symbol :: Symbol) => Proxy @Symbol (symbol :: Symbol) +deriveHeadTail :: + forall (head :: Symbol) (tail :: Symbol). + Cons (head :: Symbol) (tail :: Symbol) "World" => Proxy @Symbol (head :: Symbol) +symbolValue :: String +symbolValue' :: String +forceSolve :: + { compareEQ :: Proxy @Ordering EQ + , compareGT :: Proxy @Ordering GT + , compareLT :: Proxy @Ordering LT + , deriveAppended :: Proxy @Symbol "HelloWorld" + , deriveCons :: Proxy @Symbol "Hello" + , deriveHeadTail :: Proxy @Symbol "W" + , deriveLeft :: Proxy @Symbol "Hello" + , deriveRight :: Proxy @Symbol "World" + , symbolValue :: String + , symbolValue' :: String + } + +Types diff --git a/tests-integration/fixtures/checking2/055_prim_solver_apart/Main.purs b/tests-integration/fixtures/checking2/055_prim_solver_apart/Main.purs new file mode 100644 index 00000000..68f8fb1d --- /dev/null +++ b/tests-integration/fixtures/checking2/055_prim_solver_apart/Main.purs @@ -0,0 +1,25 @@ +module Main where + +import Prim.Int as Int +import Prim.Ordering (LT) +import Prim.Symbol as Symbol +import Type.Proxy (Proxy(..)) + +invalidAdd :: Int.Add 2 3 10 => Proxy 10 +invalidAdd = Proxy + +invalidCompare :: Int.Compare 5 1 LT => Proxy LT +invalidCompare = Proxy + +invalidAppend :: Symbol.Append "hello" "world" "xyz" => Proxy "xyz" +invalidAppend = Proxy + +invalidCons :: Symbol.Cons "hello" "world" "helloworld" => Proxy "world" +invalidCons = Proxy + +forceSolve = + { invalidAdd + , invalidCompare + , invalidAppend + , invalidCons + } diff --git a/tests-integration/fixtures/checking2/055_prim_solver_apart/Main.snap b/tests-integration/fixtures/checking2/055_prim_solver_apart/Main.snap new file mode 100644 index 00000000..d51fcc9f --- /dev/null +++ b/tests-integration/fixtures/checking2/055_prim_solver_apart/Main.snap @@ -0,0 +1,44 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +invalidAdd :: Add 2 3 10 => Proxy @Int 10 +invalidCompare :: Compare 5 1 LT => Proxy @Ordering LT +invalidAppend :: Append "hello" "world" "xyz" => Proxy @Symbol "xyz" +invalidCons :: Cons "hello" "world" "helloworld" => Proxy @Symbol "world" +forceSolve :: + { invalidAdd :: Proxy @Int 10 + , invalidAppend :: Proxy @Symbol "xyz" + , invalidCompare :: Proxy @Ordering LT + , invalidCons :: Proxy @Symbol "world" + } + +Types + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(21), + }, + crumbs: [], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(22), + }, + crumbs: [], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(23), + }, + crumbs: [], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(24), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/056_prim_row_list/Main.purs b/tests-integration/fixtures/checking2/056_prim_row_list/Main.purs new file mode 100644 index 00000000..215d6ed4 --- /dev/null +++ b/tests-integration/fixtures/checking2/056_prim_row_list/Main.purs @@ -0,0 +1,27 @@ +module Main where + +import Prim.RowList as RL +import Type.Proxy (Proxy(..)) + +rowToListSimple :: forall list. RL.RowToList (a :: Int) list => Proxy list +rowToListSimple = Proxy + +rowToListMultiple :: forall list. RL.RowToList (b :: String, a :: Int) list => Proxy list +rowToListMultiple = Proxy + +rowToListEmpty :: forall list. RL.RowToList () list => Proxy list +rowToListEmpty = Proxy + +rowToListThree :: forall list. RL.RowToList (c :: Boolean, a :: Int, b :: String) list => Proxy list +rowToListThree = Proxy + +stuckOpenRow :: forall tail list. RL.RowToList (a :: Int | tail) list => Proxy tail -> Proxy list +stuckOpenRow _ = Proxy + +forceSolve = + { rowToListSimple + , rowToListMultiple + , rowToListEmpty + , rowToListThree + , keepStuck: stuckOpenRow (Proxy :: Proxy ()) + } diff --git a/tests-integration/fixtures/checking2/056_prim_row_list/Main.snap b/tests-integration/fixtures/checking2/056_prim_row_list/Main.snap new file mode 100644 index 00000000..553a0257 --- /dev/null +++ b/tests-integration/fixtures/checking2/056_prim_row_list/Main.snap @@ -0,0 +1,40 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +rowToListSimple :: + forall (list :: RowList Type). + RowToList @Type ( a :: Int ) (list :: RowList Type) => + Proxy @(RowList Type) (list :: RowList Type) +rowToListMultiple :: + forall (list :: RowList Type). + RowToList @Type ( a :: Int, b :: String ) (list :: RowList Type) => + Proxy @(RowList Type) (list :: RowList Type) +rowToListEmpty :: + forall (t3 :: Type) (list :: RowList (t3 :: Type)). + RowToList @(t3 :: Type) () (list :: RowList (t3 :: Type)) => + Proxy @(RowList (t3 :: Type)) (list :: RowList (t3 :: Type)) +rowToListThree :: + forall (list :: RowList Type). + RowToList @Type ( a :: Int, b :: String, c :: Boolean ) (list :: RowList Type) => + Proxy @(RowList Type) (list :: RowList Type) +stuckOpenRow :: + forall (tail :: Row Type) (list :: RowList Type). + RowToList @Type ( a :: Int | (tail :: Row Type) ) (list :: RowList Type) => + Proxy @(Row Type) (tail :: Row Type) -> Proxy @(RowList Type) (list :: RowList Type) +forceSolve :: + forall (t8 :: RowList Type) (t7 :: Type). + RowToList @Type ( a :: Int ) (t8 :: RowList Type) => + { keepStuck :: Proxy @(RowList Type) (t8 :: RowList Type) + , rowToListEmpty :: Proxy @(RowList (t7 :: Type)) (Nil @(t7 :: Type)) + , rowToListMultiple :: + Proxy @(RowList Type) (Cons @Type "a" Int (Cons @Type "b" String (Nil @Type))) + , rowToListSimple :: Proxy @(RowList Type) (Cons @Type "a" Int (Nil @Type)) + , rowToListThree :: + Proxy @(RowList Type) + (Cons @Type "a" Int (Cons @Type "b" String (Cons @Type "c" Boolean (Nil @Type)))) + } + +Types diff --git a/tests-integration/fixtures/checking2/057_prim_row/Main.purs b/tests-integration/fixtures/checking2/057_prim_row/Main.purs new file mode 100644 index 00000000..704f61ef --- /dev/null +++ b/tests-integration/fixtures/checking2/057_prim_row/Main.purs @@ -0,0 +1,76 @@ +module Main where + +import Prim.Row as Row +import Type.Proxy (Proxy(..)) + +deriveUnion :: forall u. Row.Union (a :: Int) (b :: String) u => Proxy u +deriveUnion = Proxy + +deriveUnionLeft :: forall l. Row.Union l (b :: String) (a :: Int, b :: String) => Proxy l +deriveUnionLeft = Proxy + +deriveUnionRight :: forall r. Row.Union (a :: Int) r (a :: Int, b :: String) => Proxy r +deriveUnionRight = Proxy + +unionEmptyLeft :: forall u. Row.Union () (a :: Int) u => Proxy u +unionEmptyLeft = Proxy + +unionEmptyRight :: forall u. Row.Union (a :: Int) () u => Proxy u +unionEmptyRight = Proxy + +unionBothEmpty :: forall u. Row.Union () () u => Proxy u +unionBothEmpty = Proxy + +unionMultiple :: forall u. Row.Union (a :: Int, b :: String) (c :: Boolean) u => Proxy u +unionMultiple = Proxy + +deriveCons :: forall row. Row.Cons "name" String () row => Proxy row +deriveCons = Proxy + +deriveTail :: forall tail. Row.Cons "name" String tail (name :: String, age :: Int) => Proxy tail +deriveTail = Proxy + +deriveType :: forall t. Row.Cons "name" t () (name :: String) => Proxy t +deriveType = Proxy + +nestedCons :: forall row. Row.Cons "a" Int (b :: String) row => Proxy row +nestedCons = Proxy + +lacksSimple :: forall r. Row.Lacks "missing" (a :: Int, b :: String) => Proxy r -> Proxy r +lacksSimple = \x -> x + +lacksEmpty :: forall r. Row.Lacks "anything" () => Proxy r -> Proxy r +lacksEmpty = \x -> x + +nubNoDuplicates :: forall nubbed. Row.Nub (a :: Int, b :: String) nubbed => Proxy nubbed +nubNoDuplicates = Proxy + +nubEmpty :: forall nubbed. Row.Nub () nubbed => Proxy nubbed +nubEmpty = Proxy + +solveUnion = + { deriveUnion + , deriveUnionLeft + , deriveUnionRight + , unionEmptyLeft + , unionEmptyRight + , unionBothEmpty + , unionMultiple + } + +solveCons = + { deriveCons + , deriveTail + , deriveType + , nestedCons + } + +solveLacks = + { lacksSimple: lacksSimple Proxy + , lacksEmpty: lacksEmpty Proxy + } + +solveNub = + { nubNoDuplicates + , nubEmpty + } diff --git a/tests-integration/fixtures/checking2/057_prim_row/Main.snap b/tests-integration/fixtures/checking2/057_prim_row/Main.snap new file mode 100644 index 00000000..73d68421 --- /dev/null +++ b/tests-integration/fixtures/checking2/057_prim_row/Main.snap @@ -0,0 +1,86 @@ +--- +source: tests-integration/tests/checking2/generated.rs +expression: report +--- +Terms +deriveUnion :: + forall (u :: Row Type). + Union @Type ( a :: Int ) ( b :: String ) (u :: Row Type) => Proxy @(Row Type) (u :: Row Type) +deriveUnionLeft :: + forall (l :: Row Type). + Union @Type (l :: Row Type) ( b :: String ) ( a :: Int, b :: String ) => + Proxy @(Row Type) (l :: Row Type) +deriveUnionRight :: + forall (r :: Row Type). + Union @Type ( a :: Int ) (r :: Row Type) ( a :: Int, b :: String ) => + Proxy @(Row Type) (r :: Row Type) +unionEmptyLeft :: + forall (u :: Row Type). + Union @Type () ( a :: Int ) (u :: Row Type) => Proxy @(Row Type) (u :: Row Type) +unionEmptyRight :: + forall (u :: Row Type). + Union @Type ( a :: Int ) () (u :: Row Type) => Proxy @(Row Type) (u :: Row Type) +unionBothEmpty :: + forall (t6 :: Type) (u :: Row (t6 :: Type)). + Union @(t6 :: Type) () () (u :: Row (t6 :: Type)) => + Proxy @(Row (t6 :: Type)) (u :: Row (t6 :: Type)) +unionMultiple :: + forall (u :: Row Type). + Union @Type ( a :: Int, b :: String ) ( c :: Boolean ) (u :: Row Type) => + Proxy @(Row Type) (u :: Row Type) +deriveCons :: + forall (row :: Row Type). + Cons @Type "name" String () (row :: Row Type) => Proxy @(Row Type) (row :: Row Type) +deriveTail :: + forall (tail :: Row Type). + Cons @Type "name" String (tail :: Row Type) ( age :: Int, name :: String ) => + Proxy @(Row Type) (tail :: Row Type) +deriveType :: + forall (t :: Type). Cons @Type "name" (t :: Type) () ( name :: String ) => Proxy @Type (t :: Type) +nestedCons :: + forall (row :: Row Type). + Cons @Type "a" Int ( b :: String ) (row :: Row Type) => Proxy @(Row Type) (row :: Row Type) +lacksSimple :: + forall (t13 :: Type) (r :: (t13 :: Type)). + Lacks @Type "missing" ( a :: Int, b :: String ) => + Proxy @(t13 :: Type) (r :: (t13 :: Type)) -> Proxy @(t13 :: Type) (r :: (t13 :: Type)) +lacksEmpty :: + forall (t16 :: Type) (t15 :: Type) (r :: (t16 :: Type)). + Lacks @(t15 :: Type) "anything" () => + Proxy @(t16 :: Type) (r :: (t16 :: Type)) -> Proxy @(t16 :: Type) (r :: (t16 :: Type)) +nubNoDuplicates :: + forall (nubbed :: Row Type). + Nub @Type ( a :: Int, b :: String ) (nubbed :: Row Type) => + Proxy @(Row Type) (nubbed :: Row Type) +nubEmpty :: + forall (t19 :: Type) (nubbed :: Row (t19 :: Type)). + Nub @(t19 :: Type) () (nubbed :: Row (t19 :: Type)) => + Proxy @(Row (t19 :: Type)) (nubbed :: Row (t19 :: Type)) +solveUnion :: + forall (t20 :: Type). + { deriveUnion :: Proxy @(Row Type) ( a :: Int, b :: String ) + , deriveUnionLeft :: Proxy @(Row Type) ( a :: Int ) + , deriveUnionRight :: Proxy @(Row Type) ( b :: String ) + , unionBothEmpty :: Proxy @(Row (t20 :: Type)) () + , unionEmptyLeft :: Proxy @(Row Type) ( a :: Int ) + , unionEmptyRight :: Proxy @(Row Type) ( a :: Int ) + , unionMultiple :: Proxy @(Row Type) ( a :: Int, b :: String, c :: Boolean ) + } +solveCons :: + { deriveCons :: Proxy @(Row Type) ( name :: String ) + , deriveTail :: Proxy @(Row Type) ( age :: Int ) + , deriveType :: Proxy @Type String + , nestedCons :: Proxy @(Row Type) ( a :: Int, b :: String ) + } +solveLacks :: + forall (t24 :: Type) (t23 :: (t24 :: Type)) (t22 :: Type) (t21 :: (t22 :: Type)). + { lacksEmpty :: Proxy @(t24 :: Type) (t23 :: (t24 :: Type)) + , lacksSimple :: Proxy @(t22 :: Type) (t21 :: (t22 :: Type)) + } +solveNub :: + forall (t25 :: Type). + { nubEmpty :: Proxy @(Row (t25 :: Type)) () + , nubNoDuplicates :: Proxy @(Row Type) ( a :: Int, b :: String ) + } + +Types diff --git a/tests-integration/fixtures/checking2/058_prim_row_apart/Main.purs b/tests-integration/fixtures/checking2/058_prim_row_apart/Main.purs new file mode 100644 index 00000000..138fae1e --- /dev/null +++ b/tests-integration/fixtures/checking2/058_prim_row_apart/Main.purs @@ -0,0 +1,19 @@ +module Main where + +import Prim.Row as Row +import Type.Proxy (Proxy(..)) + +unionTypeMismatch :: forall l. Row.Union l (b :: String) (a :: Int, b :: Int) => Proxy l +unionTypeMismatch = Proxy + +consMissingLabel :: forall t. Row.Cons "missing" Int t (a :: Int, b :: String) => Proxy t +consMissingLabel = Proxy + +lacksPresent :: Row.Lacks "b" (a :: Int, b :: String) => Proxy (a :: Int, b :: String) +lacksPresent = Proxy + +forceSolve = + { unionTypeMismatch + , consMissingLabel + , lacksPresent + } diff --git a/tests-integration/fixtures/checking2/058_prim_row_apart/Main.snap b/tests-integration/fixtures/checking2/058_prim_row_apart/Main.snap new file mode 100644 index 00000000..9e2fdb30 --- /dev/null +++ b/tests-integration/fixtures/checking2/058_prim_row_apart/Main.snap @@ -0,0 +1,33 @@ +--- +source: tests-integration/tests/checking2/generated.rs +expression: report +--- +Terms +unionTypeMismatch :: + forall (l :: Row Type). + Union @Type (l :: Row Type) ( b :: String ) ( a :: Int, b :: Int ) => + Proxy @(Row Type) (l :: Row Type) +consMissingLabel :: + forall (t :: Row Type). + Cons @Type "missing" Int (t :: Row Type) ( a :: Int, b :: String ) => + Proxy @(Row Type) (t :: Row Type) +lacksPresent :: + Lacks @Type "b" ( a :: Int, b :: String ) => Proxy @(Row Type) ( a :: Int, b :: String ) +forceSolve :: + forall (t3 :: Row Type) (t2 :: Row Type). + Union @Type (t3 :: Row Type) ( b :: String ) ( a :: Int, b :: Int ) => + Cons @Type "missing" Int (t2 :: Row Type) ( a :: Int, b :: String ) => + { consMissingLabel :: Proxy @(Row Type) (t2 :: Row Type) + , lacksPresent :: Proxy @(Row Type) ( a :: Int, b :: String ) + , unionTypeMismatch :: Proxy @(Row Type) (t3 :: Row Type) + } + +Types + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(18), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/059_prim_row_open/Main.purs b/tests-integration/fixtures/checking2/059_prim_row_open/Main.purs new file mode 100644 index 00000000..3a8706c6 --- /dev/null +++ b/tests-integration/fixtures/checking2/059_prim_row_open/Main.purs @@ -0,0 +1,43 @@ +module Main where + +import Prim.Row as Row +import Type.Proxy (Proxy(..)) + +openLeft :: forall r u. Row.Union (a :: Int | r) (b :: String) u => Proxy u +openLeft = Proxy + +openRight :: forall r u. Row.Union (a :: Int) (b :: String | r) u => Proxy u +openRight = Proxy + +backwardLeft :: forall l r. Row.Union l (b :: String) (a :: Int, b :: String | r) => Proxy l +backwardLeft = Proxy + +backwardRight :: forall r u. Row.Union (a :: Int) r (a :: Int, b :: String | u) => Proxy r +backwardRight = Proxy + +consOpen :: forall r row. Row.Cons "x" Int (a :: String | r) row => Proxy row +consOpen = Proxy + +decomposeOpen :: forall t tail r. Row.Cons "x" t tail (x :: Int, a :: String | r) => Proxy t +decomposeOpen = Proxy + +extractTail :: forall tail r. Row.Cons "x" Int tail (x :: Int, a :: String | r) => Proxy tail +extractTail = Proxy + +lacksOpen :: forall r. Row.Lacks "missing" (a :: Int, b :: String | r) => Proxy r -> Int +lacksOpen _ = 0 + +lacksPresent :: forall r. Row.Lacks "a" (a :: Int | r) => Proxy r -> Int +lacksPresent _ = 0 + +forceSolve = + { openLeft + , openRight + , backwardLeft + , backwardRight + , consOpen + , decomposeOpen + , extractTail + , lacksOpen: lacksOpen Proxy + , lacksPresent: lacksPresent Proxy + } diff --git a/tests-integration/fixtures/checking2/059_prim_row_open/Main.snap b/tests-integration/fixtures/checking2/059_prim_row_open/Main.snap new file mode 100644 index 00000000..63891d1a --- /dev/null +++ b/tests-integration/fixtures/checking2/059_prim_row_open/Main.snap @@ -0,0 +1,70 @@ +--- +source: tests-integration/tests/checking2/generated.rs +expression: report +--- +Terms +openLeft :: + forall (r :: Row Type) (u :: Row Type). + Union @Type ( a :: Int | (r :: Row Type) ) ( b :: String ) (u :: Row Type) => + Proxy @(Row Type) (u :: Row Type) +openRight :: + forall (r :: Row Type) (u :: Row Type). + Union @Type ( a :: Int ) ( b :: String | (r :: Row Type) ) (u :: Row Type) => + Proxy @(Row Type) (u :: Row Type) +backwardLeft :: + forall (l :: Row Type) (r :: Row Type). + Union @Type (l :: Row Type) ( b :: String ) ( a :: Int, b :: String | (r :: Row Type) ) => + Proxy @(Row Type) (l :: Row Type) +backwardRight :: + forall (r :: Row Type) (u :: Row Type). + Union @Type ( a :: Int ) (r :: Row Type) ( a :: Int, b :: String | (u :: Row Type) ) => + Proxy @(Row Type) (r :: Row Type) +consOpen :: + forall (r :: Row Type) (row :: Row Type). + Cons @Type "x" Int ( a :: String | (r :: Row Type) ) (row :: Row Type) => + Proxy @(Row Type) (row :: Row Type) +decomposeOpen :: + forall (t :: Type) (tail :: Row Type) (r :: Row Type). + Cons @Type "x" (t :: Type) (tail :: Row Type) ( a :: String, x :: Int | (r :: Row Type) ) => + Proxy @Type (t :: Type) +extractTail :: + forall (tail :: Row Type) (r :: Row Type). + Cons @Type "x" Int (tail :: Row Type) ( a :: String, x :: Int | (r :: Row Type) ) => + Proxy @(Row Type) (tail :: Row Type) +lacksOpen :: + forall (r :: Row Type). + Lacks @Type "missing" ( a :: Int, b :: String | (r :: Row Type) ) => + Proxy @(Row Type) (r :: Row Type) -> Int +lacksPresent :: + forall (r :: Row Type). + Lacks @Type "a" ( a :: Int | (r :: Row Type) ) => Proxy @(Row Type) (r :: Row Type) -> Int +forceSolve :: + forall (t23 :: Row Type) (t22 :: Row Type) (t21 :: Row Type) (t20 :: Row Type) (t19 :: Row Type) + (t18 :: Row Type) (t17 :: Row Type). + Union (t23 :: Row Type) ( b :: String ) (t22 :: Row Type) => + { backwardLeft :: Proxy @(Row Type) ( a :: Int | (t21 :: Row Type) ) + , backwardRight :: Proxy @(Row Type) ( b :: String | (t20 :: Row Type) ) + , consOpen :: Proxy @(Row Type) ( a :: String, x :: Int | (t19 :: Row Type) ) + , decomposeOpen :: Proxy @Type Int + , extractTail :: Proxy @(Row Type) ( a :: String | (t18 :: Row Type) ) + , lacksOpen :: Int + , lacksPresent :: Int + , openLeft :: Proxy @(Row Type) ( a :: Int | (t22 :: Row Type) ) + , openRight :: Proxy @(Row Type) ( a :: Int, b :: String | (t17 :: Row Type) ) + } + +Types + +Errors +CheckError { + kind: AmbiguousConstraint { + constraint: Id(22), + }, + crumbs: [], +} +CheckError { + kind: AmbiguousConstraint { + constraint: Id(23), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/060_prim_row_generalization/Main.purs b/tests-integration/fixtures/checking2/060_prim_row_generalization/Main.purs new file mode 100644 index 00000000..bbf6e43f --- /dev/null +++ b/tests-integration/fixtures/checking2/060_prim_row_generalization/Main.purs @@ -0,0 +1,52 @@ +module Main where + +import Prim.Row as Prim.Row +import Type.Proxy (Proxy(..)) + +foreign import unsafeCoerce :: forall a b. a -> b + +merge + :: forall r1 r2 r3 r4 + . Prim.Row.Union r1 r2 r3 + => Prim.Row.Nub r3 r4 + => Record r1 + -> Record r2 + -> Record r4 +merge _ _ = unsafeCoerce {} + +a = merge { a: 123 } + +b = a { b: 123 } + +fromUnion + :: forall r1 r2 r3 + . Prim.Row.Union r1 r2 r3 + => Record r3 + -> Int +fromUnion _ = unsafeCoerce 0 + +test = fromUnion + +chainedUnion + :: forall r1 r2 r3 r4 r5 + . Prim.Row.Union r1 r2 r3 + => Prim.Row.Union r3 r4 r5 + => Record r1 + -> Record r5 +chainedUnion _ = unsafeCoerce {} + +testChained = chainedUnion { x: 1 } + +multiMerge + :: forall r1 r2 r3 r4 r5 r6 + . Prim.Row.Union r1 r2 r3 + => Prim.Row.Nub r3 r4 + => Prim.Row.Union r4 r5 r6 + => Record r1 + -> Record r5 + -> Record r6 +multiMerge _ _ = unsafeCoerce {} + +testMulti1 = multiMerge { a: 1 } + +testMulti2 = multiMerge { a: 1 } { b: 2 } diff --git a/tests-integration/fixtures/checking2/060_prim_row_generalization/Main.snap b/tests-integration/fixtures/checking2/060_prim_row_generalization/Main.snap new file mode 100644 index 00000000..46eeaab2 --- /dev/null +++ b/tests-integration/fixtures/checking2/060_prim_row_generalization/Main.snap @@ -0,0 +1,55 @@ +--- +source: tests-integration/tests/checking2/generated.rs +expression: report +--- +Terms +unsafeCoerce :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) +merge :: + forall (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) (r4 :: Row Type). + Union @Type (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) => + Nub @Type (r3 :: Row Type) (r4 :: Row Type) => + {| (r1 :: Row Type) } -> {| (r2 :: Row Type) } -> {| (r4 :: Row Type) } +a :: + forall (t8 :: Row Type) (t7 :: Row Type) (t6 :: Row Type). + Nub @Type (t8 :: Row Type) (t7 :: Row Type) => + Union @Type ( a :: Int ) (t6 :: Row Type) (t8 :: Row Type) => + {| (t6 :: Row Type) } -> {| (t7 :: Row Type) } +b :: { a :: Int, b :: Int } +fromUnion :: + forall (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type). + Union @Type (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) => {| (r3 :: Row Type) } -> Int +test :: + forall (t14 :: Row Type) (t13 :: Row Type) (t12 :: Row Type). + Union @Type (t14 :: Row Type) (t13 :: Row Type) (t12 :: Row Type) => + {| (t12 :: Row Type) } -> Int +chainedUnion :: + forall (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) (r4 :: Row Type) (r5 :: Row Type). + Union @Type (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) => + Union @Type (r3 :: Row Type) (r4 :: Row Type) (r5 :: Row Type) => + {| (r1 :: Row Type) } -> {| (r5 :: Row Type) } +testChained :: + forall (t23 :: Row Type) (t22 :: Row Type) (t21 :: Row Type) (t20 :: Row Type). + Union @Type (t23 :: Row Type) (t22 :: Row Type) (t21 :: Row Type) => + Union @Type ( x :: Int ) (t20 :: Row Type) (t23 :: Row Type) => + {| (t21 :: Row Type) } +multiMerge :: + forall (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) (r4 :: Row Type) (r5 :: Row Type) + (r6 :: Row Type). + Union @Type (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) => + Nub @Type (r3 :: Row Type) (r4 :: Row Type) => + Union @Type (r4 :: Row Type) (r5 :: Row Type) (r6 :: Row Type) => + {| (r1 :: Row Type) } -> {| (r5 :: Row Type) } -> {| (r6 :: Row Type) } +testMulti1 :: + forall (t34 :: Row Type) (t33 :: Row Type) (t32 :: Row Type) (t31 :: Row Type) (t30 :: Row Type). + Nub @Type (t34 :: Row Type) (t33 :: Row Type) => + Union @Type (t33 :: Row Type) (t32 :: Row Type) (t31 :: Row Type) => + Union @Type ( a :: Int ) (t30 :: Row Type) (t34 :: Row Type) => + {| (t32 :: Row Type) } -> {| (t31 :: Row Type) } +testMulti2 :: + forall (t38 :: Row Type) (t37 :: Row Type) (t36 :: Row Type) (t35 :: Row Type). + Nub @Type (t38 :: Row Type) (t37 :: Row Type) => + Union @Type ( a :: Int ) (t36 :: Row Type) (t38 :: Row Type) => + Union @Type (t37 :: Row Type) ( b :: Int ) (t35 :: Row Type) => + {| (t35 :: Row Type) } + +Types diff --git a/tests-integration/fixtures/checking2/061_prim_row_nub_left_bias/Main.purs b/tests-integration/fixtures/checking2/061_prim_row_nub_left_bias/Main.purs new file mode 100644 index 00000000..ea387c53 --- /dev/null +++ b/tests-integration/fixtures/checking2/061_prim_row_nub_left_bias/Main.purs @@ -0,0 +1,16 @@ +module Main where + +import Prim.Row as Prim.Row + +foreign import unsafeCoerce :: forall a b. a -> b + +merge + :: forall r1 r2 r3 r4 + . Prim.Row.Union r1 r2 r3 + => Prim.Row.Nub r3 r4 + => Record r1 + -> Record r2 + -> Record r4 +merge _ _ = unsafeCoerce {} + +test = merge { a: 42, b: "life" } { b: 42 } diff --git a/tests-integration/fixtures/checking2/061_prim_row_nub_left_bias/Main.snap b/tests-integration/fixtures/checking2/061_prim_row_nub_left_bias/Main.snap new file mode 100644 index 00000000..2dd9363b --- /dev/null +++ b/tests-integration/fixtures/checking2/061_prim_row_nub_left_bias/Main.snap @@ -0,0 +1,14 @@ +--- +source: tests-integration/tests/checking2/generated.rs +expression: report +--- +Terms +unsafeCoerce :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) +merge :: + forall (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) (r4 :: Row Type). + Union @Type (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) => + Nub @Type (r3 :: Row Type) (r4 :: Row Type) => + {| (r1 :: Row Type) } -> {| (r2 :: Row Type) } -> {| (r4 :: Row Type) } +test :: { a :: Int, b :: String } + +Types diff --git a/tests-integration/fixtures/checking2/062_prim_row_record/Main.purs b/tests-integration/fixtures/checking2/062_prim_row_record/Main.purs new file mode 100644 index 00000000..9ab0137a --- /dev/null +++ b/tests-integration/fixtures/checking2/062_prim_row_record/Main.purs @@ -0,0 +1,21 @@ +module Main where + +import Prim.Row as Row + +foreign import unsafeCoerce :: forall a b. a -> b + +union :: forall r1 r2 r3. Row.Union r1 r2 r3 => Record r1 -> Record r2 -> Record r3 +union _ _ = unsafeCoerce {} + +addField :: forall r. { a :: Int | r } -> { a :: Int, b :: String | r } +addField x = union x { b: "hi" } + +test = addField { a: 1, c: true } + +insertX :: forall r. Row.Lacks "x" r => Record r -> Record (x :: Int | r) +insertX _ = unsafeCoerce {} + +insertOpen :: forall r. Row.Lacks "x" r => { a :: Int | r } -> { x :: Int, a :: Int | r } +insertOpen x = insertX x + +test2 = insertOpen { a: 1, b: "hi" } diff --git a/tests-integration/fixtures/checking2/062_prim_row_record/Main.snap b/tests-integration/fixtures/checking2/062_prim_row_record/Main.snap new file mode 100644 index 00000000..ccc5db30 --- /dev/null +++ b/tests-integration/fixtures/checking2/062_prim_row_record/Main.snap @@ -0,0 +1,24 @@ +--- +source: tests-integration/tests/checking2/generated.rs +expression: report +--- +Terms +unsafeCoerce :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) +union :: + forall (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type). + Union @Type (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) => + {| (r1 :: Row Type) } -> {| (r2 :: Row Type) } -> {| (r3 :: Row Type) } +addField :: + forall (r :: Row Type). + { a :: Int | (r :: Row Type) } -> { a :: Int, b :: String | (r :: Row Type) } +test :: { a :: Int, b :: String, c :: Boolean } +insertX :: + forall (r :: Row Type). + Lacks @Type "x" (r :: Row Type) => {| (r :: Row Type) } -> { x :: Int | (r :: Row Type) } +insertOpen :: + forall (r :: Row Type). + Lacks @Type "x" (r :: Row Type) => + { a :: Int | (r :: Row Type) } -> { a :: Int, x :: Int | (r :: Row Type) } +test2 :: { a :: Int, b :: String, x :: Int } + +Types diff --git a/tests-integration/fixtures/checking2/063_prim_reflectable/Main.purs b/tests-integration/fixtures/checking2/063_prim_reflectable/Main.purs new file mode 100644 index 00000000..19958383 --- /dev/null +++ b/tests-integration/fixtures/checking2/063_prim_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/checking2/063_prim_reflectable/Main.snap b/tests-integration/fixtures/checking2/063_prim_reflectable/Main.snap new file mode 100644 index 00000000..71bb0245 --- /dev/null +++ b/tests-integration/fixtures/checking2/063_prim_reflectable/Main.snap @@ -0,0 +1,22 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +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/checking2/064_prim_type_error_warn/Main.purs b/tests-integration/fixtures/checking2/064_prim_type_error_warn/Main.purs new file mode 100644 index 00000000..d5447949 --- /dev/null +++ b/tests-integration/fixtures/checking2/064_prim_type_error_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/checking2/064_prim_type_error_warn/Main.snap b/tests-integration/fixtures/checking2/064_prim_type_error_warn/Main.snap new file mode 100644 index 00000000..727e1624 --- /dev/null +++ b/tests-integration/fixtures/checking2/064_prim_type_error_warn/Main.snap @@ -0,0 +1,125 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (k :: Type) (a :: (k :: Type)). Proxy @(k :: Type) (a :: (k :: Type)) +warnBasic :: + forall (a :: Type). Warn (Text "This function is deprecated") => (a :: Type) -> (a :: Type) +useWarnBasic :: Int +warnBeside :: + forall (a :: Type). Warn (Beside (Text "Left ") (Text "Right")) => (a :: Type) -> (a :: Type) +useWarnBeside :: Int +warnAbove :: + forall (a :: Type). Warn (Above (Text "Line 1") (Text "Line 2")) => (a :: Type) -> (a :: Type) +useWarnAbove :: Int +warnQuote :: + forall (t6 :: Type) (a :: (t6 :: Type)). + Warn (Beside (Text "Got type: ") (Quote @(t6 :: Type) (a :: (t6 :: Type)))) => + Proxy @(t6 :: Type) (a :: (t6 :: Type)) -> Proxy @(t6 :: Type) (a :: (t6 :: Type)) +useWarnQuote :: Proxy @Type Int +warnQuoteLabel :: + forall (a :: Type). + Warn (Beside (Text "Label: ") (QuoteLabel "myField")) => (a :: Type) -> (a :: Type) +useWarnQuoteLabel :: Int +warnQuoteLabelSpaces :: + forall (a :: Type). + Warn (Beside (Text "Label: ") (QuoteLabel "h e l l o")) => (a :: Type) -> (a :: Type) +useWarnQuoteLabelSpaces :: Int +warnQuoteLabelQuote :: + forall (a :: Type). + Warn (Beside (Text "Label: ") (QuoteLabel "hel\"lo")) => (a :: Type) -> (a :: Type) +useWarnQuoteLabelQuote :: Int +warnQuoteLabelRaw :: + forall (a :: Type). + Warn (Beside (Text "Label: ") (QuoteLabel """raw\nstring""")) => (a :: Type) -> (a :: Type) +useWarnQuoteLabelRaw :: Int + +Types +Proxy :: forall (k :: Type). (k :: Type) -> Type + +Roles +Proxy = [Phantom] + +Errors +CheckError { + kind: CustomWarning { + message_id: Id(12), + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + ], +} +CheckError { + kind: CustomWarning { + message_id: Id(17), + }, + crumbs: [ + TermDeclaration( + Idx::(4), + ), + ], +} +CheckError { + kind: CustomWarning { + message_id: Id(20), + }, + crumbs: [ + TermDeclaration( + Idx::(6), + ), + ], +} +CheckError { + kind: CustomWarning { + message_id: Id(22), + }, + crumbs: [ + TermDeclaration( + Idx::(8), + ), + ], +} +CheckError { + kind: CustomWarning { + message_id: Id(25), + }, + crumbs: [ + TermDeclaration( + Idx::(10), + ), + ], +} +CheckError { + kind: CustomWarning { + message_id: Id(27), + }, + crumbs: [ + TermDeclaration( + Idx::(12), + ), + ], +} +CheckError { + kind: CustomWarning { + message_id: Id(29), + }, + crumbs: [ + TermDeclaration( + Idx::(14), + ), + ], +} +CheckError { + kind: CustomWarning { + message_id: Id(31), + }, + crumbs: [ + TermDeclaration( + Idx::(16), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/065_prim_type_error_fail/Main.purs b/tests-integration/fixtures/checking2/065_prim_type_error_fail/Main.purs new file mode 100644 index 00000000..59995c57 --- /dev/null +++ b/tests-integration/fixtures/checking2/065_prim_type_error_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/checking2/065_prim_type_error_fail/Main.snap b/tests-integration/fixtures/checking2/065_prim_type_error_fail/Main.snap new file mode 100644 index 00000000..4b774ff5 --- /dev/null +++ b/tests-integration/fixtures/checking2/065_prim_type_error_fail/Main.snap @@ -0,0 +1,44 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (k :: Type) (a :: (k :: Type)). Proxy @(k :: Type) (a :: (k :: Type)) +failBasic :: + forall (a :: Type). Fail (Text "This operation is not allowed") => (a :: Type) -> (a :: Type) +useFailBasic :: Int +failComplex :: + forall (t4 :: Type) (a :: (t4 :: Type)). + Fail + (Above (Text "Error:") (Beside (Text "Type ") (Quote @(t4 :: Type) (a :: (t4 :: Type))))) => + Proxy @(t4 :: Type) (a :: (t4 :: Type)) -> Proxy @(t4 :: Type) (a :: (t4 :: Type)) +useFailComplex :: Proxy @Type String + +Types +Proxy :: forall (k :: Type). (k :: Type) -> Type + +Roles +Proxy = [Phantom] + +Errors +CheckError { + kind: CustomFailure { + message_id: Id(12), + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + ], +} +CheckError { + kind: CustomFailure { + message_id: Id(17), + }, + crumbs: [ + TermDeclaration( + Idx::(4), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/066_compiler_solved_superclass_given/Main.purs b/tests-integration/fixtures/checking2/066_compiler_solved_superclass_given/Main.purs new file mode 100644 index 00000000..c2e30958 --- /dev/null +++ b/tests-integration/fixtures/checking2/066_compiler_solved_superclass_given/Main.purs @@ -0,0 +1,23 @@ +module Main where + +import Prim.Row as Row + +foreign import unsafeCoerce :: forall a b. a -> b + +class Row.Union r1 r2 r3 <= Merge r1 r2 r3 + +merge + :: forall r1 r2 r3 + . Row.Union r1 r2 r3 + => Record r1 + -> Record r2 + -> Record r3 +merge _ _ = unsafeCoerce {} + +merge2 + :: forall r1 r2 r3 + . Merge r1 r2 r3 + => Record r1 + -> Record r2 + -> Record r3 +merge2 r1 r2 = merge r1 r2 diff --git a/tests-integration/fixtures/checking2/066_compiler_solved_superclass_given/Main.snap b/tests-integration/fixtures/checking2/066_compiler_solved_superclass_given/Main.snap new file mode 100644 index 00000000..50c3e009 --- /dev/null +++ b/tests-integration/fixtures/checking2/066_compiler_solved_superclass_given/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +unsafeCoerce :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) +merge :: + forall (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type). + Union @Type (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) => + {| (r1 :: Row Type) } -> {| (r2 :: Row Type) } -> {| (r3 :: Row Type) } +merge2 :: + forall (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type). + Merge @Type (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) => + {| (r1 :: Row Type) } -> {| (r2 :: Row Type) } -> {| (r3 :: Row Type) } + +Types +Merge :: forall (t3 :: Type). Row (t3 :: Type) -> Row (t3 :: Type) -> Row (t3 :: Type) -> Constraint + +Classes +class forall (t3 :: Type) (r1 :: Row (t3 :: Type)) (r2 :: Row (t3 :: Type)) (r3 :: Row (t3 :: Type)). Union @(t3 :: Type) (r1 :: Row (t3 :: Type)) (r2 :: Row (t3 :: Type)) (r3 :: Row (t3 :: Type)) <= Merge @(t3 :: Type) (r1 :: Row (t3 :: Type)) (r2 :: Row (t3 :: Type)) (r3 :: Row (t3 :: Type)) diff --git a/tests-integration/fixtures/checking2/067_compiler_solved_superclass_minimisation/Main.purs b/tests-integration/fixtures/checking2/067_compiler_solved_superclass_minimisation/Main.purs new file mode 100644 index 00000000..766270c0 --- /dev/null +++ b/tests-integration/fixtures/checking2/067_compiler_solved_superclass_minimisation/Main.purs @@ -0,0 +1,32 @@ +module Main where + +import Prim.Row as Row +import Type.Proxy (Proxy(..)) + +foreign import unsafeCoerce :: forall a b. a -> b + +class Row.Union r1 r2 r3 <= Merge r1 r2 r3 + +useMerge + :: forall r1 r2 r3 + . Merge r1 r2 r3 + => Proxy r3 + -> Record r1 + -> Record r2 + -> Int +useMerge _ _ _ = unsafeCoerce 0 + +useUnion + :: forall r1 r2 r3 + . Row.Union r1 r2 r3 + => Proxy r3 + -> Record r1 + -> Record r2 + -> Int +useUnion _ _ _ = unsafeCoerce 0 + +testMin p r1 r2 = + let + _ = useMerge p r1 r2 + in + useUnion p r1 r2 diff --git a/tests-integration/fixtures/checking2/067_compiler_solved_superclass_minimisation/Main.snap b/tests-integration/fixtures/checking2/067_compiler_solved_superclass_minimisation/Main.snap new file mode 100644 index 00000000..8f272560 --- /dev/null +++ b/tests-integration/fixtures/checking2/067_compiler_solved_superclass_minimisation/Main.snap @@ -0,0 +1,25 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +unsafeCoerce :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) +useMerge :: + forall (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type). + Merge @Type (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) => + Proxy @(Row Type) (r3 :: Row Type) -> {| (r1 :: Row Type) } -> {| (r2 :: Row Type) } -> Int +useUnion :: + forall (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type). + Union @Type (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) => + Proxy @(Row Type) (r3 :: Row Type) -> {| (r1 :: Row Type) } -> {| (r2 :: Row Type) } -> Int +testMin :: + forall (t14 :: Row Type) (t13 :: Row Type) (t12 :: Row Type). + Merge @Type (t14 :: Row Type) (t13 :: Row Type) (t12 :: Row Type) => + Proxy @(Row Type) (t12 :: Row Type) -> {| (t14 :: Row Type) } -> {| (t13 :: Row Type) } -> Int + +Types +Merge :: forall (t3 :: Type). Row (t3 :: Type) -> Row (t3 :: Type) -> Row (t3 :: Type) -> Constraint + +Classes +class forall (t3 :: Type) (r1 :: Row (t3 :: Type)) (r2 :: Row (t3 :: Type)) (r3 :: Row (t3 :: Type)). Union @(t3 :: Type) (r1 :: Row (t3 :: Type)) (r2 :: Row (t3 :: Type)) (r3 :: Row (t3 :: Type)) <= Merge @(t3 :: Type) (r1 :: Row (t3 :: Type)) (r2 :: Row (t3 :: Type)) (r3 :: Row (t3 :: Type)) diff --git a/tests-integration/fixtures/checking2/068_prim_coercible_reflexivity/Main.purs b/tests-integration/fixtures/checking2/068_prim_coercible_reflexivity/Main.purs new file mode 100644 index 00000000..ff1b6009 --- /dev/null +++ b/tests-integration/fixtures/checking2/068_prim_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/checking2/068_prim_coercible_reflexivity/Main.snap b/tests-integration/fixtures/checking2/068_prim_coercible_reflexivity/Main.snap new file mode 100644 index 00000000..80215717 --- /dev/null +++ b/tests-integration/fixtures/checking2/068_prim_coercible_reflexivity/Main.snap @@ -0,0 +1,11 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +testInt :: Int -> Int +testString :: String -> String +testPoly :: forall (a :: Type). (a :: Type) -> (a :: Type) + +Types diff --git a/tests-integration/fixtures/checking2/069_prim_coercible_newtype/Main.purs b/tests-integration/fixtures/checking2/069_prim_coercible_newtype/Main.purs new file mode 100644 index 00000000..875e3ce9 --- /dev/null +++ b/tests-integration/fixtures/checking2/069_prim_coercible_newtype/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/checking2/069_prim_coercible_newtype/Main.snap b/tests-integration/fixtures/checking2/069_prim_coercible_newtype/Main.snap new file mode 100644 index 00000000..9348fb03 --- /dev/null +++ b/tests-integration/fixtures/checking2/069_prim_coercible_newtype/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Age :: Int -> Age +wrapAge :: Int -> Age +unwrapAge :: Age -> Int +Wrapper :: forall (a :: Type). (a :: Type) -> Wrapper (a :: Type) +wrapValue :: forall (a :: Type). (a :: Type) -> Wrapper (a :: Type) +unwrapValue :: forall (a :: Type). Wrapper (a :: Type) -> (a :: Type) + +Types +Age :: Type +Wrapper :: Type -> Type + +Roles +Age = [] +Wrapper = [Representational] diff --git a/tests-integration/fixtures/checking2/070_prim_coercible_roles/Main.purs b/tests-integration/fixtures/checking2/070_prim_coercible_roles/Main.purs new file mode 100644 index 00000000..d1c2810a --- /dev/null +++ b/tests-integration/fixtures/checking2/070_prim_coercible_roles/Main.purs @@ -0,0 +1,28 @@ +module Main where + +import Safe.Coerce (coerce) + +newtype Age = Age Int + +-- Phantom: different inner types are fine +data Proxy a = Proxy + +coerceProxy :: Proxy Int -> Proxy String +coerceProxy = coerce + +-- Representational: Coercible inner propagates +data Maybe a = Nothing | Just a + +coerceMaybe :: Maybe Age -> Maybe Int +coerceMaybe = coerce + +coerceMaybeReverse :: Maybe Int -> Maybe Age +coerceMaybeReverse = coerce + +-- Record coercion (row decomposition) +coerceRecord :: { name :: String, age :: Age } -> { name :: String, age :: Int } +coerceRecord = coerce + +-- Function decomposition +coerceFunction :: (Int -> Age) -> (Int -> Int) +coerceFunction = coerce diff --git a/tests-integration/fixtures/checking2/070_prim_coercible_roles/Main.snap b/tests-integration/fixtures/checking2/070_prim_coercible_roles/Main.snap new file mode 100644 index 00000000..db78772e --- /dev/null +++ b/tests-integration/fixtures/checking2/070_prim_coercible_roles/Main.snap @@ -0,0 +1,25 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Age :: Int -> Age +Proxy :: forall (t1 :: Type) (a :: (t1 :: Type)). Proxy @(t1 :: Type) (a :: (t1 :: Type)) +coerceProxy :: Proxy @Type Int -> Proxy @Type String +Nothing :: forall (a :: Type). Maybe (a :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +coerceMaybe :: Maybe Age -> Maybe Int +coerceMaybeReverse :: Maybe Int -> Maybe Age +coerceRecord :: { age :: Age, name :: String } -> { age :: Int, name :: String } +coerceFunction :: (Int -> Age) -> Int -> Int + +Types +Age :: Type +Proxy :: forall (t1 :: Type). (t1 :: Type) -> Type +Maybe :: Type -> Type + +Roles +Age = [] +Proxy = [Phantom] +Maybe = [Representational] diff --git a/tests-integration/fixtures/checking2/071_prim_coercible_apart/Main.purs b/tests-integration/fixtures/checking2/071_prim_coercible_apart/Main.purs new file mode 100644 index 00000000..3fcb7b8b --- /dev/null +++ b/tests-integration/fixtures/checking2/071_prim_coercible_apart/Main.purs @@ -0,0 +1,10 @@ +module Main where + +import Safe.Coerce (coerce) + +data Maybe a = Nothing | Just a +data Either a b = Left a | Right b + +-- Different type constructors: should fail +coerceDifferent :: Maybe Int -> Either Int String +coerceDifferent = coerce diff --git a/tests-integration/fixtures/checking2/071_prim_coercible_apart/Main.snap b/tests-integration/fixtures/checking2/071_prim_coercible_apart/Main.snap new file mode 100644 index 00000000..c576118c --- /dev/null +++ b/tests-integration/fixtures/checking2/071_prim_coercible_apart/Main.snap @@ -0,0 +1,27 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe (a :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Left :: forall (a :: Type) (b :: Type). (a :: Type) -> Either (a :: Type) (b :: Type) +Right :: forall (a :: Type) (b :: Type). (b :: Type) -> Either (a :: Type) (b :: Type) +coerceDifferent :: Maybe Int -> Either Int String + +Types +Maybe :: Type -> Type +Either :: Type -> Type -> Type + +Roles +Maybe = [Representational] +Either = [Representational, Representational] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/072_prim_coercible_hidden_constructor/Lib.purs b/tests-integration/fixtures/checking2/072_prim_coercible_hidden_constructor/Lib.purs new file mode 100644 index 00000000..4eb11d87 --- /dev/null +++ b/tests-integration/fixtures/checking2/072_prim_coercible_hidden_constructor/Lib.purs @@ -0,0 +1,3 @@ +module Lib (HiddenAge) where + +newtype HiddenAge = HiddenAge Int diff --git a/tests-integration/fixtures/checking2/072_prim_coercible_hidden_constructor/Main.purs b/tests-integration/fixtures/checking2/072_prim_coercible_hidden_constructor/Main.purs new file mode 100644 index 00000000..189a3fc5 --- /dev/null +++ b/tests-integration/fixtures/checking2/072_prim_coercible_hidden_constructor/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Lib (HiddenAge) +import Safe.Coerce (coerce) + +coerceHidden :: Int -> HiddenAge +coerceHidden = coerce diff --git a/tests-integration/fixtures/checking2/072_prim_coercible_hidden_constructor/Main.snap b/tests-integration/fixtures/checking2/072_prim_coercible_hidden_constructor/Main.snap new file mode 100644 index 00000000..aa6044f7 --- /dev/null +++ b/tests-integration/fixtures/checking2/072_prim_coercible_hidden_constructor/Main.snap @@ -0,0 +1,28 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +coerceHidden :: Int -> HiddenAge + +Types + +Errors +CheckError { + kind: CoercibleConstructorNotInScope { + file_id: Idx::(39), + item_id: Idx::(0), + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/073_prim_coercible_higher_kinded/Lib.purs b/tests-integration/fixtures/checking2/073_prim_coercible_higher_kinded/Lib.purs new file mode 100644 index 00000000..2cfa34fa --- /dev/null +++ b/tests-integration/fixtures/checking2/073_prim_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 :: (Type -> Type) -> Type +type role Container representational diff --git a/tests-integration/fixtures/checking2/073_prim_coercible_higher_kinded/Main.purs b/tests-integration/fixtures/checking2/073_prim_coercible_higher_kinded/Main.purs new file mode 100644 index 00000000..53b9f8ae --- /dev/null +++ b/tests-integration/fixtures/checking2/073_prim_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/checking2/073_prim_coercible_higher_kinded/Main.snap b/tests-integration/fixtures/checking2/073_prim_coercible_higher_kinded/Main.snap new file mode 100644 index 00000000..1ba43f11 --- /dev/null +++ b/tests-integration/fixtures/checking2/073_prim_coercible_higher_kinded/Main.snap @@ -0,0 +1,10 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +coerceContainer :: Container Maybe -> Container MaybeAlias +coerceContainerReverse :: Container MaybeAlias -> Container Maybe + +Types diff --git a/tests-integration/fixtures/checking2/074_prim_coercible_transitivity/Main.purs b/tests-integration/fixtures/checking2/074_prim_coercible_transitivity/Main.purs new file mode 100644 index 00000000..f871ac88 --- /dev/null +++ b/tests-integration/fixtures/checking2/074_prim_coercible_transitivity/Main.purs @@ -0,0 +1,12 @@ +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 diff --git a/tests-integration/fixtures/checking2/074_prim_coercible_transitivity/Main.snap b/tests-integration/fixtures/checking2/074_prim_coercible_transitivity/Main.snap new file mode 100644 index 00000000..6897aabc --- /dev/null +++ b/tests-integration/fixtures/checking2/074_prim_coercible_transitivity/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Age :: Int -> Age +Years :: Age -> Years +coerceTransitive :: Int -> Years +unwrapTransitive :: Years -> Int + +Types +Age :: Type +Years :: Type + +Roles +Age = [] +Years = [] diff --git a/tests-integration/fixtures/checking2/075_prim_coercible_given_symmetry/Main.purs b/tests-integration/fixtures/checking2/075_prim_coercible_given_symmetry/Main.purs new file mode 100644 index 00000000..409937d2 --- /dev/null +++ b/tests-integration/fixtures/checking2/075_prim_coercible_given_symmetry/Main.purs @@ -0,0 +1,9 @@ +module Main where + +import Safe.Coerce (class Coercible, coerce) + +test :: forall a b. Coercible b a => a -> b +test = coerce + +test' :: forall a b. Coercible b a => a -> b +test' value = coerce value diff --git a/tests-integration/fixtures/checking2/075_prim_coercible_given_symmetry/Main.snap b/tests-integration/fixtures/checking2/075_prim_coercible_given_symmetry/Main.snap new file mode 100644 index 00000000..aca2487c --- /dev/null +++ b/tests-integration/fixtures/checking2/075_prim_coercible_given_symmetry/Main.snap @@ -0,0 +1,14 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: + forall (a :: Type) (b :: Type). + Coercible @Type (b :: Type) (a :: Type) => (a :: Type) -> (b :: Type) +test' :: + forall (a :: Type) (b :: Type). + Coercible @Type (b :: Type) (a :: Type) => (a :: Type) -> (b :: Type) + +Types diff --git a/tests-integration/fixtures/checking2/076_instance_member_functor/Main.purs b/tests-integration/fixtures/checking2/076_instance_member_functor/Main.purs new file mode 100644 index 00000000..981ea1a5 --- /dev/null +++ b/tests-integration/fixtures/checking2/076_instance_member_functor/Main.purs @@ -0,0 +1,10 @@ +module Main where + +class Functor :: (Type -> Type) -> Constraint +class Functor f where + map :: forall a b. (a -> b) -> f a -> f b + +data Box a = Box a + +instance Functor Box where + map f (Box x) = Box (f x) diff --git a/tests-integration/fixtures/checking2/076_instance_member_functor/Main.snap b/tests-integration/fixtures/checking2/076_instance_member_functor/Main.snap new file mode 100644 index 00000000..a7b2c5b7 --- /dev/null +++ b/tests-integration/fixtures/checking2/076_instance_member_functor/Main.snap @@ -0,0 +1,32 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +map :: + forall (f :: Type -> Type) (a :: Type) (b :: Type). + Functor (f :: Type -> Type) => + ((a :: Type) -> (b :: Type)) -> + (f :: Type -> Type) (a :: Type) -> + (f :: Type -> Type) (b :: Type) +Box :: forall (a :: Type). (a :: Type) -> Box (a :: Type) + +Types +Functor :: (Type -> Type) -> Constraint +Box :: Type -> Type + +Classes +class forall (f :: Type -> Type). Functor (f :: Type -> Type) + map :: + forall (f :: Type -> Type) (a :: Type) (b :: Type). + Functor (f :: Type -> Type) => + ((a :: Type) -> (b :: Type)) -> + (f :: Type -> Type) (a :: Type) -> + (f :: Type -> Type) (b :: Type) + +Instances +instance Functor Box + +Roles +Box = [Representational] diff --git a/tests-integration/fixtures/checking2/077_instance_member_signature_head_variable/Main.purs b/tests-integration/fixtures/checking2/077_instance_member_signature_head_variable/Main.purs new file mode 100644 index 00000000..e79b3287 --- /dev/null +++ b/tests-integration/fixtures/checking2/077_instance_member_signature_head_variable/Main.purs @@ -0,0 +1,11 @@ +module Main where + +class Show :: Type -> Constraint +class Show a where + show :: a -> String + +data Box a = Box a + +instance Show a => Show (Box a) where + show :: Box a -> String + show (Box x) = show x diff --git a/tests-integration/fixtures/checking2/077_instance_member_signature_head_variable/Main.snap b/tests-integration/fixtures/checking2/077_instance_member_signature_head_variable/Main.snap new file mode 100644 index 00000000..f98d59ca --- /dev/null +++ b/tests-integration/fixtures/checking2/077_instance_member_signature_head_variable/Main.snap @@ -0,0 +1,22 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +show :: forall (a :: Type). Show (a :: Type) => (a :: Type) -> String +Box :: forall (a :: Type). (a :: Type) -> Box (a :: Type) + +Types +Show :: Type -> Constraint +Box :: Type -> Type + +Classes +class forall (a :: Type). Show (a :: Type) + show :: forall (a :: Type). Show (a :: Type) => (a :: Type) -> String + +Instances +instance forall (t2 :: Type). Show (t2 :: Type) => Show (Box (t2 :: Type)) + +Roles +Box = [Representational] diff --git a/tests-integration/fixtures/checking2/078_instance_member_missing_constraint/Main.purs b/tests-integration/fixtures/checking2/078_instance_member_missing_constraint/Main.purs new file mode 100644 index 00000000..1f206ccb --- /dev/null +++ b/tests-integration/fixtures/checking2/078_instance_member_missing_constraint/Main.purs @@ -0,0 +1,11 @@ +module Main where + +class Show :: Type -> Constraint +class Show a where + show :: a -> String + +data Box a = Box a + +instance Show (Box a) where + show :: Box a -> String + show (Box x) = show x diff --git a/tests-integration/fixtures/checking2/078_instance_member_missing_constraint/Main.snap b/tests-integration/fixtures/checking2/078_instance_member_missing_constraint/Main.snap new file mode 100644 index 00000000..26f8e693 --- /dev/null +++ b/tests-integration/fixtures/checking2/078_instance_member_missing_constraint/Main.snap @@ -0,0 +1,34 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +show :: forall (a :: Type). Show (a :: Type) => (a :: Type) -> String +Box :: forall (a :: Type). (a :: Type) -> Box (a :: Type) + +Types +Show :: Type -> Constraint +Box :: Type -> Type + +Classes +class forall (a :: Type). Show (a :: Type) + show :: forall (a :: Type). Show (a :: Type) => (a :: Type) -> String + +Instances +instance forall (t2 :: Type). Show (Box (t2 :: Type)) + +Roles +Box = [Representational] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/079_instance_member_signature_mismatch/Main.purs b/tests-integration/fixtures/checking2/079_instance_member_signature_mismatch/Main.purs new file mode 100644 index 00000000..8a3a37b1 --- /dev/null +++ b/tests-integration/fixtures/checking2/079_instance_member_signature_mismatch/Main.purs @@ -0,0 +1,9 @@ +module Main where + +class Show :: Type -> Constraint +class Show a where + show :: a -> String + +instance Show Int where + show :: Int -> Int + show x = x diff --git a/tests-integration/fixtures/checking2/079_instance_member_signature_mismatch/Main.snap b/tests-integration/fixtures/checking2/079_instance_member_signature_mismatch/Main.snap new file mode 100644 index 00000000..9a8ee83b --- /dev/null +++ b/tests-integration/fixtures/checking2/079_instance_member_signature_mismatch/Main.snap @@ -0,0 +1,52 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +show :: forall (a :: Type). Show (a :: Type) => (a :: Type) -> String + +Types +Show :: Type -> Constraint + +Classes +class forall (a :: Type). Show (a :: Type) + show :: forall (a :: Type). Show (a :: Type) => (a :: Type) -> String + +Instances +instance Show Int + +Errors +CheckError { + kind: CannotUnify { + t1: Id(3), + t2: Id(4), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(5), + t2: Id(6), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: InstanceMemberTypeMismatch { + expected: Id(6), + actual: Id(5), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/080_instance_member_higher_kinded/Main.purs b/tests-integration/fixtures/checking2/080_instance_member_higher_kinded/Main.purs new file mode 100644 index 00000000..60166d66 --- /dev/null +++ b/tests-integration/fixtures/checking2/080_instance_member_higher_kinded/Main.purs @@ -0,0 +1,11 @@ +module Main where + +class Functor :: (Type -> Type) -> Constraint +class Functor f where + map :: forall a b. (a -> b) -> f a -> f b + +newtype Wrap :: forall k. Type -> (k -> Type) -> k -> Type +newtype Wrap e w a = Wrap (w a) + +instance Functor w => Functor (Wrap e w) where + map f (Wrap x) = Wrap (map f x) diff --git a/tests-integration/fixtures/checking2/080_instance_member_higher_kinded/Main.snap b/tests-integration/fixtures/checking2/080_instance_member_higher_kinded/Main.snap new file mode 100644 index 00000000..b2f697dd --- /dev/null +++ b/tests-integration/fixtures/checking2/080_instance_member_higher_kinded/Main.snap @@ -0,0 +1,36 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +map :: + forall (f :: Type -> Type) (a :: Type) (b :: Type). + Functor (f :: Type -> Type) => + ((a :: Type) -> (b :: Type)) -> + (f :: Type -> Type) (a :: Type) -> + (f :: Type -> Type) (b :: Type) +Wrap :: + forall (k :: Type) (e :: Type) (w :: (k :: Type) -> Type) (a :: (k :: Type)). + (w :: (k :: Type) -> Type) (a :: (k :: Type)) -> + Wrap @(k :: Type) (e :: Type) (w :: (k :: Type) -> Type) (a :: (k :: Type)) + +Types +Functor :: (Type -> Type) -> Constraint +Wrap :: forall (k :: Type). Type -> ((k :: Type) -> Type) -> (k :: Type) -> Type + +Classes +class forall (f :: Type -> Type). Functor (f :: Type -> Type) + map :: + forall (f :: Type -> Type) (a :: Type) (b :: Type). + Functor (f :: Type -> Type) => + ((a :: Type) -> (b :: Type)) -> + (f :: Type -> Type) (a :: Type) -> + (f :: Type -> Type) (b :: Type) + +Instances +instance forall (t8 :: Type -> Type) (t7 :: Type). + Functor (t8 :: Type -> Type) => Functor (Wrap @Type (t7 :: Type) (t8 :: Type -> Type)) + +Roles +Wrap = [Phantom, Representational, Nominal] diff --git a/tests-integration/fixtures/checking2/081_instance_member_too_many_binders/Main.purs b/tests-integration/fixtures/checking2/081_instance_member_too_many_binders/Main.purs new file mode 100644 index 00000000..e2c9c5b3 --- /dev/null +++ b/tests-integration/fixtures/checking2/081_instance_member_too_many_binders/Main.purs @@ -0,0 +1,8 @@ +module Main where + +class Show :: Type -> Constraint +class Show a where + show :: a -> String + +instance Show Int where + show x y = show x diff --git a/tests-integration/fixtures/checking2/081_instance_member_too_many_binders/Main.snap b/tests-integration/fixtures/checking2/081_instance_member_too_many_binders/Main.snap new file mode 100644 index 00000000..b58c3bb6 --- /dev/null +++ b/tests-integration/fixtures/checking2/081_instance_member_too_many_binders/Main.snap @@ -0,0 +1,31 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +show :: forall (a :: Type). Show (a :: Type) => (a :: Type) -> String + +Types +Show :: Type -> Constraint + +Classes +class forall (a :: Type). Show (a :: Type) + show :: forall (a :: Type). Show (a :: Type) => (a :: Type) -> String + +Instances +instance Show Int + +Errors +CheckError { + kind: TooManyBinders { + signature: None, + expected: 1, + actual: 2, + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/082_derive_pipeline_smoke/Main.purs b/tests-integration/fixtures/checking2/082_derive_pipeline_smoke/Main.purs new file mode 100644 index 00000000..660bf3b6 --- /dev/null +++ b/tests-integration/fixtures/checking2/082_derive_pipeline_smoke/Main.purs @@ -0,0 +1,10 @@ +module Main where + +import Data.Eq (class Eq, eq) + +data Box = Box + +derive instance Eq Box + +test :: Boolean +test = eq Box Box diff --git a/tests-integration/fixtures/checking2/082_derive_pipeline_smoke/Main.snap b/tests-integration/fixtures/checking2/082_derive_pipeline_smoke/Main.snap new file mode 100644 index 00000000..61e4d042 --- /dev/null +++ b/tests-integration/fixtures/checking2/082_derive_pipeline_smoke/Main.snap @@ -0,0 +1,14 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Box :: Box +test :: Boolean + +Types +Box :: Type + +Roles +Box = [] diff --git a/tests-integration/fixtures/checking2/083_derive_eq_simple/Main.purs b/tests-integration/fixtures/checking2/083_derive_eq_simple/Main.purs new file mode 100644 index 00000000..a2d03a12 --- /dev/null +++ b/tests-integration/fixtures/checking2/083_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/checking2/083_derive_eq_simple/Main.snap b/tests-integration/fixtures/checking2/083_derive_eq_simple/Main.snap new file mode 100644 index 00000000..493aba56 --- /dev/null +++ b/tests-integration/fixtures/checking2/083_derive_eq_simple/Main.snap @@ -0,0 +1,31 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (t1 :: Type) (a :: (t1 :: Type)). Proxy @(t1 :: Type) (a :: (t1 :: Type)) +MkNoEq :: NoEq +MkContainsNoEq :: NoEq -> ContainsNoEq + +Types +Proxy :: forall (t1 :: Type). (t1 :: Type) -> Type +NoEq :: Type +ContainsNoEq :: Type + +Roles +Proxy = [Phantom] +NoEq = [] +ContainsNoEq = [] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(4), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/084_derive_eq_parameterized/Main.purs b/tests-integration/fixtures/checking2/084_derive_eq_parameterized/Main.purs new file mode 100644 index 00000000..02d3d137 --- /dev/null +++ b/tests-integration/fixtures/checking2/084_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/checking2/084_derive_eq_parameterized/Main.snap b/tests-integration/fixtures/checking2/084_derive_eq_parameterized/Main.snap new file mode 100644 index 00000000..0c24dbcf --- /dev/null +++ b/tests-integration/fixtures/checking2/084_derive_eq_parameterized/Main.snap @@ -0,0 +1,14 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe (a :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) + +Types +Maybe :: Type -> Type + +Roles +Maybe = [Representational] diff --git a/tests-integration/fixtures/checking2/085_derive_eq_missing_instance/Main.purs b/tests-integration/fixtures/checking2/085_derive_eq_missing_instance/Main.purs new file mode 100644 index 00000000..8aaea864 --- /dev/null +++ b/tests-integration/fixtures/checking2/085_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/checking2/085_derive_eq_missing_instance/Main.snap b/tests-integration/fixtures/checking2/085_derive_eq_missing_instance/Main.snap new file mode 100644 index 00000000..e85e88fc --- /dev/null +++ b/tests-integration/fixtures/checking2/085_derive_eq_missing_instance/Main.snap @@ -0,0 +1,28 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +MkNoEq :: NoEq +MkBox :: NoEq -> Box + +Types +NoEq :: Type +Box :: Type + +Roles +NoEq = [] +Box = [] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/086_derive_ord_simple/Main.purs b/tests-integration/fixtures/checking2/086_derive_ord_simple/Main.purs new file mode 100644 index 00000000..210b310f --- /dev/null +++ b/tests-integration/fixtures/checking2/086_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/checking2/086_derive_ord_simple/Main.snap b/tests-integration/fixtures/checking2/086_derive_ord_simple/Main.snap new file mode 100644 index 00000000..de637e82 --- /dev/null +++ b/tests-integration/fixtures/checking2/086_derive_ord_simple/Main.snap @@ -0,0 +1,34 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +MkBox :: Int -> Box +MkPair :: Int -> Boolean -> Pair +MkNoOrd :: NoOrd +MkContainsNoOrd :: NoOrd -> ContainsNoOrd + +Types +Box :: Type +Pair :: Type +NoOrd :: Type +ContainsNoOrd :: Type + +Roles +Box = [] +Pair = [] +NoOrd = [] +ContainsNoOrd = [] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(10), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/087_derive_ord_1_higher_kinded/Main.purs b/tests-integration/fixtures/checking2/087_derive_ord_1_higher_kinded/Main.purs new file mode 100644 index 00000000..85ac1a72 --- /dev/null +++ b/tests-integration/fixtures/checking2/087_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/checking2/087_derive_ord_1_higher_kinded/Main.snap b/tests-integration/fixtures/checking2/087_derive_ord_1_higher_kinded/Main.snap new file mode 100644 index 00000000..58e5dc0d --- /dev/null +++ b/tests-integration/fixtures/checking2/087_derive_ord_1_higher_kinded/Main.snap @@ -0,0 +1,74 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +MkWrap :: + forall (t2 :: Type) (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)). + (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) -> + Wrap @(t2 :: Type) (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) +MkWrapNoOrd1 :: + forall (t5 :: Type) (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)). + (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)) -> + WrapNoOrd1 @(t5 :: Type) (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)) + +Types +Wrap :: forall (t2 :: Type). ((t2 :: Type) -> Type) -> (t2 :: Type) -> Type +WrapNoOrd1 :: forall (t5 :: Type). ((t5 :: Type) -> Type) -> (t5 :: Type) -> Type + +Roles +Wrap = [Representational, Nominal] +WrapNoOrd1 = [Representational, Nominal] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(10), + }, + crumbs: [ + TermDeclaration( + Idx::(4), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(11), + }, + crumbs: [ + TermDeclaration( + Idx::(5), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(12), + }, + crumbs: [ + TermDeclaration( + Idx::(5), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/088_derive_eq_1/Main.purs b/tests-integration/fixtures/checking2/088_derive_eq_1/Main.purs new file mode 100644 index 00000000..8ed9a364 --- /dev/null +++ b/tests-integration/fixtures/checking2/088_derive_eq_1/Main.purs @@ -0,0 +1,43 @@ +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) + +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' + +data NoEq a = NoEq a + +derive instance Eq1 NoEq diff --git a/tests-integration/fixtures/checking2/088_derive_eq_1/Main.snap b/tests-integration/fixtures/checking2/088_derive_eq_1/Main.snap new file mode 100644 index 00000000..b31be8ab --- /dev/null +++ b/tests-integration/fixtures/checking2/088_derive_eq_1/Main.snap @@ -0,0 +1,89 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Id :: forall (a :: Type). (a :: Type) -> Id (a :: Type) +Pair :: forall (a :: Type). (a :: Type) -> (a :: Type) -> Pair (a :: Type) +Mixed :: forall (a :: Type). Int -> (a :: Type) -> Boolean -> Mixed (a :: Type) +Rec :: forall (a :: Type). { count :: Int, value :: (a :: Type) } -> Rec (a :: Type) +Wrap :: + forall (t6 :: Type) (f :: (t6 :: Type) -> Type) (a :: (t6 :: Type)). + (f :: (t6 :: Type) -> Type) (a :: (t6 :: Type)) -> + Wrap @(t6 :: Type) (f :: (t6 :: Type) -> Type) (a :: (t6 :: Type)) +Compose :: + forall (t11 :: Type) (t10 :: Type) (f :: (t11 :: Type) -> Type) + (g :: (t10 :: Type) -> (t11 :: Type)) (a :: (t10 :: Type)). + (f :: (t11 :: Type) -> Type) ((g :: (t10 :: Type) -> (t11 :: Type)) (a :: (t10 :: Type))) -> + Compose @(t11 :: Type) @(t10 :: Type) + (f :: (t11 :: Type) -> Type) + (g :: (t10 :: Type) -> (t11 :: Type)) + (a :: (t10 :: Type)) +Left' :: forall (a :: Type). (a :: Type) -> Either' (a :: Type) +Right' :: forall (a :: Type). (a :: Type) -> Either' (a :: Type) +NoEq :: forall (a :: Type). (a :: Type) -> NoEq (a :: Type) + +Types +Id :: Type -> Type +Pair :: Type -> Type +Mixed :: Type -> Type +Rec :: Type -> Type +Wrap :: forall (t6 :: Type). ((t6 :: Type) -> Type) -> (t6 :: Type) -> Type +Compose :: + forall (t11 :: Type) (t10 :: Type). + ((t11 :: Type) -> Type) -> ((t10 :: Type) -> (t11 :: Type)) -> (t10 :: Type) -> Type +Either' :: Type -> Type +NoEq :: Type -> Type + +Roles +Id = [Representational] +Pair = [Representational] +Mixed = [Representational] +Rec = [Representational] +Wrap = [Representational, Nominal] +Compose = [Representational, Representational, Nominal] +Either' = [Representational] +NoEq = [Representational] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(13), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(10), + }, + crumbs: [ + TermDeclaration( + Idx::(16), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(11), + }, + crumbs: [ + TermDeclaration( + Idx::(17), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(12), + }, + crumbs: [ + TermDeclaration( + Idx::(23), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/089_derive_ord_1/Main.purs b/tests-integration/fixtures/checking2/089_derive_ord_1/Main.purs new file mode 100644 index 00000000..ffcba757 --- /dev/null +++ b/tests-integration/fixtures/checking2/089_derive_ord_1/Main.purs @@ -0,0 +1,32 @@ +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) + +derive instance Eq1 f => Eq1 (Compose f g) +derive instance Ord1 f => Ord1 (Compose f g) + +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/checking2/089_derive_ord_1/Main.snap b/tests-integration/fixtures/checking2/089_derive_ord_1/Main.snap new file mode 100644 index 00000000..31243335 --- /dev/null +++ b/tests-integration/fixtures/checking2/089_derive_ord_1/Main.snap @@ -0,0 +1,106 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Id :: forall (a :: Type). (a :: Type) -> Id (a :: Type) +Wrap :: + forall (t3 :: Type) (f :: (t3 :: Type) -> Type) (a :: (t3 :: Type)). + (f :: (t3 :: Type) -> Type) (a :: (t3 :: Type)) -> + Wrap @(t3 :: Type) (f :: (t3 :: Type) -> Type) (a :: (t3 :: Type)) +Compose :: + forall (t8 :: Type) (t7 :: Type) (f :: (t8 :: Type) -> Type) (g :: (t7 :: Type) -> (t8 :: Type)) + (a :: (t7 :: Type)). + (f :: (t8 :: Type) -> Type) ((g :: (t7 :: Type) -> (t8 :: Type)) (a :: (t7 :: Type))) -> + Compose @(t8 :: Type) @(t7 :: Type) + (f :: (t8 :: Type) -> Type) + (g :: (t7 :: Type) -> (t8 :: Type)) + (a :: (t7 :: Type)) +NoOrd :: forall (a :: Type). (a :: Type) -> NoOrd (a :: Type) + +Types +Id :: Type -> Type +Wrap :: forall (t3 :: Type). ((t3 :: Type) -> Type) -> (t3 :: Type) -> Type +Compose :: + forall (t8 :: Type) (t7 :: Type). + ((t8 :: Type) -> Type) -> ((t7 :: Type) -> (t8 :: Type)) -> (t7 :: Type) -> Type +NoOrd :: Type -> Type + +Roles +Id = [Representational] +Wrap = [Representational, Nominal] +Compose = [Representational, Representational, Nominal] +NoOrd = [Representational] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(6), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(10), + }, + crumbs: [ + TermDeclaration( + Idx::(8), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(11), + }, + crumbs: [ + TermDeclaration( + Idx::(11), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(12), + }, + crumbs: [ + TermDeclaration( + Idx::(12), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(13), + }, + crumbs: [ + TermDeclaration( + Idx::(13), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(14), + }, + crumbs: [ + TermDeclaration( + Idx::(14), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(15), + }, + crumbs: [ + TermDeclaration( + Idx::(18), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/090_derive_functor_simple/Main.purs b/tests-integration/fixtures/checking2/090_derive_functor_simple/Main.purs new file mode 100644 index 00000000..391e32cb --- /dev/null +++ b/tests-integration/fixtures/checking2/090_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/checking2/090_derive_functor_simple/Main.snap b/tests-integration/fixtures/checking2/090_derive_functor_simple/Main.snap new file mode 100644 index 00000000..6c077c2b --- /dev/null +++ b/tests-integration/fixtures/checking2/090_derive_functor_simple/Main.snap @@ -0,0 +1,22 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Identity :: forall (a :: Type). (a :: Type) -> Identity (a :: Type) +Const :: + forall (t3 :: Type) (e :: Type) (a :: (t3 :: Type)). + (e :: Type) -> Const @(t3 :: Type) (e :: Type) (a :: (t3 :: Type)) +Nothing :: forall (a :: Type). Maybe (a :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) + +Types +Identity :: Type -> Type +Const :: forall (t3 :: Type). Type -> (t3 :: Type) -> Type +Maybe :: Type -> Type + +Roles +Identity = [Representational] +Const = [Representational, Phantom] +Maybe = [Representational] diff --git a/tests-integration/fixtures/checking2/091_derive_functor_contravariant_error/Main.purs b/tests-integration/fixtures/checking2/091_derive_functor_contravariant_error/Main.purs new file mode 100644 index 00000000..d35c7ea6 --- /dev/null +++ b/tests-integration/fixtures/checking2/091_derive_functor_contravariant_error/Main.purs @@ -0,0 +1,12 @@ +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) + +data Cont r a = Cont ((a -> r) -> r) +derive instance Functor (Cont r) diff --git a/tests-integration/fixtures/checking2/091_derive_functor_contravariant_error/Main.snap b/tests-integration/fixtures/checking2/091_derive_functor_contravariant_error/Main.snap new file mode 100644 index 00000000..1f606cb8 --- /dev/null +++ b/tests-integration/fixtures/checking2/091_derive_functor_contravariant_error/Main.snap @@ -0,0 +1,34 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Predicate :: forall (a :: Type). ((a :: Type) -> Boolean) -> Predicate (a :: Type) +Reader :: + forall (r :: Type) (a :: Type). ((r :: Type) -> (a :: Type)) -> Reader (r :: Type) (a :: Type) +Cont :: + forall (r :: Type) (a :: Type). + (((a :: Type) -> (r :: Type)) -> (r :: Type)) -> Cont (r :: Type) (a :: Type) + +Types +Predicate :: Type -> Type +Reader :: Type -> Type -> Type +Cont :: Type -> Type -> Type + +Roles +Predicate = [Representational] +Reader = [Representational, Representational] +Cont = [Representational, Representational] + +Errors +CheckError { + kind: ContravariantOccurrence { + type_message: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/092_derive_functor_insufficient_params/Main.purs b/tests-integration/fixtures/checking2/092_derive_functor_insufficient_params/Main.purs new file mode 100644 index 00000000..b5309b6b --- /dev/null +++ b/tests-integration/fixtures/checking2/092_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/checking2/092_derive_functor_insufficient_params/Main.snap b/tests-integration/fixtures/checking2/092_derive_functor_insufficient_params/Main.snap new file mode 100644 index 00000000..1806a00e --- /dev/null +++ b/tests-integration/fixtures/checking2/092_derive_functor_insufficient_params/Main.snap @@ -0,0 +1,66 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Pair :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> Pair (a :: Type) (b :: Type) +Unit :: Unit + +Types +Pair :: Type -> Type -> Type +Unit :: Type + +Roles +Pair = [Representational, Representational] +Unit = [] + +Errors +CheckError { + kind: CannotUnify { + t1: Id(6), + t2: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + CheckingKind( + AstId(19), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(6), + t2: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + CheckingKind( + AstId(28), + ), + ], +} +CheckError { + kind: CannotDeriveForType { + type_message: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: CannotDeriveForType { + type_message: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/093_derive_bifunctor_simple/Main.purs b/tests-integration/fixtures/checking2/093_derive_bifunctor_simple/Main.purs new file mode 100644 index 00000000..aa956e4b --- /dev/null +++ b/tests-integration/fixtures/checking2/093_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/checking2/093_derive_bifunctor_simple/Main.snap b/tests-integration/fixtures/checking2/093_derive_bifunctor_simple/Main.snap new file mode 100644 index 00000000..0f50c456 --- /dev/null +++ b/tests-integration/fixtures/checking2/093_derive_bifunctor_simple/Main.snap @@ -0,0 +1,23 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Left :: forall (a :: Type) (b :: Type). (a :: Type) -> Either (a :: Type) (b :: Type) +Right :: forall (a :: Type) (b :: Type). (b :: Type) -> Either (a :: Type) (b :: Type) +Pair :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> Pair (a :: Type) (b :: Type) +Const2 :: + forall (t8 :: Type) (t7 :: Type) (e :: Type) (a :: (t8 :: Type)) (b :: (t7 :: Type)). + (e :: Type) -> + Const2 @(t8 :: Type) @(t7 :: Type) (e :: Type) (a :: (t8 :: Type)) (b :: (t7 :: Type)) + +Types +Either :: Type -> Type -> Type +Pair :: Type -> Type -> Type +Const2 :: forall (t8 :: Type) (t7 :: Type). Type -> (t8 :: Type) -> (t7 :: Type) -> Type + +Roles +Either = [Representational, Representational] +Pair = [Representational, Representational] +Const2 = [Representational, Phantom, Phantom] diff --git a/tests-integration/fixtures/checking2/094_derive_bifunctor_missing_functor/Main.purs b/tests-integration/fixtures/checking2/094_derive_bifunctor_missing_functor/Main.purs new file mode 100644 index 00000000..2a0fb76e --- /dev/null +++ b/tests-integration/fixtures/checking2/094_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/checking2/094_derive_bifunctor_missing_functor/Main.snap b/tests-integration/fixtures/checking2/094_derive_bifunctor_missing_functor/Main.snap new file mode 100644 index 00000000..111f20bc --- /dev/null +++ b/tests-integration/fixtures/checking2/094_derive_bifunctor_missing_functor/Main.snap @@ -0,0 +1,46 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +WrapBoth :: + forall (t5 :: Type) (t4 :: Type) (f :: (t5 :: Type) -> Type) (g :: (t4 :: Type) -> Type) + (a :: (t5 :: Type)) (b :: (t4 :: Type)). + (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)) -> + (g :: (t4 :: Type) -> Type) (b :: (t4 :: Type)) -> + WrapBoth @(t5 :: Type) @(t4 :: Type) + (f :: (t5 :: Type) -> Type) + (g :: (t4 :: Type) -> Type) + (a :: (t5 :: Type)) + (b :: (t4 :: Type)) + +Types +WrapBoth :: + forall (t5 :: Type) (t4 :: Type). + ((t5 :: Type) -> Type) -> ((t4 :: Type) -> Type) -> (t5 :: Type) -> (t4 :: Type) -> Type + +Roles +WrapBoth = [Representational, Representational, Nominal, Nominal] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(10), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/095_derive_bifunctor_insufficient_params/Main.purs b/tests-integration/fixtures/checking2/095_derive_bifunctor_insufficient_params/Main.purs new file mode 100644 index 00000000..9a1af2ee --- /dev/null +++ b/tests-integration/fixtures/checking2/095_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/checking2/095_derive_bifunctor_insufficient_params/Main.snap b/tests-integration/fixtures/checking2/095_derive_bifunctor_insufficient_params/Main.snap new file mode 100644 index 00000000..4c0226ae --- /dev/null +++ b/tests-integration/fixtures/checking2/095_derive_bifunctor_insufficient_params/Main.snap @@ -0,0 +1,41 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Triple :: + forall (a :: Type) (b :: Type) (c :: Type). + (a :: Type) -> (b :: Type) -> (c :: Type) -> Triple (a :: Type) (b :: Type) (c :: Type) + +Types +Triple :: Type -> Type -> Type -> Type + +Roles +Triple = [Representational, Representational, Representational] + +Errors +CheckError { + kind: CannotUnify { + t1: Id(8), + t2: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + CheckingKind( + AstId(21), + ), + ], +} +CheckError { + kind: CannotDeriveForType { + type_message: Id(10), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/096_derive_contravariant_simple/Main.purs b/tests-integration/fixtures/checking2/096_derive_contravariant_simple/Main.purs new file mode 100644 index 00000000..d6c05c84 --- /dev/null +++ b/tests-integration/fixtures/checking2/096_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/checking2/096_derive_contravariant_simple/Main.snap b/tests-integration/fixtures/checking2/096_derive_contravariant_simple/Main.snap new file mode 100644 index 00000000..552e792f --- /dev/null +++ b/tests-integration/fixtures/checking2/096_derive_contravariant_simple/Main.snap @@ -0,0 +1,19 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Predicate :: forall (a :: Type). ((a :: Type) -> Boolean) -> Predicate (a :: Type) +Comparison :: forall (a :: Type). ((a :: Type) -> (a :: Type) -> Boolean) -> Comparison (a :: Type) +Op :: forall (a :: Type) (b :: Type). ((b :: Type) -> (a :: Type)) -> Op (a :: Type) (b :: Type) + +Types +Predicate :: Type -> Type +Comparison :: Type -> Type +Op :: Type -> Type -> Type + +Roles +Predicate = [Representational] +Comparison = [Representational] +Op = [Representational, Representational] diff --git a/tests-integration/fixtures/checking2/097_derive_contravariant_error/Main.purs b/tests-integration/fixtures/checking2/097_derive_contravariant_error/Main.purs new file mode 100644 index 00000000..0713c15e --- /dev/null +++ b/tests-integration/fixtures/checking2/097_derive_contravariant_error/Main.purs @@ -0,0 +1,9 @@ +module Main where + +import Data.Functor.Contravariant (class Contravariant) + +data Identity a = Identity a +derive instance Contravariant Identity + +data Producer a = Producer (Int -> a) +derive instance Contravariant Producer diff --git a/tests-integration/fixtures/checking2/097_derive_contravariant_error/Main.snap b/tests-integration/fixtures/checking2/097_derive_contravariant_error/Main.snap new file mode 100644 index 00000000..11ea5a82 --- /dev/null +++ b/tests-integration/fixtures/checking2/097_derive_contravariant_error/Main.snap @@ -0,0 +1,38 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Identity :: forall (a :: Type). (a :: Type) -> Identity (a :: Type) +Producer :: forall (a :: Type). (Int -> (a :: Type)) -> Producer (a :: Type) + +Types +Identity :: Type -> Type +Producer :: Type -> Type + +Roles +Identity = [Representational] +Producer = [Representational] + +Errors +CheckError { + kind: CovariantOccurrence { + type_message: Id(6), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: CovariantOccurrence { + type_message: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/098_derive_profunctor_simple/Main.purs b/tests-integration/fixtures/checking2/098_derive_profunctor_simple/Main.purs new file mode 100644 index 00000000..4e9b3ce4 --- /dev/null +++ b/tests-integration/fixtures/checking2/098_derive_profunctor_simple/Main.purs @@ -0,0 +1,12 @@ +module Main where + +import Data.Profunctor (class Profunctor) + +data Fn a b = Fn (a -> b) +derive instance Profunctor Fn + +data ConstR r a b = ConstR (a -> r) +derive instance Profunctor (ConstR r) + +data Choice a b = GoLeft (a -> Int) | GoRight b +derive instance Profunctor Choice diff --git a/tests-integration/fixtures/checking2/098_derive_profunctor_simple/Main.snap b/tests-integration/fixtures/checking2/098_derive_profunctor_simple/Main.snap new file mode 100644 index 00000000..0047bce6 --- /dev/null +++ b/tests-integration/fixtures/checking2/098_derive_profunctor_simple/Main.snap @@ -0,0 +1,22 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Fn :: forall (a :: Type) (b :: Type). ((a :: Type) -> (b :: Type)) -> Fn (a :: Type) (b :: Type) +ConstR :: + forall (t5 :: Type) (r :: Type) (a :: Type) (b :: (t5 :: Type)). + ((a :: Type) -> (r :: Type)) -> ConstR @(t5 :: Type) (r :: Type) (a :: Type) (b :: (t5 :: Type)) +GoLeft :: forall (a :: Type) (b :: Type). ((a :: Type) -> Int) -> Choice (a :: Type) (b :: Type) +GoRight :: forall (a :: Type) (b :: Type). (b :: Type) -> Choice (a :: Type) (b :: Type) + +Types +Fn :: Type -> Type -> Type +ConstR :: forall (t5 :: Type). Type -> Type -> (t5 :: Type) -> Type +Choice :: Type -> Type -> Type + +Roles +Fn = [Representational, Representational] +ConstR = [Representational, Representational, Phantom] +Choice = [Representational, Representational] diff --git a/tests-integration/fixtures/checking2/099_derive_profunctor_error/Main.purs b/tests-integration/fixtures/checking2/099_derive_profunctor_error/Main.purs new file mode 100644 index 00000000..d4aa9394 --- /dev/null +++ b/tests-integration/fixtures/checking2/099_derive_profunctor_error/Main.purs @@ -0,0 +1,9 @@ +module Main where + +import Data.Profunctor (class Profunctor) + +data WrongFirst a b = WrongFirst a b +derive instance Profunctor WrongFirst + +data WrongSecond a b = WrongSecond (b -> a) +derive instance Profunctor WrongSecond diff --git a/tests-integration/fixtures/checking2/099_derive_profunctor_error/Main.snap b/tests-integration/fixtures/checking2/099_derive_profunctor_error/Main.snap new file mode 100644 index 00000000..c8ff77b3 --- /dev/null +++ b/tests-integration/fixtures/checking2/099_derive_profunctor_error/Main.snap @@ -0,0 +1,51 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +WrongFirst :: + forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> WrongFirst (a :: Type) (b :: Type) +WrongSecond :: + forall (a :: Type) (b :: Type). + ((b :: Type) -> (a :: Type)) -> WrongSecond (a :: Type) (b :: Type) + +Types +WrongFirst :: Type -> Type -> Type +WrongSecond :: Type -> Type -> Type + +Roles +WrongFirst = [Representational, Representational] +WrongSecond = [Representational, Representational] + +Errors +CheckError { + kind: CovariantOccurrence { + type_message: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: ContravariantOccurrence { + type_message: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} +CheckError { + kind: CovariantOccurrence { + type_message: Id(10), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/100_derive_foldable_simple/Main.purs b/tests-integration/fixtures/checking2/100_derive_foldable_simple/Main.purs new file mode 100644 index 00000000..3b377612 --- /dev/null +++ b/tests-integration/fixtures/checking2/100_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/checking2/100_derive_foldable_simple/Main.snap b/tests-integration/fixtures/checking2/100_derive_foldable_simple/Main.snap new file mode 100644 index 00000000..873cf2b9 --- /dev/null +++ b/tests-integration/fixtures/checking2/100_derive_foldable_simple/Main.snap @@ -0,0 +1,22 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Identity :: forall (a :: Type). (a :: Type) -> Identity (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Const :: + forall (t4 :: Type) (e :: Type) (a :: (t4 :: Type)). + (e :: Type) -> Const @(t4 :: Type) (e :: Type) (a :: (t4 :: Type)) + +Types +Identity :: Type -> Type +Maybe :: Type -> Type +Const :: forall (t4 :: Type). Type -> (t4 :: Type) -> Type + +Roles +Identity = [Representational] +Maybe = [Representational] +Const = [Representational, Phantom] diff --git a/tests-integration/fixtures/checking2/101_derive_foldable_higher_kinded/Main.purs b/tests-integration/fixtures/checking2/101_derive_foldable_higher_kinded/Main.purs new file mode 100644 index 00000000..259227a9 --- /dev/null +++ b/tests-integration/fixtures/checking2/101_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/checking2/101_derive_foldable_higher_kinded/Main.snap b/tests-integration/fixtures/checking2/101_derive_foldable_higher_kinded/Main.snap new file mode 100644 index 00000000..fe316ede --- /dev/null +++ b/tests-integration/fixtures/checking2/101_derive_foldable_higher_kinded/Main.snap @@ -0,0 +1,44 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Wrap :: + forall (t2 :: Type) (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)). + (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) -> + Wrap @(t2 :: Type) (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) +WrapNoFoldable :: + forall (t5 :: Type) (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)). + (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)) -> + WrapNoFoldable @(t5 :: Type) (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)) + +Types +Wrap :: forall (t2 :: Type). ((t2 :: Type) -> Type) -> (t2 :: Type) -> Type +WrapNoFoldable :: forall (t5 :: Type). ((t5 :: Type) -> Type) -> (t5 :: Type) -> Type + +Roles +Wrap = [Representational, Nominal] +WrapNoFoldable = [Representational, Nominal] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(6), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/102_derive_bifoldable_simple/Main.purs b/tests-integration/fixtures/checking2/102_derive_bifoldable_simple/Main.purs new file mode 100644 index 00000000..f7557039 --- /dev/null +++ b/tests-integration/fixtures/checking2/102_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/checking2/102_derive_bifoldable_simple/Main.snap b/tests-integration/fixtures/checking2/102_derive_bifoldable_simple/Main.snap new file mode 100644 index 00000000..0f50c456 --- /dev/null +++ b/tests-integration/fixtures/checking2/102_derive_bifoldable_simple/Main.snap @@ -0,0 +1,23 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Left :: forall (a :: Type) (b :: Type). (a :: Type) -> Either (a :: Type) (b :: Type) +Right :: forall (a :: Type) (b :: Type). (b :: Type) -> Either (a :: Type) (b :: Type) +Pair :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> Pair (a :: Type) (b :: Type) +Const2 :: + forall (t8 :: Type) (t7 :: Type) (e :: Type) (a :: (t8 :: Type)) (b :: (t7 :: Type)). + (e :: Type) -> + Const2 @(t8 :: Type) @(t7 :: Type) (e :: Type) (a :: (t8 :: Type)) (b :: (t7 :: Type)) + +Types +Either :: Type -> Type -> Type +Pair :: Type -> Type -> Type +Const2 :: forall (t8 :: Type) (t7 :: Type). Type -> (t8 :: Type) -> (t7 :: Type) -> Type + +Roles +Either = [Representational, Representational] +Pair = [Representational, Representational] +Const2 = [Representational, Phantom, Phantom] diff --git a/tests-integration/fixtures/checking2/103_derive_bifoldable_higher_kinded/Main.purs b/tests-integration/fixtures/checking2/103_derive_bifoldable_higher_kinded/Main.purs new file mode 100644 index 00000000..d6f1526a --- /dev/null +++ b/tests-integration/fixtures/checking2/103_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/checking2/103_derive_bifoldable_higher_kinded/Main.snap b/tests-integration/fixtures/checking2/103_derive_bifoldable_higher_kinded/Main.snap new file mode 100644 index 00000000..d8c38686 --- /dev/null +++ b/tests-integration/fixtures/checking2/103_derive_bifoldable_higher_kinded/Main.snap @@ -0,0 +1,80 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +WrapBoth :: + forall (t5 :: Type) (t4 :: Type) (f :: (t5 :: Type) -> Type) (g :: (t4 :: Type) -> Type) + (a :: (t5 :: Type)) (b :: (t4 :: Type)). + (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)) -> + (g :: (t4 :: Type) -> Type) (b :: (t4 :: Type)) -> + WrapBoth @(t5 :: Type) @(t4 :: Type) + (f :: (t5 :: Type) -> Type) + (g :: (t4 :: Type) -> Type) + (a :: (t5 :: Type)) + (b :: (t4 :: Type)) +WrapBothNoConstraint :: + forall (t11 :: Type) (t10 :: Type) (f :: (t11 :: Type) -> Type) (g :: (t10 :: Type) -> Type) + (a :: (t11 :: Type)) (b :: (t10 :: Type)). + (f :: (t11 :: Type) -> Type) (a :: (t11 :: Type)) -> + (g :: (t10 :: Type) -> Type) (b :: (t10 :: Type)) -> + WrapBothNoConstraint @(t11 :: Type) @(t10 :: Type) + (f :: (t11 :: Type) -> Type) + (g :: (t10 :: Type) -> Type) + (a :: (t11 :: Type)) + (b :: (t10 :: Type)) + +Types +WrapBoth :: + forall (t5 :: Type) (t4 :: Type). + ((t5 :: Type) -> Type) -> ((t4 :: Type) -> Type) -> (t5 :: Type) -> (t4 :: Type) -> Type +WrapBothNoConstraint :: + forall (t11 :: Type) (t10 :: Type). + ((t11 :: Type) -> Type) -> ((t10 :: Type) -> Type) -> (t11 :: Type) -> (t10 :: Type) -> Type + +Roles +WrapBoth = [Representational, Representational, Nominal, Nominal] +WrapBothNoConstraint = [Representational, Representational, Nominal, Nominal] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(10), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(11), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(12), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/104_derive_traversable_simple/Main.purs b/tests-integration/fixtures/checking2/104_derive_traversable_simple/Main.purs new file mode 100644 index 00000000..a374bd51 --- /dev/null +++ b/tests-integration/fixtures/checking2/104_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/checking2/104_derive_traversable_simple/Main.snap b/tests-integration/fixtures/checking2/104_derive_traversable_simple/Main.snap new file mode 100644 index 00000000..873cf2b9 --- /dev/null +++ b/tests-integration/fixtures/checking2/104_derive_traversable_simple/Main.snap @@ -0,0 +1,22 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Identity :: forall (a :: Type). (a :: Type) -> Identity (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Const :: + forall (t4 :: Type) (e :: Type) (a :: (t4 :: Type)). + (e :: Type) -> Const @(t4 :: Type) (e :: Type) (a :: (t4 :: Type)) + +Types +Identity :: Type -> Type +Maybe :: Type -> Type +Const :: forall (t4 :: Type). Type -> (t4 :: Type) -> Type + +Roles +Identity = [Representational] +Maybe = [Representational] +Const = [Representational, Phantom] diff --git a/tests-integration/fixtures/checking2/105_derive_traversable_higher_kinded/Main.purs b/tests-integration/fixtures/checking2/105_derive_traversable_higher_kinded/Main.purs new file mode 100644 index 00000000..d2402adf --- /dev/null +++ b/tests-integration/fixtures/checking2/105_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/checking2/105_derive_traversable_higher_kinded/Main.snap b/tests-integration/fixtures/checking2/105_derive_traversable_higher_kinded/Main.snap new file mode 100644 index 00000000..432b1645 --- /dev/null +++ b/tests-integration/fixtures/checking2/105_derive_traversable_higher_kinded/Main.snap @@ -0,0 +1,84 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Compose :: + forall (t4 :: Type) (t3 :: Type) (f :: (t4 :: Type) -> Type) (g :: (t3 :: Type) -> (t4 :: Type)) + (a :: (t3 :: Type)). + (f :: (t4 :: Type) -> Type) ((g :: (t3 :: Type) -> (t4 :: Type)) (a :: (t3 :: Type))) -> + Compose @(t4 :: Type) @(t3 :: Type) + (f :: (t4 :: Type) -> Type) + (g :: (t3 :: Type) -> (t4 :: Type)) + (a :: (t3 :: Type)) + +Types +Compose :: + forall (t4 :: Type) (t3 :: Type). + ((t4 :: Type) -> Type) -> ((t3 :: Type) -> (t4 :: Type)) -> (t3 :: Type) -> Type + +Roles +Compose = [Representational, Representational, Nominal] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(10), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(11), + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(12), + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(13), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(14), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/106_derive_traversable_missing_superclass/Main.purs b/tests-integration/fixtures/checking2/106_derive_traversable_missing_superclass/Main.purs new file mode 100644 index 00000000..cc6bb64b --- /dev/null +++ b/tests-integration/fixtures/checking2/106_derive_traversable_missing_superclass/Main.purs @@ -0,0 +1,6 @@ +module Main where + +import Data.Traversable (class Traversable) + +data Compose f g a = Compose (f (g a)) +derive instance (Traversable f, Traversable g) => Traversable (Compose f g) diff --git a/tests-integration/fixtures/checking2/106_derive_traversable_missing_superclass/Main.snap b/tests-integration/fixtures/checking2/106_derive_traversable_missing_superclass/Main.snap new file mode 100644 index 00000000..bbbab64e --- /dev/null +++ b/tests-integration/fixtures/checking2/106_derive_traversable_missing_superclass/Main.snap @@ -0,0 +1,64 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Compose :: + forall (t4 :: Type) (t3 :: Type) (f :: (t4 :: Type) -> Type) (g :: (t3 :: Type) -> (t4 :: Type)) + (a :: (t3 :: Type)). + (f :: (t4 :: Type) -> Type) ((g :: (t3 :: Type) -> (t4 :: Type)) (a :: (t3 :: Type))) -> + Compose @(t4 :: Type) @(t3 :: Type) + (f :: (t4 :: Type) -> Type) + (g :: (t3 :: Type) -> (t4 :: Type)) + (a :: (t3 :: Type)) + +Types +Compose :: + forall (t4 :: Type) (t3 :: Type). + ((t4 :: Type) -> Type) -> ((t3 :: Type) -> (t4 :: Type)) -> (t3 :: Type) -> Type + +Roles +Compose = [Representational, Representational, Nominal] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(10), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(11), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(12), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/107_derive_bitraversable_simple/Main.purs b/tests-integration/fixtures/checking2/107_derive_bitraversable_simple/Main.purs new file mode 100644 index 00000000..9f0dc4ba --- /dev/null +++ b/tests-integration/fixtures/checking2/107_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/checking2/107_derive_bitraversable_simple/Main.snap b/tests-integration/fixtures/checking2/107_derive_bitraversable_simple/Main.snap new file mode 100644 index 00000000..0115f6f9 --- /dev/null +++ b/tests-integration/fixtures/checking2/107_derive_bitraversable_simple/Main.snap @@ -0,0 +1,17 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Left :: forall (a :: Type) (b :: Type). (a :: Type) -> Either (a :: Type) (b :: Type) +Right :: forall (a :: Type) (b :: Type). (b :: Type) -> Either (a :: Type) (b :: Type) +Pair :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> Pair (a :: Type) (b :: Type) + +Types +Either :: Type -> Type -> Type +Pair :: Type -> Type -> Type + +Roles +Either = [Representational, Representational] +Pair = [Representational, Representational] diff --git a/tests-integration/fixtures/checking2/108_derive_bitraversable_higher_kinded/Main.purs b/tests-integration/fixtures/checking2/108_derive_bitraversable_higher_kinded/Main.purs new file mode 100644 index 00000000..00777010 --- /dev/null +++ b/tests-integration/fixtures/checking2/108_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/checking2/108_derive_bitraversable_higher_kinded/Main.snap b/tests-integration/fixtures/checking2/108_derive_bitraversable_higher_kinded/Main.snap new file mode 100644 index 00000000..a7eb3475 --- /dev/null +++ b/tests-integration/fixtures/checking2/108_derive_bitraversable_higher_kinded/Main.snap @@ -0,0 +1,86 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +WrapBoth :: + forall (t5 :: Type) (t4 :: Type) (f :: (t5 :: Type) -> Type) (g :: (t4 :: Type) -> Type) + (a :: (t5 :: Type)) (b :: (t4 :: Type)). + (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)) -> + (g :: (t4 :: Type) -> Type) (b :: (t4 :: Type)) -> + WrapBoth @(t5 :: Type) @(t4 :: Type) + (f :: (t5 :: Type) -> Type) + (g :: (t4 :: Type) -> Type) + (a :: (t5 :: Type)) + (b :: (t4 :: Type)) + +Types +WrapBoth :: + forall (t5 :: Type) (t4 :: Type). + ((t5 :: Type) -> Type) -> ((t4 :: Type) -> Type) -> (t5 :: Type) -> (t4 :: Type) -> Type + +Roles +WrapBoth = [Representational, Representational, Nominal, Nominal] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(12), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(13), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(14), + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(15), + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(16), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(17), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/109_derive_newtype_simple/Main.purs b/tests-integration/fixtures/checking2/109_derive_newtype_simple/Main.purs new file mode 100644 index 00000000..87027037 --- /dev/null +++ b/tests-integration/fixtures/checking2/109_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/checking2/109_derive_newtype_simple/Main.snap b/tests-integration/fixtures/checking2/109_derive_newtype_simple/Main.snap new file mode 100644 index 00000000..309c4718 --- /dev/null +++ b/tests-integration/fixtures/checking2/109_derive_newtype_simple/Main.snap @@ -0,0 +1,13 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Wrapper :: Int -> Wrapper + +Types +Wrapper :: Type + +Roles +Wrapper = [] diff --git a/tests-integration/fixtures/checking2/110_derive_newtype_parameterized/Main.purs b/tests-integration/fixtures/checking2/110_derive_newtype_parameterized/Main.purs new file mode 100644 index 00000000..439202ce --- /dev/null +++ b/tests-integration/fixtures/checking2/110_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/checking2/110_derive_newtype_parameterized/Main.snap b/tests-integration/fixtures/checking2/110_derive_newtype_parameterized/Main.snap new file mode 100644 index 00000000..e66717ce --- /dev/null +++ b/tests-integration/fixtures/checking2/110_derive_newtype_parameterized/Main.snap @@ -0,0 +1,13 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Identity :: forall (a :: Type). (a :: Type) -> Identity (a :: Type) + +Types +Identity :: Type -> Type + +Roles +Identity = [Representational] diff --git a/tests-integration/fixtures/checking2/111_derive_newtype_not_newtype/Main.purs b/tests-integration/fixtures/checking2/111_derive_newtype_not_newtype/Main.purs new file mode 100644 index 00000000..8092e314 --- /dev/null +++ b/tests-integration/fixtures/checking2/111_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/checking2/111_derive_newtype_not_newtype/Main.snap b/tests-integration/fixtures/checking2/111_derive_newtype_not_newtype/Main.snap new file mode 100644 index 00000000..fd5c52eb --- /dev/null +++ b/tests-integration/fixtures/checking2/111_derive_newtype_not_newtype/Main.snap @@ -0,0 +1,25 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Foo :: Int -> Foo + +Types +Foo :: Type + +Roles +Foo = [] + +Errors +CheckError { + kind: ExpectedNewtype { + type_message: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/112_derive_newtype_class_simple/Main.purs b/tests-integration/fixtures/checking2/112_derive_newtype_class_simple/Main.purs new file mode 100644 index 00000000..b48f3b16 --- /dev/null +++ b/tests-integration/fixtures/checking2/112_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/checking2/112_derive_newtype_class_simple/Main.snap b/tests-integration/fixtures/checking2/112_derive_newtype_class_simple/Main.snap new file mode 100644 index 00000000..cd2243b9 --- /dev/null +++ b/tests-integration/fixtures/checking2/112_derive_newtype_class_simple/Main.snap @@ -0,0 +1,13 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +UserId :: Int -> UserId + +Types +UserId :: Type + +Roles +UserId = [] diff --git a/tests-integration/fixtures/checking2/113_derive_newtype_class_parameterized/Main.purs b/tests-integration/fixtures/checking2/113_derive_newtype_class_parameterized/Main.purs new file mode 100644 index 00000000..c6284bd3 --- /dev/null +++ b/tests-integration/fixtures/checking2/113_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/checking2/113_derive_newtype_class_parameterized/Main.snap b/tests-integration/fixtures/checking2/113_derive_newtype_class_parameterized/Main.snap new file mode 100644 index 00000000..e9dfac50 --- /dev/null +++ b/tests-integration/fixtures/checking2/113_derive_newtype_class_parameterized/Main.snap @@ -0,0 +1,13 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Wrapper :: forall (a :: Type). (a :: Type) -> Wrapper (a :: Type) + +Types +Wrapper :: Type -> Type + +Roles +Wrapper = [Representational] diff --git a/tests-integration/fixtures/checking2/114_derive_newtype_class_not_newtype/Main.purs b/tests-integration/fixtures/checking2/114_derive_newtype_class_not_newtype/Main.purs new file mode 100644 index 00000000..18a518ba --- /dev/null +++ b/tests-integration/fixtures/checking2/114_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/checking2/114_derive_newtype_class_not_newtype/Main.snap b/tests-integration/fixtures/checking2/114_derive_newtype_class_not_newtype/Main.snap new file mode 100644 index 00000000..204ad3d5 --- /dev/null +++ b/tests-integration/fixtures/checking2/114_derive_newtype_class_not_newtype/Main.snap @@ -0,0 +1,25 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +NotANewtype :: Int -> NotANewtype + +Types +NotANewtype :: Type + +Roles +NotANewtype = [] + +Errors +CheckError { + kind: ExpectedNewtype { + type_message: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/115_derive_generic_simple/Main.purs b/tests-integration/fixtures/checking2/115_derive_generic_simple/Main.purs new file mode 100644 index 00000000..46f1851b --- /dev/null +++ b/tests-integration/fixtures/checking2/115_derive_generic_simple/Main.purs @@ -0,0 +1,44 @@ +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) _ + +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/checking2/115_derive_generic_simple/Main.snap b/tests-integration/fixtures/checking2/115_derive_generic_simple/Main.snap new file mode 100644 index 00000000..8d090366 --- /dev/null +++ b/tests-integration/fixtures/checking2/115_derive_generic_simple/Main.snap @@ -0,0 +1,60 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +MyUnit :: MyUnit +Identity :: forall (a :: Type). (a :: Type) -> Identity (a :: Type) +Left :: forall (a :: Type) (b :: Type). (a :: Type) -> Either (a :: Type) (b :: Type) +Right :: forall (a :: Type) (b :: Type). (b :: Type) -> Either (a :: Type) (b :: Type) +Tuple :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> Tuple (a :: Type) (b :: Type) +Wrapper :: forall (a :: Type). (a :: Type) -> Wrapper (a :: Type) +Proxy :: forall (t7 :: Type) (a :: (t7 :: Type)). Proxy @(t7 :: Type) (a :: (t7 :: Type)) +getVoid :: forall (rep :: Type). Generic Void (rep :: Type) => Proxy @Type (rep :: Type) +getMyUnit :: forall (rep :: Type). Generic MyUnit (rep :: Type) => Proxy @Type (rep :: Type) +getIdentity :: + forall (a :: Type) (rep :: Type). + Generic (Identity (a :: Type)) (rep :: Type) => Proxy @Type (rep :: Type) +getEither :: + forall (a :: Type) (b :: Type) (rep :: Type). + Generic (Either (a :: Type) (b :: Type)) (rep :: Type) => Proxy @Type (rep :: Type) +getTuple :: + forall (a :: Type) (b :: Type) (rep :: Type). + Generic (Tuple (a :: Type) (b :: Type)) (rep :: Type) => Proxy @Type (rep :: Type) +getWrapper :: + forall (a :: Type) (rep :: Type). + Generic (Wrapper (a :: Type)) (rep :: Type) => Proxy @Type (rep :: Type) +forceSolve :: + forall (t31 :: Type) (t30 :: Type) (t29 :: Type) (t28 :: Type) (t27 :: Type) (t26 :: Type). + { getEither :: + Proxy @Type + (Sum + (Constructor "Left" (Argument (t31 :: Type))) + (Constructor "Right" (Argument (t30 :: Type)))) + , getIdentity :: Proxy @Type (Constructor "Identity" (Argument (t29 :: Type))) + , getMyUnit :: Proxy @Type (Constructor "MyUnit" NoArguments) + , getTuple :: + Proxy @Type + (Constructor "Tuple" (Product (Argument (t28 :: Type)) (Argument (t27 :: Type)))) + , getVoid :: Proxy @Type NoConstructors + , getWrapper :: Proxy @Type (Constructor "Wrapper" (Argument (t26 :: Type))) + } + +Types +Void :: Type +MyUnit :: Type +Identity :: Type -> Type +Either :: Type -> Type -> Type +Tuple :: Type -> Type -> Type +Wrapper :: Type -> Type +Proxy :: forall (t7 :: Type). (t7 :: Type) -> Type + +Roles +Void = [] +MyUnit = [] +Identity = [Representational] +Either = [Representational, Representational] +Tuple = [Representational, Representational] +Wrapper = [Representational] +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking2/116_derive_eq_mutual_visibility_same_module/Main.purs b/tests-integration/fixtures/checking2/116_derive_eq_mutual_visibility_same_module/Main.purs new file mode 100644 index 00000000..efdcb3d3 --- /dev/null +++ b/tests-integration/fixtures/checking2/116_derive_eq_mutual_visibility_same_module/Main.purs @@ -0,0 +1,10 @@ +module Main where + +import Data.Eq (class Eq) + +data DurationComponent = Hours | Minutes | Seconds + +data Duration = Duration DurationComponent Int + +derive instance Eq Duration +derive instance Eq DurationComponent diff --git a/tests-integration/fixtures/checking2/116_derive_eq_mutual_visibility_same_module/Main.snap b/tests-integration/fixtures/checking2/116_derive_eq_mutual_visibility_same_module/Main.snap new file mode 100644 index 00000000..d6860998 --- /dev/null +++ b/tests-integration/fixtures/checking2/116_derive_eq_mutual_visibility_same_module/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Hours :: DurationComponent +Minutes :: DurationComponent +Seconds :: DurationComponent +Duration :: DurationComponent -> Int -> Duration + +Types +DurationComponent :: Type +Duration :: Type + +Roles +DurationComponent = [] +Duration = [] diff --git a/tests-integration/fixtures/checking2/117_derive_newtype_class_coercible/Main.purs b/tests-integration/fixtures/checking2/117_derive_newtype_class_coercible/Main.purs new file mode 100644 index 00000000..64588234 --- /dev/null +++ b/tests-integration/fixtures/checking2/117_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/checking2/117_derive_newtype_class_coercible/Main.snap b/tests-integration/fixtures/checking2/117_derive_newtype_class_coercible/Main.snap new file mode 100644 index 00000000..93136293 --- /dev/null +++ b/tests-integration/fixtures/checking2/117_derive_newtype_class_coercible/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +UserId :: Int -> UserId +wrapUserId :: Int -> UserId +unwrapUserId :: UserId -> Int +Wrapper :: forall (a :: Type). (a :: Type) -> Wrapper (a :: Type) +wrapWrapper :: forall (a :: Type). (a :: Type) -> Wrapper (a :: Type) +unwrapWrapper :: forall (a :: Type). Wrapper (a :: Type) -> (a :: Type) + +Types +UserId :: Type +Wrapper :: Type -> Type + +Roles +UserId = [] +Wrapper = [Representational] diff --git a/tests-integration/fixtures/checking2/118_derive_newtype_with_given/Main.purs b/tests-integration/fixtures/checking2/118_derive_newtype_with_given/Main.purs new file mode 100644 index 00000000..45a2f67b --- /dev/null +++ b/tests-integration/fixtures/checking2/118_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/checking2/118_derive_newtype_with_given/Main.snap b/tests-integration/fixtures/checking2/118_derive_newtype_with_given/Main.snap new file mode 100644 index 00000000..e66717ce --- /dev/null +++ b/tests-integration/fixtures/checking2/118_derive_newtype_with_given/Main.snap @@ -0,0 +1,13 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Identity :: forall (a :: Type). (a :: Type) -> Identity (a :: Type) + +Types +Identity :: Type -> Type + +Roles +Identity = [Representational] diff --git a/tests-integration/fixtures/checking2/119_derive_newtype_missing_instance/Main.purs b/tests-integration/fixtures/checking2/119_derive_newtype_missing_instance/Main.purs new file mode 100644 index 00000000..a28cb1bd --- /dev/null +++ b/tests-integration/fixtures/checking2/119_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/checking2/119_derive_newtype_missing_instance/Main.snap b/tests-integration/fixtures/checking2/119_derive_newtype_missing_instance/Main.snap new file mode 100644 index 00000000..6714c045 --- /dev/null +++ b/tests-integration/fixtures/checking2/119_derive_newtype_missing_instance/Main.snap @@ -0,0 +1,25 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Identity :: forall (a :: Type). (a :: Type) -> Identity (a :: Type) + +Types +Identity :: Type -> Type + +Roles +Identity = [Representational] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/120_derive_newtype_missing_given/Main.purs b/tests-integration/fixtures/checking2/120_derive_newtype_missing_given/Main.purs new file mode 100644 index 00000000..f99f1ba3 --- /dev/null +++ b/tests-integration/fixtures/checking2/120_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/checking2/120_derive_newtype_missing_given/Main.snap b/tests-integration/fixtures/checking2/120_derive_newtype_missing_given/Main.snap new file mode 100644 index 00000000..6714c045 --- /dev/null +++ b/tests-integration/fixtures/checking2/120_derive_newtype_missing_given/Main.snap @@ -0,0 +1,25 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Identity :: forall (a :: Type). (a :: Type) -> Identity (a :: Type) + +Types +Identity :: Type -> Type + +Roles +Identity = [Representational] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/121_derive_newtype_multi_param/Main.purs b/tests-integration/fixtures/checking2/121_derive_newtype_multi_param/Main.purs new file mode 100644 index 00000000..14b8f605 --- /dev/null +++ b/tests-integration/fixtures/checking2/121_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/checking2/121_derive_newtype_multi_param/Main.snap b/tests-integration/fixtures/checking2/121_derive_newtype_multi_param/Main.snap new file mode 100644 index 00000000..e9930127 --- /dev/null +++ b/tests-integration/fixtures/checking2/121_derive_newtype_multi_param/Main.snap @@ -0,0 +1,15 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Pair :: + forall (t2 :: Type) (a :: Type) (b :: (t2 :: Type)). + (a :: Type) -> Pair @(t2 :: Type) (a :: Type) (b :: (t2 :: Type)) + +Types +Pair :: forall (t2 :: Type). Type -> (t2 :: Type) -> Type + +Roles +Pair = [Representational, Phantom] diff --git a/tests-integration/fixtures/checking2/122_derive_newtype_higher_kinded/Main.purs b/tests-integration/fixtures/checking2/122_derive_newtype_higher_kinded/Main.purs new file mode 100644 index 00000000..c39d2fd4 --- /dev/null +++ b/tests-integration/fixtures/checking2/122_derive_newtype_higher_kinded/Main.purs @@ -0,0 +1,11 @@ +module Main where + +class Empty f where + empty :: f Int + +instance Empty Array where + empty = [] + +newtype Wrapper a = Wrapper (Array a) + +derive newtype instance Empty Wrapper diff --git a/tests-integration/fixtures/checking2/122_derive_newtype_higher_kinded/Main.snap b/tests-integration/fixtures/checking2/122_derive_newtype_higher_kinded/Main.snap new file mode 100644 index 00000000..84ea88ca --- /dev/null +++ b/tests-integration/fixtures/checking2/122_derive_newtype_higher_kinded/Main.snap @@ -0,0 +1,22 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +empty :: forall (f :: Type -> Type). Empty (f :: Type -> Type) => (f :: Type -> Type) Int +Wrapper :: forall (a :: Type). Array (a :: Type) -> Wrapper (a :: Type) + +Types +Empty :: (Type -> Type) -> Constraint +Wrapper :: Type -> Type + +Classes +class forall (f :: Type -> Type). Empty (f :: Type -> Type) + empty :: forall (f :: Type -> Type). Empty (f :: Type -> Type) => (f :: Type -> Type) Int + +Instances +instance Empty Array + +Roles +Wrapper = [Representational] diff --git a/tests-integration/fixtures/checking2/123_derive_newtype_function/Main.purs b/tests-integration/fixtures/checking2/123_derive_newtype_function/Main.purs new file mode 100644 index 00000000..d6a955a8 --- /dev/null +++ b/tests-integration/fixtures/checking2/123_derive_newtype_function/Main.purs @@ -0,0 +1,9 @@ +module Main where + +import Control.Category (class Category) +import Data.Semigroupoid (class Semigroupoid) + +newtype Builder a b = Builder (a -> b) + +derive newtype instance Semigroupoid Builder +derive newtype instance Category Builder diff --git a/tests-integration/fixtures/checking2/123_derive_newtype_function/Main.snap b/tests-integration/fixtures/checking2/123_derive_newtype_function/Main.snap new file mode 100644 index 00000000..00641ef8 --- /dev/null +++ b/tests-integration/fixtures/checking2/123_derive_newtype_function/Main.snap @@ -0,0 +1,14 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Builder :: + forall (a :: Type) (b :: Type). ((a :: Type) -> (b :: Type)) -> Builder (a :: Type) (b :: Type) + +Types +Builder :: Type -> Type -> Type + +Roles +Builder = [Representational, Representational] diff --git a/tests-integration/fixtures/checking2/124_derive_newtype_synonym_inner/Main.purs b/tests-integration/fixtures/checking2/124_derive_newtype_synonym_inner/Main.purs new file mode 100644 index 00000000..5fc20cd0 --- /dev/null +++ b/tests-integration/fixtures/checking2/124_derive_newtype_synonym_inner/Main.purs @@ -0,0 +1,12 @@ +module Main where + +import Data.Eq (class Eq) + +foreign import data State :: Type + +instance Eq State where + eq _ _ = true + +newtype Test = Test State + +derive newtype instance Eq Test diff --git a/tests-integration/fixtures/checking2/124_derive_newtype_synonym_inner/Main.snap b/tests-integration/fixtures/checking2/124_derive_newtype_synonym_inner/Main.snap new file mode 100644 index 00000000..1e0007ad --- /dev/null +++ b/tests-integration/fixtures/checking2/124_derive_newtype_synonym_inner/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Test :: State -> Test + +Types +State :: Type +Test :: Type + +Instances +instance Eq State + +Roles +State = [] +Test = [] diff --git a/tests-integration/fixtures/checking2/125_derive_eq_1_higher_kinded/Main.purs b/tests-integration/fixtures/checking2/125_derive_eq_1_higher_kinded/Main.purs new file mode 100644 index 00000000..2a449a71 --- /dev/null +++ b/tests-integration/fixtures/checking2/125_derive_eq_1_higher_kinded/Main.purs @@ -0,0 +1,11 @@ +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) + +data WrapNoEq1 f a = MkWrapNoEq1 (f a) + +derive instance Eq a => Eq (WrapNoEq1 f a) diff --git a/tests-integration/fixtures/checking2/125_derive_eq_1_higher_kinded/Main.snap b/tests-integration/fixtures/checking2/125_derive_eq_1_higher_kinded/Main.snap new file mode 100644 index 00000000..21f9745f --- /dev/null +++ b/tests-integration/fixtures/checking2/125_derive_eq_1_higher_kinded/Main.snap @@ -0,0 +1,44 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +MkWrap :: + forall (t2 :: Type) (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)). + (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) -> + Wrap @(t2 :: Type) (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) +MkWrapNoEq1 :: + forall (t5 :: Type) (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)). + (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)) -> + WrapNoEq1 @(t5 :: Type) (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)) + +Types +Wrap :: forall (t2 :: Type). ((t2 :: Type) -> Type) -> (t2 :: Type) -> Type +WrapNoEq1 :: forall (t5 :: Type). ((t5 :: Type) -> Type) -> (t5 :: Type) -> Type + +Roles +Wrap = [Representational, Nominal] +WrapNoEq1 = [Representational, Nominal] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/126_derive_eq_partial/Main.purs b/tests-integration/fixtures/checking2/126_derive_eq_partial/Main.purs new file mode 100644 index 00000000..e08f6f51 --- /dev/null +++ b/tests-integration/fixtures/checking2/126_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/checking2/126_derive_eq_partial/Main.snap b/tests-integration/fixtures/checking2/126_derive_eq_partial/Main.snap new file mode 100644 index 00000000..eeed192b --- /dev/null +++ b/tests-integration/fixtures/checking2/126_derive_eq_partial/Main.snap @@ -0,0 +1,26 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Left :: forall (a :: Type) (b :: Type). (a :: Type) -> Either (a :: Type) (b :: Type) +Right :: forall (a :: Type) (b :: Type). (b :: Type) -> Either (a :: Type) (b :: Type) + +Types +Either :: Type -> Type -> Type + +Roles +Either = [Representational, Representational] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/127_derive_eq_ord_nested_higher_kinded/Main.purs b/tests-integration/fixtures/checking2/127_derive_eq_ord_nested_higher_kinded/Main.purs new file mode 100644 index 00000000..39e57b34 --- /dev/null +++ b/tests-integration/fixtures/checking2/127_derive_eq_ord_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/checking2/127_derive_eq_ord_nested_higher_kinded/Main.snap b/tests-integration/fixtures/checking2/127_derive_eq_ord_nested_higher_kinded/Main.snap new file mode 100644 index 00000000..389d368c --- /dev/null +++ b/tests-integration/fixtures/checking2/127_derive_eq_ord_nested_higher_kinded/Main.snap @@ -0,0 +1,91 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +T :: + forall (t2 :: Type) (f :: (t2 :: Type) -> Type) (g :: Type -> (t2 :: Type)). + (f :: (t2 :: Type) -> Type) ((g :: Type -> (t2 :: Type)) Int) -> + T @(t2 :: Type) (f :: (t2 :: Type) -> Type) (g :: Type -> (t2 :: Type)) +Nothing :: forall (a :: Type). Maybe (a :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +U :: forall (f :: Type -> Type). (f :: Type -> Type) (Maybe Int) -> U (f :: Type -> Type) +V :: + forall (t7 :: Type) (f :: (t7 :: Type) -> Type) (g :: Int -> (t7 :: Type)). + (f :: (t7 :: Type) -> Type) ((g :: Int -> (t7 :: Type)) 42) -> + V @(t7 :: Type) (f :: (t7 :: Type) -> Type) (g :: Int -> (t7 :: Type)) + +Types +T :: forall (t2 :: Type). ((t2 :: Type) -> Type) -> (Type -> (t2 :: Type)) -> Type +Maybe :: Type -> Type +U :: (Type -> Type) -> Type +V :: forall (t7 :: Type). ((t7 :: Type) -> Type) -> (Int -> (t7 :: Type)) -> Type + +Roles +T = [Representational, Representational] +Maybe = [Representational] +U = [Representational] +V = [Representational, Representational] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(10), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(11), + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(12), + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(13), + }, + crumbs: [ + TermDeclaration( + Idx::(11), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(14), + }, + crumbs: [ + TermDeclaration( + Idx::(12), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/128_derive_functor_higher_kinded/Main.purs b/tests-integration/fixtures/checking2/128_derive_functor_higher_kinded/Main.purs new file mode 100644 index 00000000..af0de0ee --- /dev/null +++ b/tests-integration/fixtures/checking2/128_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/checking2/128_derive_functor_higher_kinded/Main.snap b/tests-integration/fixtures/checking2/128_derive_functor_higher_kinded/Main.snap new file mode 100644 index 00000000..8acbc7c6 --- /dev/null +++ b/tests-integration/fixtures/checking2/128_derive_functor_higher_kinded/Main.snap @@ -0,0 +1,44 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Wrap :: + forall (t2 :: Type) (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)). + (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) -> + Wrap @(t2 :: Type) (f :: (t2 :: Type) -> Type) (a :: (t2 :: Type)) +WrapNoFunctor :: + forall (t5 :: Type) (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)). + (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)) -> + WrapNoFunctor @(t5 :: Type) (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)) + +Types +Wrap :: forall (t2 :: Type). ((t2 :: Type) -> Type) -> (t2 :: Type) -> Type +WrapNoFunctor :: forall (t5 :: Type). ((t5 :: Type) -> Type) -> (t5 :: Type) -> Type + +Roles +Wrap = [Representational, Nominal] +WrapNoFunctor = [Representational, Nominal] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(6), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/129_derive_bifunctor_higher_kinded/Main.purs b/tests-integration/fixtures/checking2/129_derive_bifunctor_higher_kinded/Main.purs new file mode 100644 index 00000000..e94b0c71 --- /dev/null +++ b/tests-integration/fixtures/checking2/129_derive_bifunctor_higher_kinded/Main.purs @@ -0,0 +1,10 @@ +module Main where + +import Data.Bifunctor (class Bifunctor) +import Data.Functor (class Functor) + +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/checking2/129_derive_bifunctor_higher_kinded/Main.snap b/tests-integration/fixtures/checking2/129_derive_bifunctor_higher_kinded/Main.snap new file mode 100644 index 00000000..d8c38686 --- /dev/null +++ b/tests-integration/fixtures/checking2/129_derive_bifunctor_higher_kinded/Main.snap @@ -0,0 +1,80 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +WrapBoth :: + forall (t5 :: Type) (t4 :: Type) (f :: (t5 :: Type) -> Type) (g :: (t4 :: Type) -> Type) + (a :: (t5 :: Type)) (b :: (t4 :: Type)). + (f :: (t5 :: Type) -> Type) (a :: (t5 :: Type)) -> + (g :: (t4 :: Type) -> Type) (b :: (t4 :: Type)) -> + WrapBoth @(t5 :: Type) @(t4 :: Type) + (f :: (t5 :: Type) -> Type) + (g :: (t4 :: Type) -> Type) + (a :: (t5 :: Type)) + (b :: (t4 :: Type)) +WrapBothNoConstraint :: + forall (t11 :: Type) (t10 :: Type) (f :: (t11 :: Type) -> Type) (g :: (t10 :: Type) -> Type) + (a :: (t11 :: Type)) (b :: (t10 :: Type)). + (f :: (t11 :: Type) -> Type) (a :: (t11 :: Type)) -> + (g :: (t10 :: Type) -> Type) (b :: (t10 :: Type)) -> + WrapBothNoConstraint @(t11 :: Type) @(t10 :: Type) + (f :: (t11 :: Type) -> Type) + (g :: (t10 :: Type) -> Type) + (a :: (t11 :: Type)) + (b :: (t10 :: Type)) + +Types +WrapBoth :: + forall (t5 :: Type) (t4 :: Type). + ((t5 :: Type) -> Type) -> ((t4 :: Type) -> Type) -> (t5 :: Type) -> (t4 :: Type) -> Type +WrapBothNoConstraint :: + forall (t11 :: Type) (t10 :: Type). + ((t11 :: Type) -> Type) -> ((t10 :: Type) -> Type) -> (t11 :: Type) -> (t10 :: Type) -> Type + +Roles +WrapBoth = [Representational, Representational, Nominal, Nominal] +WrapBothNoConstraint = [Representational, Representational, Nominal, Nominal] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(10), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(11), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(12), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/130_givens_retained/Main.purs b/tests-integration/fixtures/checking2/130_givens_retained/Main.purs new file mode 100644 index 00000000..43266042 --- /dev/null +++ b/tests-integration/fixtures/checking2/130_givens_retained/Main.purs @@ -0,0 +1,10 @@ +module Main where + +class Given a where + consume :: a -> a + +testGiven :: forall a. Given a => a -> a +testGiven a = consume a + where + b = consume a + c = consume a diff --git a/tests-integration/fixtures/checking2/130_givens_retained/Main.snap b/tests-integration/fixtures/checking2/130_givens_retained/Main.snap new file mode 100644 index 00000000..31c9d400 --- /dev/null +++ b/tests-integration/fixtures/checking2/130_givens_retained/Main.snap @@ -0,0 +1,15 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +consume :: forall (a :: Type). Given (a :: Type) => (a :: Type) -> (a :: Type) +testGiven :: forall (a :: Type). Given (a :: Type) => (a :: Type) -> (a :: Type) + +Types +Given :: Type -> Constraint + +Classes +class forall (a :: Type). Given (a :: Type) + consume :: forall (a :: Type). Given (a :: Type) => (a :: Type) -> (a :: Type) diff --git a/tests-integration/fixtures/checking2/131_givens_scoped/Main.purs b/tests-integration/fixtures/checking2/131_givens_scoped/Main.purs new file mode 100644 index 00000000..b14a9d7e --- /dev/null +++ b/tests-integration/fixtures/checking2/131_givens_scoped/Main.purs @@ -0,0 +1,13 @@ +module Main where + +class Given a where + consume :: a -> a + +testGiven :: forall a. Given a => a -> a +testGiven a = consume a + where + b :: Given Int => a + b = let consumeInt = consume 42 in a + + c :: Int + c = consume 42 diff --git a/tests-integration/fixtures/checking2/131_givens_scoped/Main.snap b/tests-integration/fixtures/checking2/131_givens_scoped/Main.snap new file mode 100644 index 00000000..308074be --- /dev/null +++ b/tests-integration/fixtures/checking2/131_givens_scoped/Main.snap @@ -0,0 +1,23 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +consume :: forall (a :: Type). Given (a :: Type) => (a :: Type) -> (a :: Type) +testGiven :: forall (a :: Type). Given (a :: Type) => (a :: Type) -> (a :: Type) + +Types +Given :: Type -> Constraint + +Classes +class forall (a :: Type). Given (a :: Type) + consume :: forall (a :: Type). Given (a :: Type) => (a :: Type) -> (a :: Type) + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(6), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/132_let_constraint_scoping/Main.purs b/tests-integration/fixtures/checking2/132_let_constraint_scoping/Main.purs new file mode 100644 index 00000000..8a627f95 --- /dev/null +++ b/tests-integration/fixtures/checking2/132_let_constraint_scoping/Main.purs @@ -0,0 +1,17 @@ +module Main where + +class MyClass a where + method :: a -> Int + +instance MyClass Int where + method _ = 42 + +test :: forall a. MyClass a => a -> Int +test x = + let + bar y = method y + + baz :: MyClass Int => Int + baz = method 42 + in + bar x diff --git a/tests-integration/fixtures/checking2/132_let_constraint_scoping/Main.snap b/tests-integration/fixtures/checking2/132_let_constraint_scoping/Main.snap new file mode 100644 index 00000000..84cc19e4 --- /dev/null +++ b/tests-integration/fixtures/checking2/132_let_constraint_scoping/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +method :: forall (a :: Type). MyClass (a :: Type) => (a :: Type) -> Int +test :: forall (a :: Type). MyClass (a :: Type) => (a :: Type) -> Int + +Types +MyClass :: Type -> Constraint + +Classes +class forall (a :: Type). MyClass (a :: Type) + method :: forall (a :: Type). MyClass (a :: Type) => (a :: Type) -> Int + +Instances +instance MyClass Int diff --git a/tests-integration/fixtures/checking2/133_row_open_union/Main.purs b/tests-integration/fixtures/checking2/133_row_open_union/Main.purs new file mode 100644 index 00000000..ca542ecc --- /dev/null +++ b/tests-integration/fixtures/checking2/133_row_open_union/Main.purs @@ -0,0 +1,20 @@ +module Main where + +import Prim.Row as Row + +data Proxy :: forall k. k -> Type +data Proxy a = Proxy + +openLeft :: forall r u. Row.Union (a :: Int | r) (b :: String) u => Proxy u +openLeft = Proxy + +openRight :: forall r u. Row.Union (a :: Int) (b :: String | r) u => Proxy u +openRight = Proxy + +backwardLeft :: forall l r. Row.Union l (b :: String) (a :: Int, b :: String | r) => Proxy l +backwardLeft = Proxy + +backwardRight :: forall r u. Row.Union (a :: Int) r (a :: Int, b :: String | u) => Proxy r +backwardRight = Proxy + +forceSolve = { openLeft, openRight, backwardLeft, backwardRight } diff --git a/tests-integration/fixtures/checking2/133_row_open_union/Main.snap b/tests-integration/fixtures/checking2/133_row_open_union/Main.snap new file mode 100644 index 00000000..7bfc5779 --- /dev/null +++ b/tests-integration/fixtures/checking2/133_row_open_union/Main.snap @@ -0,0 +1,37 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (k :: Type) (a :: (k :: Type)). Proxy @(k :: Type) (a :: (k :: Type)) +openLeft :: + forall (r :: Row Type) (u :: Row Type). + Union @Type ( a :: Int | (r :: Row Type) ) ( b :: String ) (u :: Row Type) => + Proxy @(Row Type) (u :: Row Type) +openRight :: + forall (r :: Row Type) (u :: Row Type). + Union @Type ( a :: Int ) ( b :: String | (r :: Row Type) ) (u :: Row Type) => + Proxy @(Row Type) (u :: Row Type) +backwardLeft :: + forall (l :: Row Type) (r :: Row Type). + Union @Type (l :: Row Type) ( b :: String ) ( a :: Int, b :: String | (r :: Row Type) ) => + Proxy @(Row Type) (l :: Row Type) +backwardRight :: + forall (r :: Row Type) (u :: Row Type). + Union @Type ( a :: Int ) (r :: Row Type) ( a :: Int, b :: String | (u :: Row Type) ) => + Proxy @(Row Type) (r :: Row Type) +forceSolve :: + forall (t14 :: Row Type) (t13 :: Row Type) (t12 :: Row Type) (t11 :: Row Type) (t10 :: Row Type). + Union (t14 :: Row Type) ( b :: String ) (t13 :: Row Type) => + { backwardLeft :: Proxy @(Row Type) ( a :: Int | (t12 :: Row Type) ) + , backwardRight :: Proxy @(Row Type) ( b :: String | (t11 :: Row Type) ) + , openLeft :: Proxy @(Row Type) ( a :: Int | (t13 :: Row Type) ) + , openRight :: Proxy @(Row Type) ( a :: Int, b :: String | (t10 :: Row Type) ) + } + +Types +Proxy :: forall (k :: Type). (k :: Type) -> Type + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking2/134_row_open_cons/Main.purs b/tests-integration/fixtures/checking2/134_row_open_cons/Main.purs new file mode 100644 index 00000000..6a881ce7 --- /dev/null +++ b/tests-integration/fixtures/checking2/134_row_open_cons/Main.purs @@ -0,0 +1,17 @@ +module Main where + +import Prim.Row as Row + +data Proxy :: forall k. k -> Type +data Proxy a = Proxy + +consOpen :: forall r row. Row.Cons "x" Int (a :: String | r) row => Proxy row +consOpen = Proxy + +decomposeOpen :: forall t tail r. Row.Cons "x" t tail (x :: Int, a :: String | r) => Proxy t +decomposeOpen = Proxy + +extractTail :: forall tail r. Row.Cons "x" Int tail (x :: Int, a :: String | r) => Proxy tail +extractTail = Proxy + +forceSolve = { consOpen, decomposeOpen, extractTail } diff --git a/tests-integration/fixtures/checking2/134_row_open_cons/Main.snap b/tests-integration/fixtures/checking2/134_row_open_cons/Main.snap new file mode 100644 index 00000000..255ec020 --- /dev/null +++ b/tests-integration/fixtures/checking2/134_row_open_cons/Main.snap @@ -0,0 +1,31 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (k :: Type) (a :: (k :: Type)). Proxy @(k :: Type) (a :: (k :: Type)) +consOpen :: + forall (r :: Row Type) (row :: Row Type). + Cons @Type "x" Int ( a :: String | (r :: Row Type) ) (row :: Row Type) => + Proxy @(Row Type) (row :: Row Type) +decomposeOpen :: + forall (t :: Type) (tail :: Row Type) (r :: Row Type). + Cons @Type "x" (t :: Type) (tail :: Row Type) ( a :: String, x :: Int | (r :: Row Type) ) => + Proxy @Type (t :: Type) +extractTail :: + forall (tail :: Row Type) (r :: Row Type). + Cons @Type "x" Int (tail :: Row Type) ( a :: String, x :: Int | (r :: Row Type) ) => + Proxy @(Row Type) (tail :: Row Type) +forceSolve :: + forall (t10 :: Row Type) (t9 :: Row Type). + { consOpen :: Proxy @(Row Type) ( a :: String, x :: Int | (t10 :: Row Type) ) + , decomposeOpen :: Proxy @Type Int + , extractTail :: Proxy @(Row Type) ( a :: String | (t9 :: Row Type) ) + } + +Types +Proxy :: forall (k :: Type). (k :: Type) -> Type + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking2/135_row_open_lacks/Main.purs b/tests-integration/fixtures/checking2/135_row_open_lacks/Main.purs new file mode 100644 index 00000000..84c7849c --- /dev/null +++ b/tests-integration/fixtures/checking2/135_row_open_lacks/Main.purs @@ -0,0 +1,17 @@ +module Main where + +import Prim.Row as Row + +data Proxy :: forall k. k -> Type +data Proxy a = Proxy + +lacksOpen :: forall r. Row.Lacks "missing" (a :: Int, b :: String | r) => Proxy r -> Int +lacksOpen _ = 0 + +lacksPresent :: forall r. Row.Lacks "a" (a :: Int | r) => Proxy r -> Int +lacksPresent _ = 0 + +empty :: Proxy () +empty = Proxy + +forceSolve = { lacksOpen: lacksOpen empty, lacksPresent: lacksPresent empty } diff --git a/tests-integration/fixtures/checking2/135_row_open_lacks/Main.snap b/tests-integration/fixtures/checking2/135_row_open_lacks/Main.snap new file mode 100644 index 00000000..a85a0c11 --- /dev/null +++ b/tests-integration/fixtures/checking2/135_row_open_lacks/Main.snap @@ -0,0 +1,30 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (k :: Type) (a :: (k :: Type)). Proxy @(k :: Type) (a :: (k :: Type)) +lacksOpen :: + forall (r :: Row Type). + Lacks @Type "missing" ( a :: Int, b :: String | (r :: Row Type) ) => + Proxy @(Row Type) (r :: Row Type) -> Int +lacksPresent :: + forall (r :: Row Type). + Lacks @Type "a" ( a :: Int | (r :: Row Type) ) => Proxy @(Row Type) (r :: Row Type) -> Int +empty :: forall (t4 :: Type). Proxy @(Row (t4 :: Type)) () +forceSolve :: { lacksOpen :: Int, lacksPresent :: Int } + +Types +Proxy :: forall (k :: Type). (k :: Type) -> Type + +Roles +Proxy = [Phantom] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(18), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/136_row_open_record/Main.purs b/tests-integration/fixtures/checking2/136_row_open_record/Main.purs new file mode 100644 index 00000000..9ab0137a --- /dev/null +++ b/tests-integration/fixtures/checking2/136_row_open_record/Main.purs @@ -0,0 +1,21 @@ +module Main where + +import Prim.Row as Row + +foreign import unsafeCoerce :: forall a b. a -> b + +union :: forall r1 r2 r3. Row.Union r1 r2 r3 => Record r1 -> Record r2 -> Record r3 +union _ _ = unsafeCoerce {} + +addField :: forall r. { a :: Int | r } -> { a :: Int, b :: String | r } +addField x = union x { b: "hi" } + +test = addField { a: 1, c: true } + +insertX :: forall r. Row.Lacks "x" r => Record r -> Record (x :: Int | r) +insertX _ = unsafeCoerce {} + +insertOpen :: forall r. Row.Lacks "x" r => { a :: Int | r } -> { x :: Int, a :: Int | r } +insertOpen x = insertX x + +test2 = insertOpen { a: 1, b: "hi" } diff --git a/tests-integration/fixtures/checking2/136_row_open_record/Main.snap b/tests-integration/fixtures/checking2/136_row_open_record/Main.snap new file mode 100644 index 00000000..cd106588 --- /dev/null +++ b/tests-integration/fixtures/checking2/136_row_open_record/Main.snap @@ -0,0 +1,25 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +unsafeCoerce :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) +union :: + forall (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type). + Union @Type (r1 :: Row Type) (r2 :: Row Type) (r3 :: Row Type) => + {| (r1 :: Row Type) } -> {| (r2 :: Row Type) } -> {| (r3 :: Row Type) } +addField :: + forall (r :: Row Type). + { a :: Int | (r :: Row Type) } -> { a :: Int, b :: String | (r :: Row Type) } +test :: { a :: Int, b :: String, c :: Boolean } +insertX :: + forall (r :: Row Type). + Lacks @Type "x" (r :: Row Type) => {| (r :: Row Type) } -> { x :: Int | (r :: Row Type) } +insertOpen :: + forall (r :: Row Type). + Lacks @Type "x" (r :: Row Type) => + { a :: Int | (r :: Row Type) } -> { a :: Int, x :: Int | (r :: Row Type) } +test2 :: { a :: Int, b :: String, x :: Int } + +Types diff --git a/tests-integration/fixtures/checking2/137_type_operator_synonym_expansion/Main.purs b/tests-integration/fixtures/checking2/137_type_operator_synonym_expansion/Main.purs new file mode 100644 index 00000000..748a1a3d --- /dev/null +++ b/tests-integration/fixtures/checking2/137_type_operator_synonym_expansion/Main.purs @@ -0,0 +1,11 @@ +module Main where + +data Maybe a = Just a | Nothing + +type NaturalTransformation :: forall k. (k -> Type) -> (k -> Type) -> Type +type NaturalTransformation f g = forall a. f a -> g a + +infixr 4 type NaturalTransformation as ~> + +type Test1 = NaturalTransformation Array Maybe +type Test2 = Array ~> Maybe diff --git a/tests-integration/fixtures/checking2/137_type_operator_synonym_expansion/Main.snap b/tests-integration/fixtures/checking2/137_type_operator_synonym_expansion/Main.snap new file mode 100644 index 00000000..9d6603f7 --- /dev/null +++ b/tests-integration/fixtures/checking2/137_type_operator_synonym_expansion/Main.snap @@ -0,0 +1,24 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) + +Types +Maybe :: Type -> Type +NaturalTransformation :: forall (k :: Type). ((k :: Type) -> Type) -> ((k :: Type) -> Type) -> Type +~> :: forall (k :: Type). ((k :: Type) -> Type) -> ((k :: Type) -> Type) -> Type +Test1 :: Type +Test2 :: Type + +Synonyms +type NaturalTransformation f g = forall (a :: (k :: Type)). + (f :: (k :: Type) -> Type) (a :: (k :: Type)) -> (g :: (k :: Type) -> Type) (a :: (k :: Type)) +type Test1 = NaturalTransformation @Type Array Maybe +type Test2 = NaturalTransformation @Type Array Maybe + +Roles +Maybe = [Representational] diff --git a/tests-integration/fixtures/checking2/138_synonym_kind_application/Main.purs b/tests-integration/fixtures/checking2/138_synonym_kind_application/Main.purs new file mode 100644 index 00000000..b0d7f762 --- /dev/null +++ b/tests-integration/fixtures/checking2/138_synonym_kind_application/Main.purs @@ -0,0 +1,9 @@ +module Main where + +data RowBox :: Row Type -> Type +data RowBox r + +type Apply :: forall k. (k -> Type) -> k -> Type +type Apply f a = f a + +type RowDesugared = Apply RowBox () diff --git a/tests-integration/fixtures/checking2/138_synonym_kind_application/Main.snap b/tests-integration/fixtures/checking2/138_synonym_kind_application/Main.snap new file mode 100644 index 00000000..f25edea7 --- /dev/null +++ b/tests-integration/fixtures/checking2/138_synonym_kind_application/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +RowBox :: Row Type -> Type +Apply :: forall (k :: Type). ((k :: Type) -> Type) -> (k :: Type) -> Type +RowDesugared :: Type + +Synonyms +type Apply f a = (f :: (k :: Type) -> Type) (a :: (k :: Type)) +type RowDesugared = Apply @(Row Type) RowBox () + +Roles +RowBox = [Phantom] diff --git a/tests-integration/fixtures/checking2/139_synonym_operator_alias/Main.purs b/tests-integration/fixtures/checking2/139_synonym_operator_alias/Main.purs new file mode 100644 index 00000000..697b2b68 --- /dev/null +++ b/tests-integration/fixtures/checking2/139_synonym_operator_alias/Main.purs @@ -0,0 +1,9 @@ +module Main where + +type Apply :: forall k. (k -> Type) -> k -> Type +type Apply f = f + +infixr 5 type Apply as $ + +type Test1 = Apply Array Int +type Test2 = Array $ Int diff --git a/tests-integration/fixtures/checking2/139_synonym_operator_alias/Main.snap b/tests-integration/fixtures/checking2/139_synonym_operator_alias/Main.snap new file mode 100644 index 00000000..6f2ce5fa --- /dev/null +++ b/tests-integration/fixtures/checking2/139_synonym_operator_alias/Main.snap @@ -0,0 +1,17 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +Apply :: forall (k :: Type). ((k :: Type) -> Type) -> (k :: Type) -> Type +$ :: forall (k :: Type). ((k :: Type) -> Type) -> (k :: Type) -> Type +Test1 :: Type +Test2 :: Type + +Synonyms +type Apply f = (f :: (k :: Type) -> Type) +type Test1 = Apply @Type Array Int +type Test2 = Apply @Type Array Int diff --git a/tests-integration/fixtures/checking2/140_const_equation_forms/Main.purs b/tests-integration/fixtures/checking2/140_const_equation_forms/Main.purs new file mode 100644 index 00000000..b6571cee --- /dev/null +++ b/tests-integration/fixtures/checking2/140_const_equation_forms/Main.purs @@ -0,0 +1,10 @@ +module Main where + +const :: forall a b. a -> b -> a +const a b = a + +const2 :: forall a b. a -> b -> a +const2 a = \b -> a + +const3 :: forall a b. a -> b -> a +const3 = \a b -> a diff --git a/tests-integration/fixtures/checking2/140_const_equation_forms/Main.snap b/tests-integration/fixtures/checking2/140_const_equation_forms/Main.snap new file mode 100644 index 00000000..c5921108 --- /dev/null +++ b/tests-integration/fixtures/checking2/140_const_equation_forms/Main.snap @@ -0,0 +1,11 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +const :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> (a :: Type) +const2 :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> (a :: Type) +const3 :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> (a :: Type) + +Types diff --git a/tests-integration/fixtures/checking2/141_const_forms_synonym_arrow/Main.purs b/tests-integration/fixtures/checking2/141_const_forms_synonym_arrow/Main.purs new file mode 100644 index 00000000..238c3f3a --- /dev/null +++ b/tests-integration/fixtures/checking2/141_const_forms_synonym_arrow/Main.purs @@ -0,0 +1,15 @@ +module Main where + +type Const a b = a -> b -> a + +const0 :: forall a b. Const a b +const0 = \a b -> a + +const1 :: forall a b. Const a b +const1 a b = a + +const2 :: forall a b. Const a b +const2 a = \b -> a + +const3 :: forall a b. Const a b +const3 = \a -> \b -> a diff --git a/tests-integration/fixtures/checking2/141_const_forms_synonym_arrow/Main.snap b/tests-integration/fixtures/checking2/141_const_forms_synonym_arrow/Main.snap new file mode 100644 index 00000000..65cb0fef --- /dev/null +++ b/tests-integration/fixtures/checking2/141_const_forms_synonym_arrow/Main.snap @@ -0,0 +1,16 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +const0 :: forall (a :: Type) (b :: Type). Const (a :: Type) (b :: Type) +const1 :: forall (a :: Type) (b :: Type). Const (a :: Type) (b :: Type) +const2 :: forall (a :: Type) (b :: Type). Const (a :: Type) (b :: Type) +const3 :: forall (a :: Type) (b :: Type). Const (a :: Type) (b :: Type) + +Types +Const :: Type -> Type -> Type + +Synonyms +type Const a b = (a :: Type) -> (b :: Type) -> (a :: Type) diff --git a/tests-integration/fixtures/checking2/142_const_forms_synonym_forall/Main.purs b/tests-integration/fixtures/checking2/142_const_forms_synonym_forall/Main.purs new file mode 100644 index 00000000..4fb553d6 --- /dev/null +++ b/tests-integration/fixtures/checking2/142_const_forms_synonym_forall/Main.purs @@ -0,0 +1,15 @@ +module Main where + +type ConstPoly = forall a b. a -> b -> a + +poly0 :: ConstPoly +poly0 = \a b -> a + +poly1 :: ConstPoly +poly1 a b = a + +poly2 :: ConstPoly +poly2 a = \b -> a + +poly3 :: ConstPoly +poly3 = \a -> \b -> a diff --git a/tests-integration/fixtures/checking2/142_const_forms_synonym_forall/Main.snap b/tests-integration/fixtures/checking2/142_const_forms_synonym_forall/Main.snap new file mode 100644 index 00000000..c62c8559 --- /dev/null +++ b/tests-integration/fixtures/checking2/142_const_forms_synonym_forall/Main.snap @@ -0,0 +1,16 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +poly0 :: ConstPoly +poly1 :: ConstPoly +poly2 :: ConstPoly +poly3 :: ConstPoly + +Types +ConstPoly :: Type + +Synonyms +type ConstPoly = forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> (a :: Type) diff --git a/tests-integration/fixtures/checking2/143_const_forms_fn_alias/Main.purs b/tests-integration/fixtures/checking2/143_const_forms_fn_alias/Main.purs new file mode 100644 index 00000000..6d69f3ec --- /dev/null +++ b/tests-integration/fixtures/checking2/143_const_forms_fn_alias/Main.purs @@ -0,0 +1,22 @@ +module Main where + +type Fn b a = b -> a +type ConstFn a b = a -> Fn b a + +fn0 :: forall a b. ConstFn a b +fn0 = \a b -> a + +fn1 :: forall a b. ConstFn a b +fn1 a b = a + +fn2 :: forall a b. ConstFn a b +fn2 a = \b -> a + +fn3 :: forall a b. ConstFn a b +fn3 = \a -> \b -> a + +fn4 :: forall b. forall a. a -> Fn b a +fn4 a b = a + +fn5 :: forall b. forall a. a -> Fn b a +fn5 a = \b -> a diff --git a/tests-integration/fixtures/checking2/143_const_forms_fn_alias/Main.snap b/tests-integration/fixtures/checking2/143_const_forms_fn_alias/Main.snap new file mode 100644 index 00000000..2b87cbfe --- /dev/null +++ b/tests-integration/fixtures/checking2/143_const_forms_fn_alias/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +fn0 :: forall (a :: Type) (b :: Type). ConstFn (a :: Type) (b :: Type) +fn1 :: forall (a :: Type) (b :: Type). ConstFn (a :: Type) (b :: Type) +fn2 :: forall (a :: Type) (b :: Type). ConstFn (a :: Type) (b :: Type) +fn3 :: forall (a :: Type) (b :: Type). ConstFn (a :: Type) (b :: Type) +fn4 :: forall (b :: Type) (a :: Type). (a :: Type) -> Fn (b :: Type) (a :: Type) +fn5 :: forall (b :: Type) (a :: Type). (a :: Type) -> Fn (b :: Type) (a :: Type) + +Types +Fn :: Type -> Type -> Type +ConstFn :: Type -> Type -> Type + +Synonyms +type Fn b a = (b :: Type) -> (a :: Type) +type ConstFn a b = (a :: Type) -> Fn (b :: Type) (a :: Type) diff --git a/tests-integration/fixtures/checking2/144_signature_synonym_data_equation/Main.purs b/tests-integration/fixtures/checking2/144_signature_synonym_data_equation/Main.purs new file mode 100644 index 00000000..7596edb0 --- /dev/null +++ b/tests-integration/fixtures/checking2/144_signature_synonym_data_equation/Main.purs @@ -0,0 +1,6 @@ +module Main where + +type UnaryKind = Type -> Type + +data Box :: UnaryKind +data Box a = Box a diff --git a/tests-integration/fixtures/checking2/144_signature_synonym_data_equation/Main.snap b/tests-integration/fixtures/checking2/144_signature_synonym_data_equation/Main.snap new file mode 100644 index 00000000..aca2a0dd --- /dev/null +++ b/tests-integration/fixtures/checking2/144_signature_synonym_data_equation/Main.snap @@ -0,0 +1,17 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Box :: forall (a :: Type). (a :: Type) -> Box (a :: Type) + +Types +UnaryKind :: Type +Box :: UnaryKind + +Synonyms +type UnaryKind = Type -> Type + +Roles +Box = [Representational] diff --git a/tests-integration/fixtures/checking2/145_signature_synonym_class_equation/Main.purs b/tests-integration/fixtures/checking2/145_signature_synonym_class_equation/Main.purs new file mode 100644 index 00000000..1023aa62 --- /dev/null +++ b/tests-integration/fixtures/checking2/145_signature_synonym_class_equation/Main.purs @@ -0,0 +1,7 @@ +module Main where + +type ClassHead = Type -> Constraint + +class EqLike :: ClassHead +class EqLike a where + eqLike :: a -> a -> Boolean diff --git a/tests-integration/fixtures/checking2/145_signature_synonym_class_equation/Main.snap b/tests-integration/fixtures/checking2/145_signature_synonym_class_equation/Main.snap new file mode 100644 index 00000000..55255efa --- /dev/null +++ b/tests-integration/fixtures/checking2/145_signature_synonym_class_equation/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +eqLike :: forall (a :: Type). EqLike (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean + +Types +ClassHead :: Type +EqLike :: ClassHead + +Synonyms +type ClassHead = Type -> Constraint + +Classes +class forall (a :: Type). EqLike (a :: Type) + eqLike :: forall (a :: Type). EqLike (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean diff --git a/tests-integration/fixtures/checking2/146_signature_synonym_type_equation/Main.purs b/tests-integration/fixtures/checking2/146_signature_synonym_type_equation/Main.purs new file mode 100644 index 00000000..3ec4c371 --- /dev/null +++ b/tests-integration/fixtures/checking2/146_signature_synonym_type_equation/Main.purs @@ -0,0 +1,6 @@ +module Main where + +type BinaryKind = Type -> Type -> Type + +type ConstT :: BinaryKind +type ConstT a b = a diff --git a/tests-integration/fixtures/checking2/146_signature_synonym_type_equation/Main.snap b/tests-integration/fixtures/checking2/146_signature_synonym_type_equation/Main.snap new file mode 100644 index 00000000..f3e65ea8 --- /dev/null +++ b/tests-integration/fixtures/checking2/146_signature_synonym_type_equation/Main.snap @@ -0,0 +1,14 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +BinaryKind :: Type +ConstT :: BinaryKind + +Synonyms +type BinaryKind = Type -> Type -> Type +type ConstT a b = (a :: Type) diff --git a/tests-integration/fixtures/checking2/147_synonym_oversaturation_equations/Main.purs b/tests-integration/fixtures/checking2/147_synonym_oversaturation_equations/Main.purs new file mode 100644 index 00000000..f694f4c5 --- /dev/null +++ b/tests-integration/fixtures/checking2/147_synonym_oversaturation_equations/Main.purs @@ -0,0 +1,14 @@ +module Main where + +type Identity :: forall k. k -> k +type Identity a = a + +data Tuple a b = Tuple a b + +test1 :: Identity Array Int +test1 = [42] + +test2 :: Identity Tuple Int String +test2 = Tuple 42 "hello" + +forceSolve = { test1, test2 } diff --git a/tests-integration/fixtures/checking2/147_synonym_oversaturation_equations/Main.snap b/tests-integration/fixtures/checking2/147_synonym_oversaturation_equations/Main.snap new file mode 100644 index 00000000..5762da42 --- /dev/null +++ b/tests-integration/fixtures/checking2/147_synonym_oversaturation_equations/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Tuple :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> Tuple (a :: Type) (b :: Type) +test1 :: Identity @(Type -> Type) Array Int +test2 :: Identity @(Type -> Type -> Type) Tuple Int String +forceSolve :: { test1 :: Array Int, test2 :: Tuple Int String } + +Types +Identity :: forall (k :: Type). (k :: Type) -> (k :: Type) +Tuple :: Type -> Type -> Type + +Synonyms +type Identity a = (a :: (k :: Type)) + +Roles +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking2/148_class_member_prenex/Main.purs b/tests-integration/fixtures/checking2/148_class_member_prenex/Main.purs new file mode 100644 index 00000000..2ec9a4c7 --- /dev/null +++ b/tests-integration/fixtures/checking2/148_class_member_prenex/Main.purs @@ -0,0 +1,5 @@ +module Main where + +class Const :: (Type -> Type) -> Constraint +class Const f where + const :: forall a. f a -> forall b. f b -> f a diff --git a/tests-integration/fixtures/checking2/148_class_member_prenex/Main.snap b/tests-integration/fixtures/checking2/148_class_member_prenex/Main.snap new file mode 100644 index 00000000..3b36c82c --- /dev/null +++ b/tests-integration/fixtures/checking2/148_class_member_prenex/Main.snap @@ -0,0 +1,24 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +const :: + forall (f :: Type -> Type) (a :: Type) (b :: Type). + Const (f :: Type -> Type) => + (f :: Type -> Type) (a :: Type) -> + (f :: Type -> Type) (b :: Type) -> + (f :: Type -> Type) (a :: Type) + +Types +Const :: (Type -> Type) -> Constraint + +Classes +class forall (f :: Type -> Type). Const (f :: Type -> Type) + const :: + forall (f :: Type -> Type) (a :: Type) (b :: Type). + Const (f :: Type -> Type) => + (f :: Type -> Type) (a :: Type) -> + (f :: Type -> Type) (b :: Type) -> + (f :: Type -> Type) (a :: Type) diff --git a/tests-integration/fixtures/checking2/149_synonym_oversaturation_kind/Main.purs b/tests-integration/fixtures/checking2/149_synonym_oversaturation_kind/Main.purs new file mode 100644 index 00000000..bccb121a --- /dev/null +++ b/tests-integration/fixtures/checking2/149_synonym_oversaturation_kind/Main.purs @@ -0,0 +1,19 @@ +module Main where + +data Proxy :: forall k. k -> Type +data Proxy a = Proxy + +type Test0 = Proxy + +test0 :: Test0 42 +test0 = Proxy + +type Test1 a = Proxy + +test1 :: Test1 Int 42 +test1 = Proxy + +type Test2 a b = Proxy + +test2 :: Test2 Int String 42 +test2 = Proxy diff --git a/tests-integration/fixtures/checking2/149_synonym_oversaturation_kind/Main.snap b/tests-integration/fixtures/checking2/149_synonym_oversaturation_kind/Main.snap new file mode 100644 index 00000000..33894cc9 --- /dev/null +++ b/tests-integration/fixtures/checking2/149_synonym_oversaturation_kind/Main.snap @@ -0,0 +1,26 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (k :: Type) (a :: (k :: Type)). Proxy @(k :: Type) (a :: (k :: Type)) +test0 :: Test0 @Int 42 +test1 :: Test1 @Type @Int Int 42 +test2 :: Test2 @Type @Type @Int Int String 42 + +Types +Proxy :: forall (k :: Type). (k :: Type) -> Type +Test0 :: forall (t2 :: Type). (t2 :: Type) -> Type +Test1 :: forall (t5 :: Type) (t4 :: Type). (t5 :: Type) -> (t4 :: Type) -> Type +Test2 :: + forall (t10 :: Type) (t9 :: Type) (t8 :: Type). + (t10 :: Type) -> (t9 :: Type) -> (t8 :: Type) -> Type + +Synonyms +type Test0 = Proxy @(t2 :: Type) +type Test1 a = Proxy @(t4 :: Type) +type Test2 a b = Proxy @(t8 :: Type) + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking2/150_derive_generic_insufficient_params/Main.purs b/tests-integration/fixtures/checking2/150_derive_generic_insufficient_params/Main.purs new file mode 100644 index 00000000..d0b16d42 --- /dev/null +++ b/tests-integration/fixtures/checking2/150_derive_generic_insufficient_params/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Generic.Rep (class Generic) + +data Box = Box Int + +derive instance Generic Box diff --git a/tests-integration/fixtures/checking2/150_derive_generic_insufficient_params/Main.snap b/tests-integration/fixtures/checking2/150_derive_generic_insufficient_params/Main.snap new file mode 100644 index 00000000..0de4ec95 --- /dev/null +++ b/tests-integration/fixtures/checking2/150_derive_generic_insufficient_params/Main.snap @@ -0,0 +1,52 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Box :: Int -> Box + +Types +Box :: Type + +Roles +Box = [] + +Errors +CheckError { + kind: DeriveInvalidArity { + class_file: Idx::(23), + class_id: Idx::(0), + expected: 2, + actual: 1, + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(7), + t2: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: DeriveInvalidArity { + class_file: Idx::(23), + class_id: Idx::(0), + expected: 2, + actual: 1, + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/151_derive_generic_not_constructor/Main.purs b/tests-integration/fixtures/checking2/151_derive_generic_not_constructor/Main.purs new file mode 100644 index 00000000..4d347339 --- /dev/null +++ b/tests-integration/fixtures/checking2/151_derive_generic_not_constructor/Main.purs @@ -0,0 +1,5 @@ +module Main where + +import Data.Generic.Rep (class Generic) + +derive instance Generic (Int -> Int) _ diff --git a/tests-integration/fixtures/checking2/151_derive_generic_not_constructor/Main.snap b/tests-integration/fixtures/checking2/151_derive_generic_not_constructor/Main.snap new file mode 100644 index 00000000..75c7ab0f --- /dev/null +++ b/tests-integration/fixtures/checking2/151_derive_generic_not_constructor/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types + +Errors +CheckError { + kind: CannotDeriveForType { + type_message: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/152_derive_generic_missing_rep/Data.Generic.Rep.purs b/tests-integration/fixtures/checking2/152_derive_generic_missing_rep/Data.Generic.Rep.purs new file mode 100644 index 00000000..25cb321d --- /dev/null +++ b/tests-integration/fixtures/checking2/152_derive_generic_missing_rep/Data.Generic.Rep.purs @@ -0,0 +1,6 @@ +-- | This shadows the test prelude to remove the representation types. +module Data.Generic.Rep where + +class Generic a rep | a -> rep where + to :: rep -> a + from :: a -> rep diff --git a/tests-integration/fixtures/checking2/152_derive_generic_missing_rep/Main.purs b/tests-integration/fixtures/checking2/152_derive_generic_missing_rep/Main.purs new file mode 100644 index 00000000..9aceff83 --- /dev/null +++ b/tests-integration/fixtures/checking2/152_derive_generic_missing_rep/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Generic.Rep (class Generic) + +data Box = Box Int + +derive instance Generic Box _ diff --git a/tests-integration/fixtures/checking2/152_derive_generic_missing_rep/Main.snap b/tests-integration/fixtures/checking2/152_derive_generic_missing_rep/Main.snap new file mode 100644 index 00000000..9c8b9ce0 --- /dev/null +++ b/tests-integration/fixtures/checking2/152_derive_generic_missing_rep/Main.snap @@ -0,0 +1,26 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Box :: Int -> Box + +Types +Box :: Type + +Roles +Box = [] + +Errors +CheckError { + kind: CannotDeriveClass { + class_file: Idx::(39), + class_id: Idx::(0), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/153_do_discard/Main.purs b/tests-integration/fixtures/checking2/153_do_discard/Main.purs new file mode 100644 index 00000000..4bc8e403 --- /dev/null +++ b/tests-integration/fixtures/checking2/153_do_discard/Main.purs @@ -0,0 +1,21 @@ +module Main where + +foreign import data Effect :: Type -> Type + +data Unit = Unit + +foreign import pure :: forall a. a -> Effect a +foreign import bind :: forall a b. Effect a -> (a -> Effect b) -> Effect b +foreign import discard :: forall a b. Effect a -> (a -> Effect b) -> Effect b + +unit :: Unit +unit = Unit + +test :: Effect Unit +test = do + pure 1 + pure unit + +test' = do + pure 1 + pure unit diff --git a/tests-integration/fixtures/checking2/153_do_discard/Main.snap b/tests-integration/fixtures/checking2/153_do_discard/Main.snap new file mode 100644 index 00000000..239b5b9b --- /dev/null +++ b/tests-integration/fixtures/checking2/153_do_discard/Main.snap @@ -0,0 +1,25 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Unit :: Unit +pure :: forall (a :: Type). (a :: Type) -> Effect (a :: Type) +bind :: + forall (a :: Type) (b :: Type). + Effect (a :: Type) -> ((a :: Type) -> Effect (b :: Type)) -> Effect (b :: Type) +discard :: + forall (a :: Type) (b :: Type). + Effect (a :: Type) -> ((a :: Type) -> Effect (b :: Type)) -> Effect (b :: Type) +unit :: Unit +test :: Effect Unit +test' :: Effect Unit + +Types +Effect :: Type -> Type +Unit :: Type + +Roles +Effect = [Nominal] +Unit = [] diff --git a/tests-integration/fixtures/checking2/154_do_bind/Main.purs b/tests-integration/fixtures/checking2/154_do_bind/Main.purs new file mode 100644 index 00000000..b95434c6 --- /dev/null +++ b/tests-integration/fixtures/checking2/154_do_bind/Main.purs @@ -0,0 +1,20 @@ +module Main where + +foreign import data Effect :: Type -> Type + +data Tuple a b = Tuple a b + +foreign import pure :: forall a. a -> Effect a +foreign import bind :: forall a b. Effect a -> (a -> Effect b) -> Effect b +foreign import discard :: forall a b. Effect a -> (a -> Effect b) -> Effect b + +test :: Effect (Tuple Int Int) +test = do + x <- pure 1 + y <- pure 2 + pure (Tuple x y) + +test' = do + x <- pure 1 + y <- pure 2 + pure (Tuple x y) diff --git a/tests-integration/fixtures/checking2/154_do_bind/Main.snap b/tests-integration/fixtures/checking2/154_do_bind/Main.snap new file mode 100644 index 00000000..42f16f93 --- /dev/null +++ b/tests-integration/fixtures/checking2/154_do_bind/Main.snap @@ -0,0 +1,24 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Tuple :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> Tuple (a :: Type) (b :: Type) +pure :: forall (a :: Type). (a :: Type) -> Effect (a :: Type) +bind :: + forall (a :: Type) (b :: Type). + Effect (a :: Type) -> ((a :: Type) -> Effect (b :: Type)) -> Effect (b :: Type) +discard :: + forall (a :: Type) (b :: Type). + Effect (a :: Type) -> ((a :: Type) -> Effect (b :: Type)) -> Effect (b :: Type) +test :: Effect (Tuple Int Int) +test' :: Effect (Tuple Int Int) + +Types +Effect :: Type -> Type +Tuple :: Type -> Type -> Type + +Roles +Effect = [Nominal] +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking2/155_ado_discard/Main.purs b/tests-integration/fixtures/checking2/155_ado_discard/Main.purs new file mode 100644 index 00000000..7e74a291 --- /dev/null +++ b/tests-integration/fixtures/checking2/155_ado_discard/Main.purs @@ -0,0 +1,23 @@ +module Main where + +foreign import data Effect :: Type -> Type + +data Unit = Unit + +foreign import pure :: forall a. a -> Effect a +foreign import map :: forall a b. (a -> b) -> Effect a -> Effect b +foreign import apply :: forall a b. Effect (a -> b) -> Effect a -> Effect b + +unit :: Unit +unit = Unit + +test :: Effect Unit +test = ado + pure 1 + pure unit + in unit + +test' = ado + pure 1 + pure unit + in unit diff --git a/tests-integration/fixtures/checking2/155_ado_discard/Main.snap b/tests-integration/fixtures/checking2/155_ado_discard/Main.snap new file mode 100644 index 00000000..9acf8058 --- /dev/null +++ b/tests-integration/fixtures/checking2/155_ado_discard/Main.snap @@ -0,0 +1,25 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Unit :: Unit +pure :: forall (a :: Type). (a :: Type) -> Effect (a :: Type) +map :: + forall (a :: Type) (b :: Type). + ((a :: Type) -> (b :: Type)) -> Effect (a :: Type) -> Effect (b :: Type) +apply :: + forall (a :: Type) (b :: Type). + Effect ((a :: Type) -> (b :: Type)) -> Effect (a :: Type) -> Effect (b :: Type) +unit :: Unit +test :: Effect Unit +test' :: Effect Unit + +Types +Effect :: Type -> Type +Unit :: Type + +Roles +Effect = [Nominal] +Unit = [] diff --git a/tests-integration/fixtures/checking2/156_ado_bind/Main.purs b/tests-integration/fixtures/checking2/156_ado_bind/Main.purs new file mode 100644 index 00000000..babb2031 --- /dev/null +++ b/tests-integration/fixtures/checking2/156_ado_bind/Main.purs @@ -0,0 +1,20 @@ +module Main where + +foreign import data Effect :: Type -> Type + +data Tuple a b = Tuple a b + +foreign import pure :: forall a. a -> Effect a +foreign import map :: forall a b. (a -> b) -> Effect a -> Effect b +foreign import apply :: forall a b. Effect (a -> b) -> Effect a -> Effect b + +test :: Effect (Tuple Int Int) +test = ado + x <- pure 1 + y <- pure 2 + in Tuple x y + +test' = ado + x <- pure 1 + y <- pure 2 + in Tuple x y diff --git a/tests-integration/fixtures/checking2/156_ado_bind/Main.snap b/tests-integration/fixtures/checking2/156_ado_bind/Main.snap new file mode 100644 index 00000000..893d3c0a --- /dev/null +++ b/tests-integration/fixtures/checking2/156_ado_bind/Main.snap @@ -0,0 +1,24 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Tuple :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> Tuple (a :: Type) (b :: Type) +pure :: forall (a :: Type). (a :: Type) -> Effect (a :: Type) +map :: + forall (a :: Type) (b :: Type). + ((a :: Type) -> (b :: Type)) -> Effect (a :: Type) -> Effect (b :: Type) +apply :: + forall (a :: Type) (b :: Type). + Effect ((a :: Type) -> (b :: Type)) -> Effect (a :: Type) -> Effect (b :: Type) +test :: Effect (Tuple Int Int) +test' :: Effect (Tuple Int Int) + +Types +Effect :: Type -> Type +Tuple :: Type -> Type -> Type + +Roles +Effect = [Nominal] +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking2/157_do_polymorphic/Main.purs b/tests-integration/fixtures/checking2/157_do_polymorphic/Main.purs new file mode 100644 index 00000000..6c9de827 --- /dev/null +++ b/tests-integration/fixtures/checking2/157_do_polymorphic/Main.purs @@ -0,0 +1,19 @@ +module Main where + +data Tuple a b = Tuple a b + +foreign import bind :: forall m a b. m a -> (a -> m b) -> m b +foreign import discard :: forall m a b. m a -> (a -> m b) -> m b + +foreign import pure :: forall m a. a -> m a + +test :: forall m. m (Tuple Int String) +test = do + x <- pure 1 + y <- pure "hello" + pure (Tuple x y) + +test' = do + x <- pure 1 + y <- pure "hello" + pure (Tuple x y) diff --git a/tests-integration/fixtures/checking2/157_do_polymorphic/Main.snap b/tests-integration/fixtures/checking2/157_do_polymorphic/Main.snap new file mode 100644 index 00000000..201f0736 --- /dev/null +++ b/tests-integration/fixtures/checking2/157_do_polymorphic/Main.snap @@ -0,0 +1,26 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Tuple :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> Tuple (a :: Type) (b :: Type) +bind :: + forall (m :: Type -> Type) (a :: Type) (b :: Type). + (m :: Type -> Type) (a :: Type) -> + ((a :: Type) -> (m :: Type -> Type) (b :: Type)) -> + (m :: Type -> Type) (b :: Type) +discard :: + forall (m :: Type -> Type) (a :: Type) (b :: Type). + (m :: Type -> Type) (a :: Type) -> + ((a :: Type) -> (m :: Type -> Type) (b :: Type)) -> + (m :: Type -> Type) (b :: Type) +pure :: forall (m :: Type -> Type) (a :: Type). (a :: Type) -> (m :: Type -> Type) (a :: Type) +test :: forall (m :: Type -> Type). (m :: Type -> Type) (Tuple Int String) +test' :: forall (t11 :: Type -> Type). (t11 :: Type -> Type) (Tuple Int String) + +Types +Tuple :: Type -> Type -> Type + +Roles +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking2/158_ado_polymorphic/Main.purs b/tests-integration/fixtures/checking2/158_ado_polymorphic/Main.purs new file mode 100644 index 00000000..572e455b --- /dev/null +++ b/tests-integration/fixtures/checking2/158_ado_polymorphic/Main.purs @@ -0,0 +1,19 @@ +module Main where + +data Tuple a b = Tuple a b + +foreign import map :: forall f a b. (a -> b) -> f a -> f b +foreign import apply :: forall f a b. f (a -> b) -> f a -> f b + +foreign import pure :: forall f a. a -> f a + +test :: forall f. f (Tuple Int String) +test = ado + x <- pure 1 + y <- pure "hello" + in Tuple x y + +test' = ado + x <- pure 1 + y <- pure "hello" + in Tuple x y diff --git a/tests-integration/fixtures/checking2/158_ado_polymorphic/Main.snap b/tests-integration/fixtures/checking2/158_ado_polymorphic/Main.snap new file mode 100644 index 00000000..dbeaaa82 --- /dev/null +++ b/tests-integration/fixtures/checking2/158_ado_polymorphic/Main.snap @@ -0,0 +1,26 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Tuple :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> Tuple (a :: Type) (b :: Type) +map :: + forall (f :: Type -> Type) (a :: Type) (b :: Type). + ((a :: Type) -> (b :: Type)) -> + (f :: Type -> Type) (a :: Type) -> + (f :: Type -> Type) (b :: Type) +apply :: + forall (f :: Type -> Type) (a :: Type) (b :: Type). + (f :: Type -> Type) ((a :: Type) -> (b :: Type)) -> + (f :: Type -> Type) (a :: Type) -> + (f :: Type -> Type) (b :: Type) +pure :: forall (f :: Type -> Type) (a :: Type). (a :: Type) -> (f :: Type -> Type) (a :: Type) +test :: forall (f :: Type -> Type). (f :: Type -> Type) (Tuple Int String) +test' :: forall (t11 :: Type -> Type). (t11 :: Type -> Type) (Tuple Int String) + +Types +Tuple :: Type -> Type -> Type + +Roles +Tuple = [Representational, Representational] diff --git a/tests-integration/fixtures/checking2/159_do_empty_block/Main.purs b/tests-integration/fixtures/checking2/159_do_empty_block/Main.purs new file mode 100644 index 00000000..ed4e84ff --- /dev/null +++ b/tests-integration/fixtures/checking2/159_do_empty_block/Main.purs @@ -0,0 +1,9 @@ +module Main where + +foreign import data Effect :: Type -> Type + +foreign import pure :: forall a. a -> Effect a +foreign import bind :: forall a b. Effect a -> (a -> Effect b) -> Effect b +foreign import discard :: forall a b. Effect a -> (a -> Effect b) -> Effect b + +test = do diff --git a/tests-integration/fixtures/checking2/159_do_empty_block/Main.snap b/tests-integration/fixtures/checking2/159_do_empty_block/Main.snap new file mode 100644 index 00000000..b2727c05 --- /dev/null +++ b/tests-integration/fixtures/checking2/159_do_empty_block/Main.snap @@ -0,0 +1,44 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +pure :: forall (a :: Type). (a :: Type) -> Effect (a :: Type) +bind :: + forall (a :: Type) (b :: Type). + Effect (a :: Type) -> ((a :: Type) -> Effect (b :: Type)) -> Effect (b :: Type) +discard :: + forall (a :: Type) (b :: Type). + Effect (a :: Type) -> ((a :: Type) -> Effect (b :: Type)) -> Effect (b :: Type) +test :: ?[empty do block] + +Types +Effect :: Type -> Type + +Roles +Effect = [Nominal] + +Errors +CheckError { + kind: EmptyDoBlock, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + InferringExpression( + AstId(56), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(9), + t2: Id(10), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/160_ado_empty_block/Main.purs b/tests-integration/fixtures/checking2/160_ado_empty_block/Main.purs new file mode 100644 index 00000000..2dff67e6 --- /dev/null +++ b/tests-integration/fixtures/checking2/160_ado_empty_block/Main.purs @@ -0,0 +1,12 @@ +module Main where + +foreign import data Effect :: Type -> Type + +foreign import pure :: forall a. a -> Effect a +foreign import map :: forall a b. (a -> b) -> Effect a -> Effect b +foreign import apply :: forall a b. Effect (a -> b) -> Effect a -> Effect b + +test1 = ado + +test2 = ado + in 123 diff --git a/tests-integration/fixtures/checking2/160_ado_empty_block/Main.snap b/tests-integration/fixtures/checking2/160_ado_empty_block/Main.snap new file mode 100644 index 00000000..75cbaaf1 --- /dev/null +++ b/tests-integration/fixtures/checking2/160_ado_empty_block/Main.snap @@ -0,0 +1,45 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +pure :: forall (a :: Type). (a :: Type) -> Effect (a :: Type) +map :: + forall (a :: Type) (b :: Type). + ((a :: Type) -> (b :: Type)) -> Effect (a :: Type) -> Effect (b :: Type) +apply :: + forall (a :: Type) (b :: Type). + Effect ((a :: Type) -> (b :: Type)) -> Effect (a :: Type) -> Effect (b :: Type) +test1 :: ?[empty ado block] +test2 :: Effect Int + +Types +Effect :: Type -> Type + +Roles +Effect = [Nominal] + +Errors +CheckError { + kind: EmptyAdoBlock, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + InferringExpression( + AstId(54), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(7), + t2: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/161_do_ado_constrained/Main.purs b/tests-integration/fixtures/checking2/161_do_ado_constrained/Main.purs new file mode 100644 index 00000000..a2656963 --- /dev/null +++ b/tests-integration/fixtures/checking2/161_do_ado_constrained/Main.purs @@ -0,0 +1,51 @@ +module Main where + +data Tuple a b = Tuple a b + +class Functor f where + map :: forall a b. (a -> b) -> f a -> f b + +class Functor f <= Apply f where + apply :: forall a b. f (a -> b) -> f a -> f b + +class Apply f <= Applicative f where + pure :: forall a. a -> f a + +class Applicative m <= Discard m where + discard :: forall a b. m a -> (a -> m b) -> m b + +class Applicative m <= Bind m where + bind :: forall a b. m a -> (a -> m b) -> m b + +class Bind m <= Monad m + +testDo :: forall m. Monad m => m (Tuple Int String) +testDo = do + x <- pure 1 + y <- pure "hello" + pure (Tuple x y) + +testDo' = do + x <- pure 1 + y <- pure "hello" + pure (Tuple x y) + +testAdo :: forall f. Applicative f => f (Tuple Int String) +testAdo = ado + x <- pure 1 + y <- pure "hello" + in Tuple x y + +testAdo' = ado + x <- pure 1 + y <- pure "hello" + in Tuple x y + +testDoDiscard :: forall m. Monad m => m Int +testDoDiscard = do + pure "ignored" + pure 42 + +testDoDiscard' = do + pure "ignored" + pure 42 diff --git a/tests-integration/fixtures/checking2/161_do_ado_constrained/Main.snap b/tests-integration/fixtures/checking2/161_do_ado_constrained/Main.snap new file mode 100644 index 00000000..eb020d53 --- /dev/null +++ b/tests-integration/fixtures/checking2/161_do_ado_constrained/Main.snap @@ -0,0 +1,103 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Tuple :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> Tuple (a :: Type) (b :: Type) +map :: + forall (f :: Type -> Type) (a :: Type) (b :: Type). + Functor (f :: Type -> Type) => + ((a :: Type) -> (b :: Type)) -> + (f :: Type -> Type) (a :: Type) -> + (f :: Type -> Type) (b :: Type) +apply :: + forall (f :: Type -> Type) (a :: Type) (b :: Type). + Apply (f :: Type -> Type) => + (f :: Type -> Type) ((a :: Type) -> (b :: Type)) -> + (f :: Type -> Type) (a :: Type) -> + (f :: Type -> Type) (b :: Type) +pure :: + forall (f :: Type -> Type) (a :: Type). + Applicative (f :: Type -> Type) => (a :: Type) -> (f :: Type -> Type) (a :: Type) +discard :: + forall (m :: Type -> Type) (a :: Type) (b :: Type). + Discard (m :: Type -> Type) => + (m :: Type -> Type) (a :: Type) -> + ((a :: Type) -> (m :: Type -> Type) (b :: Type)) -> + (m :: Type -> Type) (b :: Type) +bind :: + forall (m :: Type -> Type) (a :: Type) (b :: Type). + Bind (m :: Type -> Type) => + (m :: Type -> Type) (a :: Type) -> + ((a :: Type) -> (m :: Type -> Type) (b :: Type)) -> + (m :: Type -> Type) (b :: Type) +testDo :: + forall (m :: Type -> Type). Monad (m :: Type -> Type) => (m :: Type -> Type) (Tuple Int String) +testDo' :: + forall (t18 :: Type -> Type). + Bind (t18 :: Type -> Type) => (t18 :: Type -> Type) (Tuple Int String) +testAdo :: + forall (f :: Type -> Type). + Applicative (f :: Type -> Type) => (f :: Type -> Type) (Tuple Int String) +testAdo' :: + forall (t20 :: Type -> Type). + Applicative (t20 :: Type -> Type) => (t20 :: Type -> Type) (Tuple Int String) +testDoDiscard :: forall (m :: Type -> Type). Monad (m :: Type -> Type) => (m :: Type -> Type) Int +testDoDiscard' :: + forall (t22 :: Type -> Type). Discard (t22 :: Type -> Type) => (t22 :: Type -> Type) Int + +Types +Tuple :: Type -> Type -> Type +Functor :: (Type -> Type) -> Constraint +Apply :: (Type -> Type) -> Constraint +Applicative :: (Type -> Type) -> Constraint +Discard :: (Type -> Type) -> Constraint +Bind :: (Type -> Type) -> Constraint +Monad :: (Type -> Type) -> Constraint + +Classes +class forall (f :: Type -> Type). Functor (f :: Type -> Type) + map :: + forall (f :: Type -> Type) (a :: Type) (b :: Type). + Functor (f :: Type -> Type) => + ((a :: Type) -> (b :: Type)) -> + (f :: Type -> Type) (a :: Type) -> + (f :: Type -> Type) (b :: Type) +class forall (f :: Type -> Type). Functor (f :: Type -> Type) <= Apply (f :: Type -> Type) + apply :: + forall (f :: Type -> Type) (a :: Type) (b :: Type). + Apply (f :: Type -> Type) => + (f :: Type -> Type) ((a :: Type) -> (b :: Type)) -> + (f :: Type -> Type) (a :: Type) -> + (f :: Type -> Type) (b :: Type) +class forall (f :: Type -> Type). Apply (f :: Type -> Type) <= Applicative (f :: Type -> Type) + pure :: + forall (f :: Type -> Type) (a :: Type). + Applicative (f :: Type -> Type) => (a :: Type) -> (f :: Type -> Type) (a :: Type) +class forall (m :: Type -> Type). Applicative (m :: Type -> Type) <= Discard (m :: Type -> Type) + discard :: + forall (m :: Type -> Type) (a :: Type) (b :: Type). + Discard (m :: Type -> Type) => + (m :: Type -> Type) (a :: Type) -> + ((a :: Type) -> (m :: Type -> Type) (b :: Type)) -> + (m :: Type -> Type) (b :: Type) +class forall (m :: Type -> Type). Applicative (m :: Type -> Type) <= Bind (m :: Type -> Type) + bind :: + forall (m :: Type -> Type) (a :: Type) (b :: Type). + Bind (m :: Type -> Type) => + (m :: Type -> Type) (a :: Type) -> + ((a :: Type) -> (m :: Type -> Type) (b :: Type)) -> + (m :: Type -> Type) (b :: Type) +class forall (m :: Type -> Type). Bind (m :: Type -> Type) <= Monad (m :: Type -> Type) + +Roles +Tuple = [Representational, Representational] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(11), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/162_do_let_premature_solve/Main.purs b/tests-integration/fixtures/checking2/162_do_let_premature_solve/Main.purs new file mode 100644 index 00000000..2aa41d84 --- /dev/null +++ b/tests-integration/fixtures/checking2/162_do_let_premature_solve/Main.purs @@ -0,0 +1,28 @@ +module Main where + +foreign import data Effect :: Type -> Type +foreign import data Unit :: Type + +foreign import unit :: Unit +foreign import pure :: forall a. a -> Effect a +foreign import bind :: forall a b. Effect a -> (a -> Effect b) -> Effect b +foreign import discard :: forall a. Effect a -> Effect Unit -> Effect Unit + +class Semiring a where + add :: a -> a -> a + +instance Semiring Int where + add = addImpl + +foreign import addImpl :: Int -> Int -> Int + +infixl 6 add as + + +thing1 :: Effect String +thing1 = pure "hello" + +test :: Effect Unit +test = do + a <- thing1 + let f = a + 123 + pure unit diff --git a/tests-integration/fixtures/checking2/162_do_let_premature_solve/Main.snap b/tests-integration/fixtures/checking2/162_do_let_premature_solve/Main.snap new file mode 100644 index 00000000..e0881335 --- /dev/null +++ b/tests-integration/fixtures/checking2/162_do_let_premature_solve/Main.snap @@ -0,0 +1,67 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +unit :: Unit +pure :: forall (a :: Type). (a :: Type) -> Effect (a :: Type) +bind :: + forall (a :: Type) (b :: Type). + Effect (a :: Type) -> ((a :: Type) -> Effect (b :: Type)) -> Effect (b :: Type) +discard :: forall (a :: Type). Effect (a :: Type) -> Effect Unit -> Effect Unit +add :: forall (a :: Type). Semiring (a :: Type) => (a :: Type) -> (a :: Type) -> (a :: Type) +addImpl :: Int -> Int -> Int ++ :: forall (a :: Type). Semiring (a :: Type) => (a :: Type) -> (a :: Type) -> (a :: Type) +thing1 :: Effect String +test :: Effect Unit + +Types +Effect :: Type -> Type +Unit :: Type +Semiring :: Type -> Constraint + +Classes +class forall (a :: Type). Semiring (a :: Type) + add :: forall (a :: Type). Semiring (a :: Type) => (a :: Type) -> (a :: Type) -> (a :: Type) + +Instances +instance Semiring Int + +Roles +Effect = [Nominal] +Unit = [] + +Errors +CheckError { + kind: CannotUnify { + t1: Id(10), + t2: Id(11), + }, + crumbs: [ + TermDeclaration( + Idx::(9), + ), + CheckingExpression( + AstId(97), + ), + CheckingDoLet( + AstId(102), + ), + CheckingLetName( + Idx::(0), + ), + InferringExpression( + AstId(107), + ), + CheckingExpression( + AstId(111), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(12), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/163_do_let_annotation_solve/Main.purs b/tests-integration/fixtures/checking2/163_do_let_annotation_solve/Main.purs new file mode 100644 index 00000000..9ab3a657 --- /dev/null +++ b/tests-integration/fixtures/checking2/163_do_let_annotation_solve/Main.purs @@ -0,0 +1,20 @@ +module Main where + +foreign import data Effect :: Type -> Type +foreign import data Unit :: Type + +foreign import unit :: Unit +foreign import pure :: forall a. a -> Effect a +foreign import bind :: forall a b. Effect a -> (a -> Effect b) -> Effect b +foreign import discard :: forall a. Effect a -> Effect Unit -> Effect Unit + +thing1 :: Effect String +thing1 = pure "hello" + +test :: Effect Unit +test = do + a <- thing1 + let + f :: Int + f = a + pure unit diff --git a/tests-integration/fixtures/checking2/163_do_let_annotation_solve/Main.snap b/tests-integration/fixtures/checking2/163_do_let_annotation_solve/Main.snap new file mode 100644 index 00000000..d92ebba5 --- /dev/null +++ b/tests-integration/fixtures/checking2/163_do_let_annotation_solve/Main.snap @@ -0,0 +1,47 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +unit :: Unit +pure :: forall (a :: Type). (a :: Type) -> Effect (a :: Type) +bind :: + forall (a :: Type) (b :: Type). + Effect (a :: Type) -> ((a :: Type) -> Effect (b :: Type)) -> Effect (b :: Type) +discard :: forall (a :: Type). Effect (a :: Type) -> Effect Unit -> Effect Unit +thing1 :: Effect String +test :: Effect Unit + +Types +Effect :: Type -> Type +Unit :: Type + +Roles +Effect = [Nominal] +Unit = [] + +Errors +CheckError { + kind: CannotUnify { + t1: Id(8), + t2: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(5), + ), + CheckingExpression( + AstId(71), + ), + CheckingDoLet( + AstId(76), + ), + CheckingLetName( + Idx::(0), + ), + CheckingExpression( + AstId(83), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/164_ado_let_premature_solve/Main.purs b/tests-integration/fixtures/checking2/164_ado_let_premature_solve/Main.purs new file mode 100644 index 00000000..77f9722c --- /dev/null +++ b/tests-integration/fixtures/checking2/164_ado_let_premature_solve/Main.purs @@ -0,0 +1,28 @@ +module Main where + +foreign import data Effect :: Type -> Type +foreign import data Unit :: Type + +foreign import unit :: Unit +foreign import pure :: forall a. a -> Effect a +foreign import map :: forall a b. (a -> b) -> Effect a -> Effect b +foreign import apply :: forall a b. Effect (a -> b) -> Effect a -> Effect b + +class Semiring a where + add :: a -> a -> a + +instance Semiring Int where + add = addImpl + +foreign import addImpl :: Int -> Int -> Int + +infixl 6 add as + + +thing1 :: Effect String +thing1 = pure "hello" + +test :: Effect Unit +test = ado + a <- thing1 + let f = a + 123 + in unit diff --git a/tests-integration/fixtures/checking2/164_ado_let_premature_solve/Main.snap b/tests-integration/fixtures/checking2/164_ado_let_premature_solve/Main.snap new file mode 100644 index 00000000..0ec553bb --- /dev/null +++ b/tests-integration/fixtures/checking2/164_ado_let_premature_solve/Main.snap @@ -0,0 +1,69 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +unit :: Unit +pure :: forall (a :: Type). (a :: Type) -> Effect (a :: Type) +map :: + forall (a :: Type) (b :: Type). + ((a :: Type) -> (b :: Type)) -> Effect (a :: Type) -> Effect (b :: Type) +apply :: + forall (a :: Type) (b :: Type). + Effect ((a :: Type) -> (b :: Type)) -> Effect (a :: Type) -> Effect (b :: Type) +add :: forall (a :: Type). Semiring (a :: Type) => (a :: Type) -> (a :: Type) -> (a :: Type) +addImpl :: Int -> Int -> Int ++ :: forall (a :: Type). Semiring (a :: Type) => (a :: Type) -> (a :: Type) -> (a :: Type) +thing1 :: Effect String +test :: Effect Unit + +Types +Effect :: Type -> Type +Unit :: Type +Semiring :: Type -> Constraint + +Classes +class forall (a :: Type). Semiring (a :: Type) + add :: forall (a :: Type). Semiring (a :: Type) => (a :: Type) -> (a :: Type) -> (a :: Type) + +Instances +instance Semiring Int + +Roles +Effect = [Nominal] +Unit = [] + +Errors +CheckError { + kind: CannotUnify { + t1: Id(10), + t2: Id(11), + }, + crumbs: [ + TermDeclaration( + Idx::(9), + ), + CheckingExpression( + AstId(99), + ), + CheckingAdoLet( + AstId(104), + ), + CheckingLetName( + Idx::(0), + ), + InferringExpression( + AstId(109), + ), + CheckingExpression( + AstId(113), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(12), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/165_ado_let_annotation_solve/Main.purs b/tests-integration/fixtures/checking2/165_ado_let_annotation_solve/Main.purs new file mode 100644 index 00000000..0728bc90 --- /dev/null +++ b/tests-integration/fixtures/checking2/165_ado_let_annotation_solve/Main.purs @@ -0,0 +1,20 @@ +module Main where + +foreign import data Effect :: Type -> Type +foreign import data Unit :: Type + +foreign import unit :: Unit +foreign import pure :: forall a. a -> Effect a +foreign import map :: forall a b. (a -> b) -> Effect a -> Effect b +foreign import apply :: forall a b. Effect (a -> b) -> Effect a -> Effect b + +thing1 :: Effect String +thing1 = pure "hello" + +test :: Effect Unit +test = ado + a <- thing1 + let + f :: Int + f = a + in unit diff --git a/tests-integration/fixtures/checking2/165_ado_let_annotation_solve/Main.snap b/tests-integration/fixtures/checking2/165_ado_let_annotation_solve/Main.snap new file mode 100644 index 00000000..858c0f20 --- /dev/null +++ b/tests-integration/fixtures/checking2/165_ado_let_annotation_solve/Main.snap @@ -0,0 +1,49 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +unit :: Unit +pure :: forall (a :: Type). (a :: Type) -> Effect (a :: Type) +map :: + forall (a :: Type) (b :: Type). + ((a :: Type) -> (b :: Type)) -> Effect (a :: Type) -> Effect (b :: Type) +apply :: + forall (a :: Type) (b :: Type). + Effect ((a :: Type) -> (b :: Type)) -> Effect (a :: Type) -> Effect (b :: Type) +thing1 :: Effect String +test :: Effect Unit + +Types +Effect :: Type -> Type +Unit :: Type + +Roles +Effect = [Nominal] +Unit = [] + +Errors +CheckError { + kind: CannotUnify { + t1: Id(8), + t2: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(5), + ), + CheckingExpression( + AstId(73), + ), + CheckingAdoLet( + AstId(78), + ), + CheckingLetName( + Idx::(0), + ), + CheckingExpression( + AstId(85), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/166_do_bind_only/Main.purs b/tests-integration/fixtures/checking2/166_do_bind_only/Main.purs new file mode 100644 index 00000000..2e40cc8e --- /dev/null +++ b/tests-integration/fixtures/checking2/166_do_bind_only/Main.purs @@ -0,0 +1,14 @@ +module Main where + +import Control.Applicative (pure) +import Control.Bind (bind) +import Effect (Effect) + +test :: Effect Int +test = do + x <- pure 1 + pure x + +test' = do + x <- pure 1 + pure x diff --git a/tests-integration/fixtures/checking2/166_do_bind_only/Main.snap b/tests-integration/fixtures/checking2/166_do_bind_only/Main.snap new file mode 100644 index 00000000..3b5b70ae --- /dev/null +++ b/tests-integration/fixtures/checking2/166_do_bind_only/Main.snap @@ -0,0 +1,12 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: Effect Int +test' :: + forall (t0 :: Type -> Type). + Bind (t0 :: Type -> Type) => Applicative (t0 :: Type -> Type) => (t0 :: Type -> Type) Int + +Types diff --git a/tests-integration/fixtures/checking2/167_do_final_bind/Main.purs b/tests-integration/fixtures/checking2/167_do_final_bind/Main.purs new file mode 100644 index 00000000..9c3019d6 --- /dev/null +++ b/tests-integration/fixtures/checking2/167_do_final_bind/Main.purs @@ -0,0 +1,12 @@ +module Main where + +import Control.Applicative (pure) +import Control.Bind (bind) +import Effect (Effect) + +test :: Effect Int +test = do + x <- pure 1 + +test' = do + x <- pure 1 diff --git a/tests-integration/fixtures/checking2/167_do_final_bind/Main.snap b/tests-integration/fixtures/checking2/167_do_final_bind/Main.snap new file mode 100644 index 00000000..78333dd2 --- /dev/null +++ b/tests-integration/fixtures/checking2/167_do_final_bind/Main.snap @@ -0,0 +1,40 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: Effect Int +test' :: forall (t0 :: Type -> Type). Applicative (t0 :: Type -> Type) => (t0 :: Type -> Type) Int + +Types + +Errors +CheckError { + kind: InvalidFinalBind, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + CheckingExpression( + AstId(27), + ), + InferringDoBind( + AstId(29), + ), + ], +} +CheckError { + kind: InvalidFinalBind, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + InferringExpression( + AstId(38), + ), + InferringDoBind( + AstId(40), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/168_do_final_let/Main.purs b/tests-integration/fixtures/checking2/168_do_final_let/Main.purs new file mode 100644 index 00000000..bfcd874e --- /dev/null +++ b/tests-integration/fixtures/checking2/168_do_final_let/Main.purs @@ -0,0 +1,10 @@ +module Main where + +import Effect (Effect) + +test :: Effect Int +test = do + let x = 1 + +test' = do + let x = 1 diff --git a/tests-integration/fixtures/checking2/168_do_final_let/Main.snap b/tests-integration/fixtures/checking2/168_do_final_let/Main.snap new file mode 100644 index 00000000..a18e85e1 --- /dev/null +++ b/tests-integration/fixtures/checking2/168_do_final_let/Main.snap @@ -0,0 +1,65 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: Effect Int +test' :: ?[invalid final let] + +Types + +Errors +CheckError { + kind: InvalidFinalLet, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + CheckingExpression( + AstId(17), + ), + CheckingDoLet( + AstId(19), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(11), + t2: Id(12), + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + CheckingExpression( + AstId(17), + ), + ], +} +CheckError { + kind: InvalidFinalLet, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + InferringExpression( + AstId(28), + ), + CheckingDoLet( + AstId(30), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(13), + t2: Id(14), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/169_exhaustive_multiple/Main.purs b/tests-integration/fixtures/checking2/169_exhaustive_multiple/Main.purs new file mode 100644 index 00000000..bfe45529 --- /dev/null +++ b/tests-integration/fixtures/checking2/169_exhaustive_multiple/Main.purs @@ -0,0 +1,29 @@ +module Main where + +data Maybe a = Just a | Nothing + +complete = case _, _ of + Just _, Just _ -> 1 + Just _, Nothing -> 2 + Nothing, Just _ -> 3 + Nothing, Nothing -> 4 + +incomplete1 = case _, _ of + Just _, Nothing -> 2 + Nothing, Just _ -> 3 + Nothing, Nothing -> 4 + +incomplete2 = case _, _ of + Just _, Just _ -> 1 + Nothing, Just _ -> 3 + Nothing, Nothing -> 4 + +incomplete3 = case _, _ of + Just _, Just _ -> 1 + Just _, Nothing -> 2 + Nothing, Nothing -> 4 + +incomplete4 = case _, _ of + Just _, Just _ -> 1 + Just _, Nothing -> 2 + Nothing, Just _ -> 3 diff --git a/tests-integration/fixtures/checking2/169_exhaustive_multiple/Main.snap b/tests-integration/fixtures/checking2/169_exhaustive_multiple/Main.snap new file mode 100644 index 00000000..c87c579c --- /dev/null +++ b/tests-integration/fixtures/checking2/169_exhaustive_multiple/Main.snap @@ -0,0 +1,85 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +complete :: forall (t2 :: Type) (t1 :: Type). Maybe (t2 :: Type) -> Maybe (t1 :: Type) -> Int +incomplete1 :: + forall (t4 :: Type) (t3 :: Type). Partial => Maybe (t4 :: Type) -> Maybe (t3 :: Type) -> Int +incomplete2 :: + forall (t6 :: Type) (t5 :: Type). Partial => Maybe (t6 :: Type) -> Maybe (t5 :: Type) -> Int +incomplete3 :: + forall (t8 :: Type) (t7 :: Type). Partial => Maybe (t8 :: Type) -> Maybe (t7 :: Type) -> Int +incomplete4 :: + forall (t10 :: Type) (t9 :: Type). Partial => Maybe (t10 :: Type) -> Maybe (t9 :: Type) -> Int + +Types +Maybe :: Type -> Type + +Roles +Maybe = [Representational] + +Errors +CheckError { + kind: MissingPatterns { + patterns: [ + Id(7), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + InferringExpression( + AstId(53), + ), + ], +} +CheckError { + kind: MissingPatterns { + patterns: [ + Id(8), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(4), + ), + InferringExpression( + AstId(84), + ), + ], +} +CheckError { + kind: MissingPatterns { + patterns: [ + Id(9), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(5), + ), + InferringExpression( + AstId(116), + ), + ], +} +CheckError { + kind: MissingPatterns { + patterns: [ + Id(10), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(6), + ), + InferringExpression( + AstId(148), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/170_exhaustive_tuple/Main.purs b/tests-integration/fixtures/checking2/170_exhaustive_tuple/Main.purs new file mode 100644 index 00000000..9e80fa60 --- /dev/null +++ b/tests-integration/fixtures/checking2/170_exhaustive_tuple/Main.purs @@ -0,0 +1,31 @@ +module Main where + +data Tuple a b = Tuple a b + +data Maybe a = Just a | Nothing + +complete = case _ of + Tuple (Just _) (Just _) -> 1 + Tuple (Just _) Nothing -> 2 + Tuple Nothing (Just _) -> 3 + Tuple Nothing Nothing -> 4 + +incomplete1 = case _ of + Tuple (Just _) Nothing -> 2 + Tuple Nothing (Just _) -> 3 + Tuple Nothing Nothing -> 4 + +incomplete2 = case _ of + Tuple (Just _) (Just _) -> 1 + Tuple Nothing (Just _) -> 3 + Tuple Nothing Nothing -> 4 + +incomplete3 = case _ of + Tuple (Just _) (Just _) -> 1 + Tuple (Just _) Nothing -> 2 + Tuple Nothing Nothing -> 4 + +incomplete4 = case _ of + Tuple (Just _) (Just _) -> 1 + Tuple (Just _) Nothing -> 2 + Tuple Nothing (Just _) -> 3 diff --git a/tests-integration/fixtures/checking2/170_exhaustive_tuple/Main.snap b/tests-integration/fixtures/checking2/170_exhaustive_tuple/Main.snap new file mode 100644 index 00000000..17629e0c --- /dev/null +++ b/tests-integration/fixtures/checking2/170_exhaustive_tuple/Main.snap @@ -0,0 +1,92 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Tuple :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> Tuple (a :: Type) (b :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +complete :: forall (t4 :: Type) (t3 :: Type). Tuple (Maybe (t4 :: Type)) (Maybe (t3 :: Type)) -> Int +incomplete1 :: + forall (t6 :: Type) (t5 :: Type). + Partial => Tuple (Maybe (t6 :: Type)) (Maybe (t5 :: Type)) -> Int +incomplete2 :: + forall (t8 :: Type) (t7 :: Type). + Partial => Tuple (Maybe (t8 :: Type)) (Maybe (t7 :: Type)) -> Int +incomplete3 :: + forall (t10 :: Type) (t9 :: Type). + Partial => Tuple (Maybe (t10 :: Type)) (Maybe (t9 :: Type)) -> Int +incomplete4 :: + forall (t12 :: Type) (t11 :: Type). + Partial => Tuple (Maybe (t12 :: Type)) (Maybe (t11 :: Type)) -> Int + +Types +Tuple :: Type -> Type -> Type +Maybe :: Type -> Type + +Roles +Tuple = [Representational, Representational] +Maybe = [Representational] + +Errors +CheckError { + kind: MissingPatterns { + patterns: [ + Id(8), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(4), + ), + InferringExpression( + AstId(66), + ), + ], +} +CheckError { + kind: MissingPatterns { + patterns: [ + Id(9), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(5), + ), + InferringExpression( + AstId(101), + ), + ], +} +CheckError { + kind: MissingPatterns { + patterns: [ + Id(10), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(6), + ), + InferringExpression( + AstId(138), + ), + ], +} +CheckError { + kind: MissingPatterns { + patterns: [ + Id(11), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(7), + ), + InferringExpression( + AstId(175), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/171_exhaustive_equation_redundant/Main.purs b/tests-integration/fixtures/checking2/171_exhaustive_equation_redundant/Main.purs new file mode 100644 index 00000000..943cbf1d --- /dev/null +++ b/tests-integration/fixtures/checking2/171_exhaustive_equation_redundant/Main.purs @@ -0,0 +1,25 @@ +module Main where + +data Unit = Unit + +unit :: Unit -> Int +unit Unit = 1 +unit _ = 2 +unit Unit = 3 + +data YesNo = Yes | No + +yes :: YesNo -> Int +yes Yes = 1 +yes _ = 2 +yes Yes = 3 + +no :: YesNo -> Int +no Yes = 1 +no _ = 2 +no No = 3 + +yesNo :: YesNo -> Int +yesNo Yes = 1 +yesNo No = 2 +yesNo _ = 3 diff --git a/tests-integration/fixtures/checking2/171_exhaustive_equation_redundant/Main.snap b/tests-integration/fixtures/checking2/171_exhaustive_equation_redundant/Main.snap new file mode 100644 index 00000000..5922962d --- /dev/null +++ b/tests-integration/fixtures/checking2/171_exhaustive_equation_redundant/Main.snap @@ -0,0 +1,72 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Unit :: Unit +unit :: Unit -> Int +Yes :: YesNo +No :: YesNo +yes :: YesNo -> Int +no :: YesNo -> Int +yesNo :: YesNo -> Int + +Types +Unit :: Type +YesNo :: Type + +Roles +Unit = [] +YesNo = [] + +Errors +CheckError { + kind: RedundantPatterns { + patterns: [ + "_", + "Unit", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: RedundantPatterns { + patterns: [ + "Yes", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(4), + ), + ], +} +CheckError { + kind: RedundantPatterns { + patterns: [ + "No", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(5), + ), + ], +} +CheckError { + kind: RedundantPatterns { + patterns: [ + "_", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(6), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/172_exhaustive_equation_guarded/Main.purs b/tests-integration/fixtures/checking2/172_exhaustive_equation_guarded/Main.purs new file mode 100644 index 00000000..0bfc36ba --- /dev/null +++ b/tests-integration/fixtures/checking2/172_exhaustive_equation_guarded/Main.purs @@ -0,0 +1,9 @@ +module Main where + +testGuarded :: Boolean -> Int +testGuarded true | false = 1 +testGuarded false = 0 + +testGuardedBoth :: Boolean -> Int +testGuardedBoth true | true = 1 +testGuardedBoth false | true = 0 diff --git a/tests-integration/fixtures/checking2/172_exhaustive_equation_guarded/Main.snap b/tests-integration/fixtures/checking2/172_exhaustive_equation_guarded/Main.snap new file mode 100644 index 00000000..da3490a7 --- /dev/null +++ b/tests-integration/fixtures/checking2/172_exhaustive_equation_guarded/Main.snap @@ -0,0 +1,24 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +testGuarded :: Boolean -> Int +testGuardedBoth :: Boolean -> Int + +Types + +Errors +CheckError { + kind: MissingPatterns { + patterns: [ + Id(4), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/173_exhaustive_let_equation/Main.purs b/tests-integration/fixtures/checking2/173_exhaustive_let_equation/Main.purs new file mode 100644 index 00000000..c8b08014 --- /dev/null +++ b/tests-integration/fixtures/checking2/173_exhaustive_let_equation/Main.purs @@ -0,0 +1,17 @@ +module Main where + +data Maybe a = Just a | Nothing + +test :: Int +test = + let + f :: Maybe Int -> Int + f (Just _) = 1 + in f Nothing + +test2 :: Int +test2 = + let + g :: Maybe Int -> Int + g Nothing = 1 + in g (Just 42) diff --git a/tests-integration/fixtures/checking2/173_exhaustive_let_equation/Main.snap b/tests-integration/fixtures/checking2/173_exhaustive_let_equation/Main.snap new file mode 100644 index 00000000..00a7b33b --- /dev/null +++ b/tests-integration/fixtures/checking2/173_exhaustive_let_equation/Main.snap @@ -0,0 +1,54 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +test :: Int +test2 :: Int + +Types +Maybe :: Type -> Type + +Roles +Maybe = [Representational] + +Errors +CheckError { + kind: MissingPatterns { + patterns: [ + Id(7), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(2), + ), + CheckingExpression( + AstId(15), + ), + CheckingLetName( + Idx::(0), + ), + ], +} +CheckError { + kind: MissingPatterns { + patterns: [ + Id(8), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + CheckingExpression( + AstId(40), + ), + CheckingLetName( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/174_exhaustive_instance_equation/Main.purs b/tests-integration/fixtures/checking2/174_exhaustive_instance_equation/Main.purs new file mode 100644 index 00000000..8527ab12 --- /dev/null +++ b/tests-integration/fixtures/checking2/174_exhaustive_instance_equation/Main.purs @@ -0,0 +1,7 @@ +module Main where + +class C a where + c :: a -> Int + +instance cBool :: C Boolean where + c true = 1 diff --git a/tests-integration/fixtures/checking2/174_exhaustive_instance_equation/Main.snap b/tests-integration/fixtures/checking2/174_exhaustive_instance_equation/Main.snap new file mode 100644 index 00000000..df261558 --- /dev/null +++ b/tests-integration/fixtures/checking2/174_exhaustive_instance_equation/Main.snap @@ -0,0 +1,31 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +c :: forall (a :: Type). C (a :: Type) => (a :: Type) -> Int + +Types +C :: Type -> Constraint + +Classes +class forall (a :: Type). C (a :: Type) + c :: forall (a :: Type). C (a :: Type) => (a :: Type) -> Int + +Instances +instance C Boolean + +Errors +CheckError { + kind: MissingPatterns { + patterns: [ + Id(7), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/175_record_constructor_exhaustive/Main.purs b/tests-integration/fixtures/checking2/175_record_constructor_exhaustive/Main.purs new file mode 100644 index 00000000..1d48a7d0 --- /dev/null +++ b/tests-integration/fixtures/checking2/175_record_constructor_exhaustive/Main.purs @@ -0,0 +1,19 @@ +module Main where + +data Maybe a = Just a | Nothing + +test1 :: { x :: Int, y :: Int } -> Int +test1 { x, y } = x + +test2 :: { x :: Maybe Int } -> Int +test2 { x: Just n } = n + +test3 :: { x :: Int, y :: Int } -> Int +test3 { x, y } = x +test3 r = 0 + +test4 :: { a :: Maybe Int, b :: Maybe String } -> Int +test4 { a: Just n, b: Just _ } = n + +test5 :: { inner :: { x :: Int } } -> Int +test5 { inner: { x } } = x diff --git a/tests-integration/fixtures/checking2/175_record_constructor_exhaustive/Main.snap b/tests-integration/fixtures/checking2/175_record_constructor_exhaustive/Main.snap new file mode 100644 index 00000000..c9b54cbc --- /dev/null +++ b/tests-integration/fixtures/checking2/175_record_constructor_exhaustive/Main.snap @@ -0,0 +1,57 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +test1 :: { x :: Int, y :: Int } -> Int +test2 :: { x :: Maybe Int } -> Int +test3 :: { x :: Int, y :: Int } -> Int +test4 :: { a :: Maybe Int, b :: Maybe String } -> Int +test5 :: { inner :: { x :: Int } } -> Int + +Types +Maybe :: Type -> Type + +Roles +Maybe = [Representational] + +Errors +CheckError { + kind: MissingPatterns { + patterns: [ + Id(7), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(3), + ), + ], +} +CheckError { + kind: RedundantPatterns { + patterns: [ + "_", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(4), + ), + ], +} +CheckError { + kind: MissingPatterns { + patterns: [ + Id(8), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(5), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/176_array_exhaustive/Main.purs b/tests-integration/fixtures/checking2/176_array_exhaustive/Main.purs new file mode 100644 index 00000000..1a4854ae --- /dev/null +++ b/tests-integration/fixtures/checking2/176_array_exhaustive/Main.purs @@ -0,0 +1,15 @@ +module Main where + +testNonExhaustive :: Array Int -> Int +testNonExhaustive [] = 0 +testNonExhaustive [x] = x + +testRedundant :: Array Int -> Int +testRedundant [x] = x +testRedundant [y] = y +testRedundant _ = 0 + +testExhaustive :: Array Int -> Int +testExhaustive [] = 0 +testExhaustive [x] = x +testExhaustive _ = 0 diff --git a/tests-integration/fixtures/checking2/176_array_exhaustive/Main.snap b/tests-integration/fixtures/checking2/176_array_exhaustive/Main.snap new file mode 100644 index 00000000..213f75a7 --- /dev/null +++ b/tests-integration/fixtures/checking2/176_array_exhaustive/Main.snap @@ -0,0 +1,37 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +testNonExhaustive :: Array Int -> Int +testRedundant :: Array Int -> Int +testExhaustive :: Array Int -> Int + +Types + +Errors +CheckError { + kind: MissingPatterns { + patterns: [ + Id(5), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + ], +} +CheckError { + kind: RedundantPatterns { + patterns: [ + "[_]", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/177_exhaustive_guards_otherwise_true/Main.purs b/tests-integration/fixtures/checking2/177_exhaustive_guards_otherwise_true/Main.purs new file mode 100644 index 00000000..c8c99ad5 --- /dev/null +++ b/tests-integration/fixtures/checking2/177_exhaustive_guards_otherwise_true/Main.purs @@ -0,0 +1,27 @@ +module Main where + +import Data.Boolean (otherwise) + +foreign import lessThan :: Int -> Int -> Boolean + +test :: Int -> Int +test x = case x of + n + | lessThan n 0 -> 0 + | otherwise -> n + +test' x = case x of + n + | lessThan n 0 -> 0 + | otherwise -> n + +test2 :: Int -> Int +test2 x = case x of + n + | lessThan n 0 -> 0 + | true -> n + +test2' x = case x of + n + | lessThan n 0 -> 0 + | true -> n diff --git a/tests-integration/fixtures/checking2/177_exhaustive_guards_otherwise_true/Main.snap b/tests-integration/fixtures/checking2/177_exhaustive_guards_otherwise_true/Main.snap new file mode 100644 index 00000000..dffef2be --- /dev/null +++ b/tests-integration/fixtures/checking2/177_exhaustive_guards_otherwise_true/Main.snap @@ -0,0 +1,13 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +lessThan :: Int -> Int -> Boolean +test :: Int -> Int +test' :: Int -> Int +test2 :: Int -> Int +test2' :: Int -> Int + +Types diff --git a/tests-integration/fixtures/checking2/178_application_infix_chain/Main.purs b/tests-integration/fixtures/checking2/178_application_infix_chain/Main.purs new file mode 100644 index 00000000..b8d16da5 --- /dev/null +++ b/tests-integration/fixtures/checking2/178_application_infix_chain/Main.purs @@ -0,0 +1,57 @@ +module Main where + +add :: Int -> Int -> Int +add x y = x + +sub :: Int -> Int -> Int +sub x y = x + +mul :: Int -> Int -> Int +mul x y = x + +test1 :: Int +test1 = 1 `add` 2 + +test1' = 1 `add` 2 + +test2 :: Int +test2 = 1 `add` 2 `sub` 3 + +test2' = 1 `add` 2 `sub` 3 + +test3 :: Int +test3 = 1 `add` 2 `sub` 3 `mul` 4 + +test3' = 1 `add` 2 `sub` 3 `mul` 4 + +const :: forall a b. a -> b -> a +const x y = x + +test4 :: Int +test4 = 1 `const` "hello" + +test4' = 1 `const` "hello" + +test5 :: Int +test5 = 1 `const` "hello" `add` 2 + +test5' = 1 `const` "hello" `add` 2 + +apply :: forall a b. (a -> b) -> a -> b +apply f x = f x + +chain :: forall a b c. (b -> c) -> (a -> b) -> a -> c +chain g f x = g (f x) + +test6 :: Int +test6 = (\x -> x) `chain` (\x -> x) `apply` 1 + +test6' = (\x -> x) `chain` (\x -> x) `apply` 1 + +curry :: forall a b c. (a -> b -> c) -> a -> b -> c +curry f x y = f x y + +test7 :: Int -> Int +test7 = add `curry` 1 + +test7' = add `curry` 1 diff --git a/tests-integration/fixtures/checking2/178_application_infix_chain/Main.snap b/tests-integration/fixtures/checking2/178_application_infix_chain/Main.snap new file mode 100644 index 00000000..e1875764 --- /dev/null +++ b/tests-integration/fixtures/checking2/178_application_infix_chain/Main.snap @@ -0,0 +1,33 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +add :: Int -> Int -> Int +sub :: Int -> Int -> Int +mul :: Int -> Int -> Int +test1 :: Int +test1' :: Int +test2 :: Int +test2' :: Int +test3 :: Int +test3' :: Int +const :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) -> (a :: Type) +test4 :: Int +test4' :: Int +test5 :: Int +test5' :: Int +apply :: forall (a :: Type) (b :: Type). ((a :: Type) -> (b :: Type)) -> (a :: Type) -> (b :: Type) +chain :: + forall (a :: Type) (b :: Type) (c :: Type). + ((b :: Type) -> (c :: Type)) -> ((a :: Type) -> (b :: Type)) -> (a :: Type) -> (c :: Type) +test6 :: Int +test6' :: Int +curry :: + forall (a :: Type) (b :: Type) (c :: Type). + ((a :: Type) -> (b :: Type) -> (c :: Type)) -> (a :: Type) -> (b :: Type) -> (c :: Type) +test7 :: Int -> Int +test7' :: Int -> Int + +Types diff --git a/tests-integration/fixtures/checking2/179_application_function_subtype/Main.purs b/tests-integration/fixtures/checking2/179_application_function_subtype/Main.purs new file mode 100644 index 00000000..a7750080 --- /dev/null +++ b/tests-integration/fixtures/checking2/179_application_function_subtype/Main.purs @@ -0,0 +1,14 @@ +module Main where + +class Category :: forall k. (k -> k -> Type) -> Constraint +class Category a where + identity :: forall t. a t t + +instance categoryFn :: Category (->) where + identity x = x + +test :: (->) Int Int +test = identity + +test' :: Int -> Int +test' = identity diff --git a/tests-integration/fixtures/checking2/179_application_function_subtype/Main.snap b/tests-integration/fixtures/checking2/179_application_function_subtype/Main.snap new file mode 100644 index 00000000..899c6ea1 --- /dev/null +++ b/tests-integration/fixtures/checking2/179_application_function_subtype/Main.snap @@ -0,0 +1,25 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +identity :: + forall (k :: Type) (a :: (k :: Type) -> (k :: Type) -> Type) (t :: (k :: Type)). + Category @(k :: Type) (a :: (k :: Type) -> (k :: Type) -> Type) => + (a :: (k :: Type) -> (k :: Type) -> Type) (t :: (k :: Type)) (t :: (k :: Type)) +test :: Function Int Int +test' :: Int -> Int + +Types +Category :: forall (k :: Type). ((k :: Type) -> (k :: Type) -> Type) -> Constraint + +Classes +class forall (k :: Type) (a :: (k :: Type) -> (k :: Type) -> Type). Category @(k :: Type) (a :: (k :: Type) -> (k :: Type) -> Type) + identity :: + forall (k :: Type) (a :: (k :: Type) -> (k :: Type) -> Type) (t :: (k :: Type)). + Category @(k :: Type) (a :: (k :: Type) -> (k :: Type) -> Type) => + (a :: (k :: Type) -> (k :: Type) -> Type) (t :: (k :: Type)) (t :: (k :: Type)) + +Instances +instance Category @Type Function diff --git a/tests-integration/fixtures/checking2/180_application_function_decomposition/Main.purs b/tests-integration/fixtures/checking2/180_application_function_decomposition/Main.purs new file mode 100644 index 00000000..10c45f83 --- /dev/null +++ b/tests-integration/fixtures/checking2/180_application_function_decomposition/Main.purs @@ -0,0 +1,23 @@ +module Main where + +import Data.Semigroupoid ((<<<)) + +data Maybe a = Just a | Nothing + +class Foldable f where + foldMap :: forall a m. (a -> m) -> f a -> m + +class Foldable f <= FoldableWithIndex i f where + foldMapWithIndex :: forall a m. (i -> a -> m) -> f a -> m + foldlWithIndex :: forall a b. (i -> b -> a -> b) -> b -> f a -> b + foldrWithIndex :: forall a b. (i -> a -> b -> b) -> b -> f a -> b + +data NonEmpty f a = NonEmpty a (f a) + +instance Foldable f => Foldable (NonEmpty f) where + foldMap f (NonEmpty a fa) = f a + +instance FoldableWithIndex i f => FoldableWithIndex (Maybe i) (NonEmpty f) where + foldMapWithIndex f (NonEmpty a fa) = f Nothing a + foldlWithIndex f b (NonEmpty a fa) = foldlWithIndex (f <<< Just) (f Nothing b a) fa + foldrWithIndex f b (NonEmpty a fa) = f Nothing a (foldrWithIndex (f <<< Just) b fa) diff --git a/tests-integration/fixtures/checking2/180_application_function_decomposition/Main.snap b/tests-integration/fixtures/checking2/180_application_function_decomposition/Main.snap new file mode 100644 index 00000000..02457097 --- /dev/null +++ b/tests-integration/fixtures/checking2/180_application_function_decomposition/Main.snap @@ -0,0 +1,76 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +foldMap :: + forall (f :: Type -> Type) (a :: Type) (m :: Type). + Foldable (f :: Type -> Type) => + ((a :: Type) -> (m :: Type)) -> (f :: Type -> Type) (a :: Type) -> (m :: Type) +foldMapWithIndex :: + forall (i :: Type) (f :: Type -> Type) (a :: Type) (m :: Type). + FoldableWithIndex (i :: Type) (f :: Type -> Type) => + ((i :: Type) -> (a :: Type) -> (m :: Type)) -> (f :: Type -> Type) (a :: Type) -> (m :: Type) +foldlWithIndex :: + forall (i :: Type) (f :: Type -> Type) (a :: Type) (b :: Type). + FoldableWithIndex (i :: Type) (f :: Type -> Type) => + ((i :: Type) -> (b :: Type) -> (a :: Type) -> (b :: Type)) -> + (b :: Type) -> + (f :: Type -> Type) (a :: Type) -> + (b :: Type) +foldrWithIndex :: + forall (i :: Type) (f :: Type -> Type) (a :: Type) (b :: Type). + FoldableWithIndex (i :: Type) (f :: Type -> Type) => + ((i :: Type) -> (a :: Type) -> (b :: Type) -> (b :: Type)) -> + (b :: Type) -> + (f :: Type -> Type) (a :: Type) -> + (b :: Type) +NonEmpty :: + forall (f :: Type -> Type) (a :: Type). + (a :: Type) -> (f :: Type -> Type) (a :: Type) -> NonEmpty (f :: Type -> Type) (a :: Type) + +Types +Maybe :: Type -> Type +Foldable :: (Type -> Type) -> Constraint +FoldableWithIndex :: Type -> (Type -> Type) -> Constraint +NonEmpty :: (Type -> Type) -> Type -> Type + +Classes +class forall (f :: Type -> Type). Foldable (f :: Type -> Type) + foldMap :: + forall (f :: Type -> Type) (a :: Type) (m :: Type). + Foldable (f :: Type -> Type) => + ((a :: Type) -> (m :: Type)) -> (f :: Type -> Type) (a :: Type) -> (m :: Type) +class forall (i :: Type) (f :: Type -> Type). Foldable (f :: Type -> Type) <= FoldableWithIndex (i :: Type) (f :: Type -> Type) + foldMapWithIndex :: + forall (i :: Type) (f :: Type -> Type) (a :: Type) (m :: Type). + FoldableWithIndex (i :: Type) (f :: Type -> Type) => + ((i :: Type) -> (a :: Type) -> (m :: Type)) -> (f :: Type -> Type) (a :: Type) -> (m :: Type) + foldlWithIndex :: + forall (i :: Type) (f :: Type -> Type) (a :: Type) (b :: Type). + FoldableWithIndex (i :: Type) (f :: Type -> Type) => + ((i :: Type) -> (b :: Type) -> (a :: Type) -> (b :: Type)) -> + (b :: Type) -> + (f :: Type -> Type) (a :: Type) -> + (b :: Type) + foldrWithIndex :: + forall (i :: Type) (f :: Type -> Type) (a :: Type) (b :: Type). + FoldableWithIndex (i :: Type) (f :: Type -> Type) => + ((i :: Type) -> (a :: Type) -> (b :: Type) -> (b :: Type)) -> + (b :: Type) -> + (f :: Type -> Type) (a :: Type) -> + (b :: Type) + +Instances +instance forall (t14 :: Type -> Type). + Foldable (t14 :: Type -> Type) => Foldable (NonEmpty (t14 :: Type -> Type)) +instance forall (t15 :: Type) (t16 :: Type -> Type). + FoldableWithIndex (t15 :: Type) (t16 :: Type -> Type) => + FoldableWithIndex (Maybe (t15 :: Type)) (NonEmpty (t16 :: Type -> Type)) + +Roles +Maybe = [Representational] +NonEmpty = [Representational, Nominal] diff --git a/tests-integration/fixtures/checking2/181_type_application_invalid_basic/Main.purs b/tests-integration/fixtures/checking2/181_type_application_invalid_basic/Main.purs new file mode 100644 index 00000000..56548ca0 --- /dev/null +++ b/tests-integration/fixtures/checking2/181_type_application_invalid_basic/Main.purs @@ -0,0 +1,3 @@ +module Main where + +type Bad = Int String diff --git a/tests-integration/fixtures/checking2/181_type_application_invalid_basic/Main.snap b/tests-integration/fixtures/checking2/181_type_application_invalid_basic/Main.snap new file mode 100644 index 00000000..9e9ab968 --- /dev/null +++ b/tests-integration/fixtures/checking2/181_type_application_invalid_basic/Main.snap @@ -0,0 +1,40 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +Bad :: ?[cannot apply function type] + +Synonyms +type Bad = Int String + +Errors +CheckError { + kind: InvalidTypeApplication { + function_type: Id(3), + function_kind: Id(4), + argument_type: Id(5), + }, + crumbs: [ + CheckingKind( + AstId(6), + ), + InferringKind( + AstId(6), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(4), + t2: Id(7), + }, + crumbs: [ + CheckingKind( + AstId(6), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/182_type_application_invalid_too_many/Main.purs b/tests-integration/fixtures/checking2/182_type_application_invalid_too_many/Main.purs new file mode 100644 index 00000000..5929643f --- /dev/null +++ b/tests-integration/fixtures/checking2/182_type_application_invalid_too_many/Main.purs @@ -0,0 +1,3 @@ +module Main where + +type Bad = Array Int String diff --git a/tests-integration/fixtures/checking2/182_type_application_invalid_too_many/Main.snap b/tests-integration/fixtures/checking2/182_type_application_invalid_too_many/Main.snap new file mode 100644 index 00000000..ad93cbd2 --- /dev/null +++ b/tests-integration/fixtures/checking2/182_type_application_invalid_too_many/Main.snap @@ -0,0 +1,40 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +Bad :: ?[cannot apply function type] + +Synonyms +type Bad = Array Int String + +Errors +CheckError { + kind: InvalidTypeApplication { + function_type: Id(3), + function_kind: Id(4), + argument_type: Id(5), + }, + crumbs: [ + CheckingKind( + AstId(6), + ), + InferringKind( + AstId(6), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(4), + t2: Id(7), + }, + crumbs: [ + CheckingKind( + AstId(6), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/183_record_expression_exact/Main.purs b/tests-integration/fixtures/checking2/183_record_expression_exact/Main.purs new file mode 100644 index 00000000..80032018 --- /dev/null +++ b/tests-integration/fixtures/checking2/183_record_expression_exact/Main.purs @@ -0,0 +1,4 @@ +module Main where + +test :: { a :: Int, b :: Int } +test = { a: 1, b: 2 } diff --git a/tests-integration/fixtures/checking2/183_record_expression_exact/Main.snap b/tests-integration/fixtures/checking2/183_record_expression_exact/Main.snap new file mode 100644 index 00000000..bf1a7efa --- /dev/null +++ b/tests-integration/fixtures/checking2/183_record_expression_exact/Main.snap @@ -0,0 +1,9 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: { a :: Int, b :: Int } + +Types diff --git a/tests-integration/fixtures/checking2/184_record_expression_missing_field/Main.purs b/tests-integration/fixtures/checking2/184_record_expression_missing_field/Main.purs new file mode 100644 index 00000000..b38d9667 --- /dev/null +++ b/tests-integration/fixtures/checking2/184_record_expression_missing_field/Main.purs @@ -0,0 +1,4 @@ +module Main where + +test :: { a :: Int, b :: Int } +test = { a: 1 } diff --git a/tests-integration/fixtures/checking2/184_record_expression_missing_field/Main.snap b/tests-integration/fixtures/checking2/184_record_expression_missing_field/Main.snap new file mode 100644 index 00000000..2d5e7e90 --- /dev/null +++ b/tests-integration/fixtures/checking2/184_record_expression_missing_field/Main.snap @@ -0,0 +1,26 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: { a :: Int, b :: Int } + +Types + +Errors +CheckError { + kind: PropertyIsMissing { + labels: [ + "b", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + CheckingExpression( + AstId(14), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/185_record_expression_additional_field/Main.purs b/tests-integration/fixtures/checking2/185_record_expression_additional_field/Main.purs new file mode 100644 index 00000000..2deff69d --- /dev/null +++ b/tests-integration/fixtures/checking2/185_record_expression_additional_field/Main.purs @@ -0,0 +1,4 @@ +module Main where + +test :: { a :: Int } +test = { a: 1, b: 2 } diff --git a/tests-integration/fixtures/checking2/185_record_expression_additional_field/Main.snap b/tests-integration/fixtures/checking2/185_record_expression_additional_field/Main.snap new file mode 100644 index 00000000..34d7ee1d --- /dev/null +++ b/tests-integration/fixtures/checking2/185_record_expression_additional_field/Main.snap @@ -0,0 +1,26 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: { a :: Int } + +Types + +Errors +CheckError { + kind: AdditionalProperty { + labels: [ + "b", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + CheckingExpression( + AstId(12), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/186_record_expression_missing_and_additional/Main.purs b/tests-integration/fixtures/checking2/186_record_expression_missing_and_additional/Main.purs new file mode 100644 index 00000000..bdd8fb6d --- /dev/null +++ b/tests-integration/fixtures/checking2/186_record_expression_missing_and_additional/Main.purs @@ -0,0 +1,4 @@ +module Main where + +test :: { a :: Int, b :: Int } +test = { a: 1, c: 3 } diff --git a/tests-integration/fixtures/checking2/186_record_expression_missing_and_additional/Main.snap b/tests-integration/fixtures/checking2/186_record_expression_missing_and_additional/Main.snap new file mode 100644 index 00000000..cc6f11d3 --- /dev/null +++ b/tests-integration/fixtures/checking2/186_record_expression_missing_and_additional/Main.snap @@ -0,0 +1,41 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: { a :: Int, b :: Int } + +Types + +Errors +CheckError { + kind: PropertyIsMissing { + labels: [ + "b", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + CheckingExpression( + AstId(14), + ), + ], +} +CheckError { + kind: AdditionalProperty { + labels: [ + "c", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + CheckingExpression( + AstId(14), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/187_record_expression_nested_additional/Main.purs b/tests-integration/fixtures/checking2/187_record_expression_nested_additional/Main.purs new file mode 100644 index 00000000..e11b1035 --- /dev/null +++ b/tests-integration/fixtures/checking2/187_record_expression_nested_additional/Main.purs @@ -0,0 +1,4 @@ +module Main where + +test :: { outer :: { x :: Int } } +test = { outer: { x: 1, y: 2 } } diff --git a/tests-integration/fixtures/checking2/187_record_expression_nested_additional/Main.snap b/tests-integration/fixtures/checking2/187_record_expression_nested_additional/Main.snap new file mode 100644 index 00000000..e023167a --- /dev/null +++ b/tests-integration/fixtures/checking2/187_record_expression_nested_additional/Main.snap @@ -0,0 +1,44 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: { outer :: { x :: Int } } + +Types + +Errors +CheckError { + kind: AdditionalProperty { + labels: [ + "y", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + CheckingExpression( + AstId(14), + ), + CheckingExpression( + AstId(16), + ), + ], +} +CheckError { + kind: AdditionalProperty { + labels: [ + "y", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + CheckingExpression( + AstId(14), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/188_record_access_sections/Main.purs b/tests-integration/fixtures/checking2/188_record_access_sections/Main.purs new file mode 100644 index 00000000..cb921c80 --- /dev/null +++ b/tests-integration/fixtures/checking2/188_record_access_sections/Main.purs @@ -0,0 +1,39 @@ +module Main where + +map :: forall a b. (a -> b) -> Array a -> Array b +map f x = [] + +apply :: forall a b. (a -> b) -> a -> b +apply f x = f x + +f :: ({ foo :: Int } -> Int) -> Int +f g = g { foo: 1 } + +test1 = _.prop + +test2 = _.a.b.c + +test3 = _.poly + +test4 = map _.prop + +test5 = f _.foo + +test6 r g = g _.bar + +test7 = \r -> _.nonexistent r + +test8 = (_ _.foo) + +test9 = map (_ _.prop) + +test10 = apply (_.a.b) + +test11 = { a: _, b: _.foo } + +test12 = [_, _.bar] + +test13 = case _ of + _ -> _.prop + +test14 = if _ then _.a else _.b diff --git a/tests-integration/fixtures/checking2/188_record_access_sections/Main.snap b/tests-integration/fixtures/checking2/188_record_access_sections/Main.snap new file mode 100644 index 00000000..1be6a854 --- /dev/null +++ b/tests-integration/fixtures/checking2/188_record_access_sections/Main.snap @@ -0,0 +1,61 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +map :: + forall (a :: Type) (b :: Type). + ((a :: Type) -> (b :: Type)) -> Array (a :: Type) -> Array (b :: Type) +apply :: forall (a :: Type) (b :: Type). ((a :: Type) -> (b :: Type)) -> (a :: Type) -> (b :: Type) +f :: ({ foo :: Int } -> Int) -> Int +test1 :: + forall (t5 :: Type) (t4 :: Row Type). { prop :: (t5 :: Type) | (t4 :: Row Type) } -> (t5 :: Type) +test2 :: + forall (t9 :: Type) (t8 :: Row Type) (t7 :: Row Type) (t6 :: Row Type). + { a :: { b :: { c :: (t9 :: Type) | (t8 :: Row Type) } | (t7 :: Row Type) } + | (t6 :: Row Type) + } -> + (t9 :: Type) +test3 :: + forall (t11 :: Type) (t10 :: Row Type). + { poly :: (t11 :: Type) | (t10 :: Row Type) } -> (t11 :: Type) +test4 :: + forall (t13 :: Type) (t12 :: Row Type). + Array { prop :: (t13 :: Type) | (t12 :: Row Type) } -> Array (t13 :: Type) +test5 :: Int +test6 :: + forall (t17 :: Type) (t16 :: Type) (t15 :: Row Type) (t14 :: Type). + (t17 :: Type) -> + (({ bar :: (t16 :: Type) | (t15 :: Row Type) } -> (t16 :: Type)) -> (t14 :: Type)) -> + (t14 :: Type) +test7 :: + forall (t19 :: Type) (t18 :: Row Type). + { nonexistent :: (t19 :: Type) | (t18 :: Row Type) } -> (t19 :: Type) +test8 :: + forall (t22 :: Type) (t21 :: Row Type) (t20 :: Type). + (({ foo :: (t22 :: Type) | (t21 :: Row Type) } -> (t22 :: Type)) -> (t20 :: Type)) -> + (t20 :: Type) +test9 :: + forall (t25 :: Type) (t24 :: Row Type) (t23 :: Type). + Array (({ prop :: (t25 :: Type) | (t24 :: Row Type) } -> (t25 :: Type)) -> (t23 :: Type)) -> + Array (t23 :: Type) +test10 :: + forall (t28 :: Type) (t27 :: Row Type) (t26 :: Row Type). + { a :: { b :: (t28 :: Type) | (t27 :: Row Type) } | (t26 :: Row Type) } -> (t28 :: Type) +test11 :: + forall (t31 :: Type) (t30 :: Type) (t29 :: Row Type). + (t31 :: Type) -> + { a :: (t31 :: Type), b :: { foo :: (t30 :: Type) | (t29 :: Row Type) } -> (t30 :: Type) } +test12 :: + forall (t33 :: Type) (t32 :: Row Type). + ({ bar :: (t33 :: Type) | (t32 :: Row Type) } -> (t33 :: Type)) -> + Array ({ bar :: (t33 :: Type) | (t32 :: Row Type) } -> (t33 :: Type)) +test13 :: + forall (t36 :: Type) (t35 :: Type) (t34 :: Row Type). + (t36 :: Type) -> { prop :: (t35 :: Type) | (t34 :: Row Type) } -> (t35 :: Type) +test14 :: + forall (t38 :: Type) (t37 :: Row Type). + Boolean -> { a :: (t38 :: Type), b :: (t38 :: Type) | (t37 :: Row Type) } -> (t38 :: Type) + +Types diff --git a/tests-integration/fixtures/checking2/189_record_update_sections/Main.purs b/tests-integration/fixtures/checking2/189_record_update_sections/Main.purs new file mode 100644 index 00000000..5a234a2c --- /dev/null +++ b/tests-integration/fixtures/checking2/189_record_update_sections/Main.purs @@ -0,0 +1,33 @@ +module Main where + +singleFieldUpdate = _ { a = 1 } + +multipleFieldUpdate = _ { a = 2, b = true, c = "three" } + +nestedRecordUpdate = _ { a { b { c = 42 } } } + +updateWithValueSection = _ { a = (_ + 1) } + +updateWithSectionInteraction = _ { a = _ } + +polymorphicRecordUpdate = _ { foo = 0 } + +higherOrderContext = map (_ { x = 10 }) + +multipleSectionsInteraction = _ { x = _, y = _ } + +nestedSectionInteraction = _ { a { b = _ } } + +mixedSections = _ { a = _ + 1, b = 2 } + +recordAccessSectionUpdate = _ { a = _.b } + +concreteRecordUpdateSection = ({ a: 1 } { a = _ }) + +map :: forall a b. (a -> b) -> Array a -> Array b +map f x = [] + +infixl 6 add as + + +add :: Int -> Int -> Int +add x y = x diff --git a/tests-integration/fixtures/checking2/189_record_update_sections/Main.snap b/tests-integration/fixtures/checking2/189_record_update_sections/Main.snap new file mode 100644 index 00000000..5f9956e1 --- /dev/null +++ b/tests-integration/fixtures/checking2/189_record_update_sections/Main.snap @@ -0,0 +1,60 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +singleFieldUpdate :: + forall (t1 :: Type) (t0 :: Row Type). + { a :: (t1 :: Type) | (t0 :: Row Type) } -> { a :: Int | (t0 :: Row Type) } +multipleFieldUpdate :: + forall (t5 :: Type) (t4 :: Type) (t3 :: Type) (t2 :: Row Type). + { a :: (t5 :: Type), b :: (t4 :: Type), c :: (t3 :: Type) | (t2 :: Row Type) } -> + { a :: Int, b :: Boolean, c :: String | (t2 :: Row Type) } +nestedRecordUpdate :: + forall (t9 :: Type) (t8 :: Row Type) (t7 :: Row Type) (t6 :: Row Type). + { a :: { b :: { c :: (t9 :: Type) | (t8 :: Row Type) } | (t7 :: Row Type) } + | (t6 :: Row Type) + } -> + { a :: { b :: { c :: Int | (t8 :: Row Type) } | (t7 :: Row Type) } | (t6 :: Row Type) } +updateWithValueSection :: + forall (t11 :: Type) (t10 :: Row Type). + { a :: (t11 :: Type) | (t10 :: Row Type) } -> { a :: Int -> Int | (t10 :: Row Type) } +updateWithSectionInteraction :: + forall (t14 :: Type) (t13 :: Row Type) (t12 :: Type). + { a :: (t14 :: Type) | (t13 :: Row Type) } -> + (t12 :: Type) -> + { a :: (t12 :: Type) | (t13 :: Row Type) } +polymorphicRecordUpdate :: + forall (t16 :: Type) (t15 :: Row Type). + { foo :: (t16 :: Type) | (t15 :: Row Type) } -> { foo :: Int | (t15 :: Row Type) } +higherOrderContext :: + forall (t20 :: Type) (t19 :: Row Type). + Array { x :: (t20 :: Type) | (t19 :: Row Type) } -> Array { x :: Int | (t19 :: Row Type) } +multipleSectionsInteraction :: + forall (t25 :: Type) (t24 :: Type) (t23 :: Row Type) (t22 :: Type) (t21 :: Type). + { x :: (t25 :: Type), y :: (t24 :: Type) | (t23 :: Row Type) } -> + (t22 :: Type) -> + (t21 :: Type) -> + { x :: (t22 :: Type), y :: (t21 :: Type) | (t23 :: Row Type) } +nestedSectionInteraction :: + forall (t29 :: Type) (t28 :: Row Type) (t27 :: Row Type) (t26 :: Type). + { a :: { b :: (t29 :: Type) | (t28 :: Row Type) } | (t27 :: Row Type) } -> + (t26 :: Type) -> + { a :: { b :: (t26 :: Type) | (t28 :: Row Type) } | (t27 :: Row Type) } +mixedSections :: + forall (t32 :: Type) (t31 :: Type) (t30 :: Row Type). + { a :: (t32 :: Type), b :: (t31 :: Type) | (t30 :: Row Type) } -> + { a :: Int -> Int, b :: Int | (t30 :: Row Type) } +recordAccessSectionUpdate :: + forall (t36 :: Type) (t35 :: Row Type) (t34 :: Type) (t33 :: Row Type). + { a :: (t36 :: Type) | (t35 :: Row Type) } -> + { a :: { b :: (t34 :: Type) | (t33 :: Row Type) } -> (t34 :: Type) | (t35 :: Row Type) } +concreteRecordUpdateSection :: forall (t37 :: Type). (t37 :: Type) -> { a :: (t37 :: Type) } +map :: + forall (a :: Type) (b :: Type). + ((a :: Type) -> (b :: Type)) -> Array (a :: Type) -> Array (b :: Type) ++ :: Int -> Int -> Int +add :: Int -> Int -> Int + +Types diff --git a/tests-integration/fixtures/checking2/190_record_binder_shrinking/Main.purs b/tests-integration/fixtures/checking2/190_record_binder_shrinking/Main.purs new file mode 100644 index 00000000..9a86121a --- /dev/null +++ b/tests-integration/fixtures/checking2/190_record_binder_shrinking/Main.purs @@ -0,0 +1,4 @@ +module Main where + +test :: { a :: Int, b :: Int } -> Int +test { a } = a diff --git a/tests-integration/fixtures/checking2/190_record_binder_shrinking/Main.snap b/tests-integration/fixtures/checking2/190_record_binder_shrinking/Main.snap new file mode 100644 index 00000000..518afcfa --- /dev/null +++ b/tests-integration/fixtures/checking2/190_record_binder_shrinking/Main.snap @@ -0,0 +1,9 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: { a :: Int, b :: Int } -> Int + +Types diff --git a/tests-integration/fixtures/checking2/191_record_binder_additional_property/Main.purs b/tests-integration/fixtures/checking2/191_record_binder_additional_property/Main.purs new file mode 100644 index 00000000..bc79aeef --- /dev/null +++ b/tests-integration/fixtures/checking2/191_record_binder_additional_property/Main.purs @@ -0,0 +1,4 @@ +module Main where + +test :: { a :: Int } -> Int +test { a, b, c } = a diff --git a/tests-integration/fixtures/checking2/191_record_binder_additional_property/Main.snap b/tests-integration/fixtures/checking2/191_record_binder_additional_property/Main.snap new file mode 100644 index 00000000..789dc9ba --- /dev/null +++ b/tests-integration/fixtures/checking2/191_record_binder_additional_property/Main.snap @@ -0,0 +1,27 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: { a :: Int } -> Int + +Types + +Errors +CheckError { + kind: AdditionalProperty { + labels: [ + "b", + "c", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + CheckingBinder( + AstId(13), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/192_record_binder_additional_property_nested/Main.purs b/tests-integration/fixtures/checking2/192_record_binder_additional_property_nested/Main.purs new file mode 100644 index 00000000..f14006b4 --- /dev/null +++ b/tests-integration/fixtures/checking2/192_record_binder_additional_property_nested/Main.purs @@ -0,0 +1,4 @@ +module Main where + +test :: { outer :: { x :: Int } } -> Int +test { outer: { x, y } } = x diff --git a/tests-integration/fixtures/checking2/192_record_binder_additional_property_nested/Main.snap b/tests-integration/fixtures/checking2/192_record_binder_additional_property_nested/Main.snap new file mode 100644 index 00000000..8e480492 --- /dev/null +++ b/tests-integration/fixtures/checking2/192_record_binder_additional_property_nested/Main.snap @@ -0,0 +1,29 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: { outer :: { x :: Int } } -> Int + +Types + +Errors +CheckError { + kind: AdditionalProperty { + labels: [ + "y", + ], + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + CheckingBinder( + AstId(15), + ), + CheckingBinder( + AstId(17), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/193_givens_matching/Main.purs b/tests-integration/fixtures/checking2/193_givens_matching/Main.purs new file mode 100644 index 00000000..4b9bc0a6 --- /dev/null +++ b/tests-integration/fixtures/checking2/193_givens_matching/Main.purs @@ -0,0 +1,7 @@ +module Main where + +class Eq a where + eq :: a -> a -> Boolean + +test :: forall a. Eq a => a -> Boolean +test x = eq x x diff --git a/tests-integration/fixtures/checking2/193_givens_matching/Main.snap b/tests-integration/fixtures/checking2/193_givens_matching/Main.snap new file mode 100644 index 00000000..8fafd85d --- /dev/null +++ b/tests-integration/fixtures/checking2/193_givens_matching/Main.snap @@ -0,0 +1,15 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean +test :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> Boolean + +Types +Eq :: Type -> Constraint + +Classes +class forall (a :: Type). Eq (a :: Type) + eq :: forall (a :: Type). Eq (a :: Type) => (a :: Type) -> (a :: Type) -> Boolean diff --git a/tests-integration/fixtures/checking2/194_givens_functional_dependency/Main.purs b/tests-integration/fixtures/checking2/194_givens_functional_dependency/Main.purs new file mode 100644 index 00000000..89b9c793 --- /dev/null +++ b/tests-integration/fixtures/checking2/194_givens_functional_dependency/Main.purs @@ -0,0 +1,13 @@ +module Main where + +class Convert a b | a -> b where + convert :: a -> b + +useGiven :: forall x. Convert Int x => x +useGiven = convert 42 + +class Relate a b c | a b -> c where + relate :: a -> b -> c + +useRelate :: forall y. Relate Int String y => y +useRelate = relate 1 "hello" diff --git a/tests-integration/fixtures/checking2/194_givens_functional_dependency/Main.snap b/tests-integration/fixtures/checking2/194_givens_functional_dependency/Main.snap new file mode 100644 index 00000000..7fb76aae --- /dev/null +++ b/tests-integration/fixtures/checking2/194_givens_functional_dependency/Main.snap @@ -0,0 +1,26 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +convert :: + forall (a :: Type) (b :: Type). Convert (a :: Type) (b :: Type) => (a :: Type) -> (b :: Type) +useGiven :: forall (x :: Type). Convert Int (x :: Type) => (x :: Type) +relate :: + forall (a :: Type) (b :: Type) (c :: Type). + Relate (a :: Type) (b :: Type) (c :: Type) => (a :: Type) -> (b :: Type) -> (c :: Type) +useRelate :: forall (y :: Type). Relate Int String (y :: Type) => (y :: Type) + +Types +Convert :: Type -> Type -> Constraint +Relate :: Type -> Type -> Type -> Constraint + +Classes +class forall (a :: Type) (b :: Type). Convert (a :: Type) (b :: Type) + convert :: + forall (a :: Type) (b :: Type). Convert (a :: Type) (b :: Type) => (a :: Type) -> (b :: Type) +class forall (a :: Type) (b :: Type) (c :: Type). Relate (a :: Type) (b :: Type) (c :: Type) + relate :: + forall (a :: Type) (b :: Type) (c :: Type). + Relate (a :: Type) (b :: Type) (c :: Type) => (a :: Type) -> (b :: Type) -> (c :: Type) diff --git a/tests-integration/fixtures/checking2/195_givens_superclass_where_binding/Main.purs b/tests-integration/fixtures/checking2/195_givens_superclass_where_binding/Main.purs new file mode 100644 index 00000000..01301cf1 --- /dev/null +++ b/tests-integration/fixtures/checking2/195_givens_superclass_where_binding/Main.purs @@ -0,0 +1,22 @@ +module Main where + +import Control.Applicative (class Applicative, pure) +import Control.Bind (class Bind, bind) +import Control.Monad (class Monad) +import Control.Monad.Rec (class MonadRec, tailRecM) +import Data.Functor (class Functor, map) + +test :: forall m a. MonadRec m => a -> m a +test a = go a + where + go x = pure x + +test2 :: forall m a. MonadRec m => m a -> m a +test2 ma = go ma + where + go x = bind x pure + +test3 :: forall m. MonadRec m => m Int -> m Int +test3 mi = go mi + where + go x = map (\y -> y) x diff --git a/tests-integration/fixtures/checking2/195_givens_superclass_where_binding/Main.snap b/tests-integration/fixtures/checking2/195_givens_superclass_where_binding/Main.snap new file mode 100644 index 00000000..da06dd28 --- /dev/null +++ b/tests-integration/fixtures/checking2/195_givens_superclass_where_binding/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: + forall (m :: Type -> Type) (a :: Type). + MonadRec (m :: Type -> Type) => (a :: Type) -> (m :: Type -> Type) (a :: Type) +test2 :: + forall (m :: Type -> Type) (a :: Type). + MonadRec (m :: Type -> Type) => + (m :: Type -> Type) (a :: Type) -> (m :: Type -> Type) (a :: Type) +test3 :: + forall (m :: Type -> Type). + MonadRec (m :: Type -> Type) => (m :: Type -> Type) Int -> (m :: Type -> Type) Int + +Types diff --git a/tests-integration/fixtures/checking2/196_instance_record_matching/Main.purs b/tests-integration/fixtures/checking2/196_instance_record_matching/Main.purs new file mode 100644 index 00000000..1d49266a --- /dev/null +++ b/tests-integration/fixtures/checking2/196_instance_record_matching/Main.purs @@ -0,0 +1,21 @@ +module Main where + +class Make :: Type -> Type -> Constraint +class Make a b | a -> b where + make :: a -> b + +instance Make { | r } { | r } where + make x = x + +testMake :: { a :: Int, b :: String } -> { a :: Int, b :: String } +testMake = make + +class Convert :: Type -> Type -> Constraint +class Convert a b | a -> b where + convert :: a -> b + +instance Convert { | r } { converted :: { | r } } where + convert x = { converted: x } + +testConvert :: { x :: Int } -> { converted :: { x :: Int } } +testConvert = convert diff --git a/tests-integration/fixtures/checking2/196_instance_record_matching/Main.snap b/tests-integration/fixtures/checking2/196_instance_record_matching/Main.snap new file mode 100644 index 00000000..b3cab8ee --- /dev/null +++ b/tests-integration/fixtures/checking2/196_instance_record_matching/Main.snap @@ -0,0 +1,26 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +make :: forall (a :: Type) (b :: Type). Make (a :: Type) (b :: Type) => (a :: Type) -> (b :: Type) +testMake :: { a :: Int, b :: String } -> { a :: Int, b :: String } +convert :: + forall (a :: Type) (b :: Type). Convert (a :: Type) (b :: Type) => (a :: Type) -> (b :: Type) +testConvert :: { x :: Int } -> { converted :: { x :: Int } } + +Types +Make :: Type -> Type -> Constraint +Convert :: Type -> Type -> Constraint + +Classes +class forall (a :: Type) (b :: Type). Make (a :: Type) (b :: Type) + make :: forall (a :: Type) (b :: Type). Make (a :: Type) (b :: Type) => (a :: Type) -> (b :: Type) +class forall (a :: Type) (b :: Type). Convert (a :: Type) (b :: Type) + convert :: + forall (a :: Type) (b :: Type). Convert (a :: Type) (b :: Type) => (a :: Type) -> (b :: Type) + +Instances +instance forall (t4 :: Row Type). Make {| (t4 :: Row Type) } {| (t4 :: Row Type) } +instance forall (t5 :: Row Type). Convert {| (t5 :: Row Type) } { converted :: {| (t5 :: Row Type) } } diff --git a/tests-integration/fixtures/checking2/197_instance_record_open_row/Main.purs b/tests-integration/fixtures/checking2/197_instance_record_open_row/Main.purs new file mode 100644 index 00000000..146f6187 --- /dev/null +++ b/tests-integration/fixtures/checking2/197_instance_record_open_row/Main.purs @@ -0,0 +1,27 @@ +module Main where + +class Clone :: Type -> Type -> Constraint +class Clone a b | a -> b where + clone :: a -> b + +instance Clone { | r } { | r } where + clone x = x + +testClonePerson :: { name :: String, age :: Int } -> { name :: String, age :: Int } +testClonePerson = clone + +testCloneEmpty :: {} -> {} +testCloneEmpty = clone + +testCloneSingle :: { x :: Int } -> { x :: Int } +testCloneSingle = clone + +class Nest :: Type -> Type -> Constraint +class Nest a b | a -> b where + nest :: a -> b + +instance Nest { | r } { inner :: { | r }, outer :: Int } where + nest x = { inner: x, outer: 0 } + +testNest :: { a :: String } -> { inner :: { a :: String }, outer :: Int } +testNest = nest diff --git a/tests-integration/fixtures/checking2/197_instance_record_open_row/Main.snap b/tests-integration/fixtures/checking2/197_instance_record_open_row/Main.snap new file mode 100644 index 00000000..dfd20102 --- /dev/null +++ b/tests-integration/fixtures/checking2/197_instance_record_open_row/Main.snap @@ -0,0 +1,26 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +clone :: forall (a :: Type) (b :: Type). Clone (a :: Type) (b :: Type) => (a :: Type) -> (b :: Type) +testClonePerson :: { age :: Int, name :: String } -> { age :: Int, name :: String } +testCloneEmpty :: {} -> {} +testCloneSingle :: { x :: Int } -> { x :: Int } +nest :: forall (a :: Type) (b :: Type). Nest (a :: Type) (b :: Type) => (a :: Type) -> (b :: Type) +testNest :: { a :: String } -> { inner :: { a :: String }, outer :: Int } + +Types +Clone :: Type -> Type -> Constraint +Nest :: Type -> Type -> Constraint + +Classes +class forall (a :: Type) (b :: Type). Clone (a :: Type) (b :: Type) + clone :: forall (a :: Type) (b :: Type). Clone (a :: Type) (b :: Type) => (a :: Type) -> (b :: Type) +class forall (a :: Type) (b :: Type). Nest (a :: Type) (b :: Type) + nest :: forall (a :: Type) (b :: Type). Nest (a :: Type) (b :: Type) => (a :: Type) -> (b :: Type) + +Instances +instance forall (t4 :: Row Type). Clone {| (t4 :: Row Type) } {| (t4 :: Row Type) } +instance forall (t5 :: Row Type). Nest {| (t5 :: Row Type) } { inner :: {| (t5 :: Row Type) }, outer :: Int } diff --git a/tests-integration/fixtures/checking2/198_instance_head_invalid_row/Main.purs b/tests-integration/fixtures/checking2/198_instance_head_invalid_row/Main.purs new file mode 100644 index 00000000..461d9b6f --- /dev/null +++ b/tests-integration/fixtures/checking2/198_instance_head_invalid_row/Main.purs @@ -0,0 +1,12 @@ +module Main where + +data Proxy :: forall k. k -> Type +data Proxy a = Proxy + +class T :: forall k. k -> Type +class T a + +instance T ( a :: Int ) +instance T { a :: Int } +instance T (Proxy ( a :: Int )) +instance T (Proxy { a :: Int }) diff --git a/tests-integration/fixtures/checking2/198_instance_head_invalid_row/Main.snap b/tests-integration/fixtures/checking2/198_instance_head_invalid_row/Main.snap new file mode 100644 index 00000000..35a53ce8 --- /dev/null +++ b/tests-integration/fixtures/checking2/198_instance_head_invalid_row/Main.snap @@ -0,0 +1,89 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (k :: Type) (a :: (k :: Type)). Proxy @(k :: Type) (a :: (k :: Type)) + +Types +Proxy :: forall (k :: Type). (k :: Type) -> Type +T :: forall (k :: Type). (k :: Type) -> Type + +Classes +class forall (k :: Type) (a :: (k :: Type)). T @(k :: Type) (a :: (k :: Type)) + +Instances +instance T @(Row Type) ( a :: Int ) +instance T @Type { a :: Int } +instance T @Type (Proxy @(Row Type) ( a :: Int )) +instance T @Type (Proxy @Type { a :: Int }) + +Roles +Proxy = [Phantom] + +Errors +CheckError { + kind: CannotUnify { + t1: Id(5), + t2: Id(6), + }, + crumbs: [], +} +CheckError { + kind: InstanceHeadLabeledRow { + class_file: Idx::(39), + class_item: Idx::(1), + position: 0, + type_message: Id(7), + }, + crumbs: [], +} +CheckError { + kind: CannotUnify { + t1: Id(5), + t2: Id(6), + }, + crumbs: [], +} +CheckError { + kind: InstanceHeadLabeledRow { + class_file: Idx::(39), + class_item: Idx::(1), + position: 0, + type_message: Id(8), + }, + crumbs: [], +} +CheckError { + kind: CannotUnify { + t1: Id(5), + t2: Id(6), + }, + crumbs: [], +} +CheckError { + kind: InstanceHeadLabeledRow { + class_file: Idx::(39), + class_item: Idx::(1), + position: 0, + type_message: Id(9), + }, + crumbs: [], +} +CheckError { + kind: CannotUnify { + t1: Id(5), + t2: Id(6), + }, + crumbs: [], +} +CheckError { + kind: InstanceHeadLabeledRow { + class_file: Idx::(39), + class_item: Idx::(1), + position: 0, + type_message: Id(10), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/199_instance_kind_application_matching/Main.purs b/tests-integration/fixtures/checking2/199_instance_kind_application_matching/Main.purs new file mode 100644 index 00000000..53105fa8 --- /dev/null +++ b/tests-integration/fixtures/checking2/199_instance_kind_application_matching/Main.purs @@ -0,0 +1,11 @@ +module Main where + +import Data.Newtype (class Newtype, unwrap) + +newtype Endo :: forall k. (k -> k -> Type) -> k -> Type +newtype Endo c a = Endo (c a a) + +instance Newtype (Endo c a) (c a a) + +test :: forall b. Endo Function b -> b -> b +test x = unwrap x diff --git a/tests-integration/fixtures/checking2/199_instance_kind_application_matching/Main.snap b/tests-integration/fixtures/checking2/199_instance_kind_application_matching/Main.snap new file mode 100644 index 00000000..624c09ff --- /dev/null +++ b/tests-integration/fixtures/checking2/199_instance_kind_application_matching/Main.snap @@ -0,0 +1,23 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Endo :: + forall (k :: Type) (c :: (k :: Type) -> (k :: Type) -> Type) (a :: (k :: Type)). + (c :: (k :: Type) -> (k :: Type) -> Type) (a :: (k :: Type)) (a :: (k :: Type)) -> + Endo @(k :: Type) (c :: (k :: Type) -> (k :: Type) -> Type) (a :: (k :: Type)) +test :: forall (b :: Type). Endo @Type Function (b :: Type) -> (b :: Type) -> (b :: Type) + +Types +Endo :: forall (k :: Type). ((k :: Type) -> (k :: Type) -> Type) -> (k :: Type) -> Type + +Instances +instance forall (t5 :: Type) (t3 :: (t5 :: Type) -> (t5 :: Type) -> Type) (t4 :: (t5 :: Type)). + Newtype @Type + (Endo @(t5 :: Type) (t3 :: (t5 :: Type) -> (t5 :: Type) -> Type) (t4 :: (t5 :: Type))) + ((t3 :: (t5 :: Type) -> (t5 :: Type) -> Type) (t4 :: (t5 :: Type)) (t4 :: (t5 :: Type))) + +Roles +Endo = [Representational, Nominal] diff --git a/tests-integration/fixtures/checking2/200_instance_bound_variable_unification/Main.purs b/tests-integration/fixtures/checking2/200_instance_bound_variable_unification/Main.purs new file mode 100644 index 00000000..e84b05aa --- /dev/null +++ b/tests-integration/fixtures/checking2/200_instance_bound_variable_unification/Main.purs @@ -0,0 +1,16 @@ +module Main where + +foreign import unsafeSet :: forall r1 r2 a. String -> a -> Record r1 -> Record r2 + +class BuildRecord :: Row Type -> Row Type -> Constraint +class BuildRecord row subrow | row -> subrow where + buildIt :: Record subrow + +instance buildRecordImpl :: BuildRecord row subrow where + buildIt = result + where + result :: Record subrow + result = unsafeSet "x" 42 {} + +test :: forall r s. BuildRecord r s => Record s +test = buildIt diff --git a/tests-integration/fixtures/checking2/200_instance_bound_variable_unification/Main.snap b/tests-integration/fixtures/checking2/200_instance_bound_variable_unification/Main.snap new file mode 100644 index 00000000..9f72093a --- /dev/null +++ b/tests-integration/fixtures/checking2/200_instance_bound_variable_unification/Main.snap @@ -0,0 +1,27 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +unsafeSet :: + forall (r1 :: Row Type) (r2 :: Row Type) (a :: Type). + String -> (a :: Type) -> {| (r1 :: Row Type) } -> {| (r2 :: Row Type) } +buildIt :: + forall (row :: Row Type) (subrow :: Row Type). + BuildRecord (row :: Row Type) (subrow :: Row Type) => {| (subrow :: Row Type) } +test :: + forall (r :: Row Type) (s :: Row Type). + BuildRecord (r :: Row Type) (s :: Row Type) => {| (s :: Row Type) } + +Types +BuildRecord :: Row Type -> Row Type -> Constraint + +Classes +class forall (row :: Row Type) (subrow :: Row Type). BuildRecord (row :: Row Type) (subrow :: Row Type) + buildIt :: + forall (row :: Row Type) (subrow :: Row Type). + BuildRecord (row :: Row Type) (subrow :: Row Type) => {| (subrow :: Row Type) } + +Instances +instance forall (t2 :: Row Type) (t3 :: Row Type). BuildRecord (t2 :: Row Type) (t3 :: Row Type) diff --git a/tests-integration/fixtures/checking2/201_synonym_function_result_kind/Main.purs b/tests-integration/fixtures/checking2/201_synonym_function_result_kind/Main.purs new file mode 100644 index 00000000..7b0132c9 --- /dev/null +++ b/tests-integration/fixtures/checking2/201_synonym_function_result_kind/Main.purs @@ -0,0 +1,9 @@ +module Main where + +data Box (f :: Type -> Type) (a :: Type) = Box (f a) + +type Wrap :: (Type -> Type) -> Type -> Type +type Wrap f = Box f + +test :: forall f. Wrap f Int -> Wrap f Int +test x = x diff --git a/tests-integration/fixtures/checking2/201_synonym_function_result_kind/Main.snap b/tests-integration/fixtures/checking2/201_synonym_function_result_kind/Main.snap new file mode 100644 index 00000000..c2af8435 --- /dev/null +++ b/tests-integration/fixtures/checking2/201_synonym_function_result_kind/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Box :: + forall (f :: Type -> Type) (a :: Type). + (f :: Type -> Type) (a :: Type) -> Box (f :: Type -> Type) (a :: Type) +test :: forall (f :: Type -> Type). Wrap (f :: Type -> Type) Int -> Wrap (f :: Type -> Type) Int + +Types +Box :: (Type -> Type) -> Type -> Type +Wrap :: (Type -> Type) -> Type -> Type + +Synonyms +type Wrap f = Box (f :: Type -> Type) + +Roles +Box = [Representational, Nominal] diff --git a/tests-integration/fixtures/checking2/202_synonym_forall_expansion/Main.purs b/tests-integration/fixtures/checking2/202_synonym_forall_expansion/Main.purs new file mode 100644 index 00000000..9a8c39a5 --- /dev/null +++ b/tests-integration/fixtures/checking2/202_synonym_forall_expansion/Main.purs @@ -0,0 +1,6 @@ +module Main where + +type NatTrans f g = forall a. f a -> g a + +apply :: forall f g. NatTrans f g -> f Int -> g Int +apply nat fa = nat fa diff --git a/tests-integration/fixtures/checking2/202_synonym_forall_expansion/Main.snap b/tests-integration/fixtures/checking2/202_synonym_forall_expansion/Main.snap new file mode 100644 index 00000000..4386d09f --- /dev/null +++ b/tests-integration/fixtures/checking2/202_synonym_forall_expansion/Main.snap @@ -0,0 +1,18 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +apply :: + forall (f :: Type -> Type) (g :: Type -> Type). + NatTrans @Type (f :: Type -> Type) (g :: Type -> Type) -> + (f :: Type -> Type) Int -> + (g :: Type -> Type) Int + +Types +NatTrans :: forall (t3 :: Type). ((t3 :: Type) -> Type) -> ((t3 :: Type) -> Type) -> Type + +Synonyms +type NatTrans f g = forall (a :: (t3 :: Type)). + (f :: (t3 :: Type) -> Type) (a :: (t3 :: Type)) -> (g :: (t3 :: Type) -> Type) (a :: (t3 :: Type)) diff --git a/tests-integration/fixtures/checking2/203_binder_instantiation/Main.purs b/tests-integration/fixtures/checking2/203_binder_instantiation/Main.purs new file mode 100644 index 00000000..ceb764ab --- /dev/null +++ b/tests-integration/fixtures/checking2/203_binder_instantiation/Main.purs @@ -0,0 +1,25 @@ +module Main where + +data Maybe a = Just a | Nothing +data Id = MkId (forall a. a -> a) + +identity :: forall a. Maybe (a -> a) +identity = Nothing + +test :: Partial => Int +test = case identity of + Just f -> let _ = f 42 in f true + +test2 :: Id -> Boolean +test2 x = case x of + MkId f -> let _ = f 42 in f true + +test3 :: Partial => Int +test3 = + let (Just f) = identity + in let _ = f 42 in f true + +test4 :: Id -> Boolean +test4 x = + let (MkId f) = x + in let _ = f 42 in f true diff --git a/tests-integration/fixtures/checking2/203_binder_instantiation/Main.snap b/tests-integration/fixtures/checking2/203_binder_instantiation/Main.snap new file mode 100644 index 00000000..0632a4a7 --- /dev/null +++ b/tests-integration/fixtures/checking2/203_binder_instantiation/Main.snap @@ -0,0 +1,100 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nothing :: forall (a :: Type). Maybe (a :: Type) +MkId :: (forall (a :: Type). (a :: Type) -> (a :: Type)) -> Id +identity :: forall (a :: Type). Maybe ((a :: Type) -> (a :: Type)) +test :: Partial => Int +test2 :: Id -> Boolean +test3 :: Partial => Int +test4 :: Id -> Boolean + +Types +Maybe :: Type -> Type +Id :: Type + +Roles +Maybe = [Representational] +Id = [] + +Errors +CheckError { + kind: CannotUnify { + t1: Id(7), + t2: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(4), + ), + CheckingExpression( + AstId(38), + ), + CheckingExpression( + AstId(48), + ), + CheckingExpression( + AstId(57), + ), + CheckingExpression( + AstId(60), + ), + ], +} +CheckError { + kind: MissingPatterns { + patterns: [ + Id(9), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(4), + ), + CheckingExpression( + AstId(38), + ), + ], +} +CheckError { + kind: MissingPatterns { + patterns: [ + Id(9), + ], + }, + crumbs: [ + TermDeclaration( + Idx::(6), + ), + CheckingExpression( + AstId(100), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(7), + t2: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(6), + ), + CheckingExpression( + AstId(100), + ), + CheckingExpression( + AstId(108), + ), + CheckingExpression( + AstId(117), + ), + CheckingExpression( + AstId(120), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/204_coercible_phantom/Main.purs b/tests-integration/fixtures/checking2/204_coercible_phantom/Main.purs new file mode 100644 index 00000000..a0ac3b63 --- /dev/null +++ b/tests-integration/fixtures/checking2/204_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/checking2/204_coercible_phantom/Main.snap b/tests-integration/fixtures/checking2/204_coercible_phantom/Main.snap new file mode 100644 index 00000000..d3f49958 --- /dev/null +++ b/tests-integration/fixtures/checking2/204_coercible_phantom/Main.snap @@ -0,0 +1,17 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (t1 :: Type) (a :: (t1 :: Type)). Proxy @(t1 :: Type) (a :: (t1 :: Type)) +coerceProxy :: + forall (t5 :: Type) (t4 :: Type) (a :: (t5 :: Type)) (b :: (t4 :: Type)). + Proxy @(t5 :: Type) (a :: (t5 :: Type)) -> Proxy @(t4 :: Type) (b :: (t4 :: Type)) +coerceProxyIntString :: Proxy @Type Int -> Proxy @Type String + +Types +Proxy :: forall (t1 :: Type). (t1 :: Type) -> Type + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking2/205_coercible_representational/Main.purs b/tests-integration/fixtures/checking2/205_coercible_representational/Main.purs new file mode 100644 index 00000000..3af53be0 --- /dev/null +++ b/tests-integration/fixtures/checking2/205_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/checking2/205_coercible_representational/Main.snap b/tests-integration/fixtures/checking2/205_coercible_representational/Main.snap new file mode 100644 index 00000000..ba465de5 --- /dev/null +++ b/tests-integration/fixtures/checking2/205_coercible_representational/Main.snap @@ -0,0 +1,23 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe (a :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +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 + +Roles +Maybe = [Representational] +Age = [] +UserId = [] diff --git a/tests-integration/fixtures/checking2/206_coercible_array/Main.purs b/tests-integration/fixtures/checking2/206_coercible_array/Main.purs new file mode 100644 index 00000000..290137fa --- /dev/null +++ b/tests-integration/fixtures/checking2/206_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/checking2/206_coercible_array/Main.snap b/tests-integration/fixtures/checking2/206_coercible_array/Main.snap new file mode 100644 index 00000000..cfba3bd6 --- /dev/null +++ b/tests-integration/fixtures/checking2/206_coercible_array/Main.snap @@ -0,0 +1,15 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Age :: Int -> Age +coerceArray :: Array Age -> Array Int +coerceArrayReverse :: Array Int -> Array Age + +Types +Age :: Type + +Roles +Age = [] diff --git a/tests-integration/fixtures/checking2/207_coercible_record/Main.purs b/tests-integration/fixtures/checking2/207_coercible_record/Main.purs new file mode 100644 index 00000000..42dc5819 --- /dev/null +++ b/tests-integration/fixtures/checking2/207_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/checking2/207_coercible_record/Main.snap b/tests-integration/fixtures/checking2/207_coercible_record/Main.snap new file mode 100644 index 00000000..4b40c09b --- /dev/null +++ b/tests-integration/fixtures/checking2/207_coercible_record/Main.snap @@ -0,0 +1,19 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +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 + +Roles +Age = [] +UserId = [] diff --git a/tests-integration/fixtures/checking2/208_coercible_transitivity/Main.purs b/tests-integration/fixtures/checking2/208_coercible_transitivity/Main.purs new file mode 100644 index 00000000..9cd68596 --- /dev/null +++ b/tests-integration/fixtures/checking2/208_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/checking2/208_coercible_transitivity/Main.snap b/tests-integration/fixtures/checking2/208_coercible_transitivity/Main.snap new file mode 100644 index 00000000..fc5ad5fc --- /dev/null +++ b/tests-integration/fixtures/checking2/208_coercible_transitivity/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +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 + +Roles +Age = [] +Years = [] diff --git a/tests-integration/fixtures/checking2/209_coercible_different_heads_error/Main.purs b/tests-integration/fixtures/checking2/209_coercible_different_heads_error/Main.purs new file mode 100644 index 00000000..e4945c35 --- /dev/null +++ b/tests-integration/fixtures/checking2/209_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/checking2/209_coercible_different_heads_error/Main.snap b/tests-integration/fixtures/checking2/209_coercible_different_heads_error/Main.snap new file mode 100644 index 00000000..c576118c --- /dev/null +++ b/tests-integration/fixtures/checking2/209_coercible_different_heads_error/Main.snap @@ -0,0 +1,27 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe (a :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Left :: forall (a :: Type) (b :: Type). (a :: Type) -> Either (a :: Type) (b :: Type) +Right :: forall (a :: Type) (b :: Type). (b :: Type) -> Either (a :: Type) (b :: Type) +coerceDifferent :: Maybe Int -> Either Int String + +Types +Maybe :: Type -> Type +Either :: Type -> Type -> Type + +Roles +Maybe = [Representational] +Either = [Representational, Representational] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/210_coercible_nominal/Main.purs b/tests-integration/fixtures/checking2/210_coercible_nominal/Main.purs new file mode 100644 index 00000000..b42e1690 --- /dev/null +++ b/tests-integration/fixtures/checking2/210_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/checking2/210_coercible_nominal/Main.snap b/tests-integration/fixtures/checking2/210_coercible_nominal/Main.snap new file mode 100644 index 00000000..3911bcdb --- /dev/null +++ b/tests-integration/fixtures/checking2/210_coercible_nominal/Main.snap @@ -0,0 +1,22 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +coerceNominalSame :: Nominal Int -> Nominal Int +coerceNominalDifferent :: Nominal Int -> Nominal String + +Types +Nominal :: Type -> Type + +Roles +Nominal = [Nominal] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/211_coercible_newtype_hidden/Lib.purs b/tests-integration/fixtures/checking2/211_coercible_newtype_hidden/Lib.purs new file mode 100644 index 00000000..4eb11d87 --- /dev/null +++ b/tests-integration/fixtures/checking2/211_coercible_newtype_hidden/Lib.purs @@ -0,0 +1,3 @@ +module Lib (HiddenAge) where + +newtype HiddenAge = HiddenAge Int diff --git a/tests-integration/fixtures/checking2/211_coercible_newtype_hidden/Main.purs b/tests-integration/fixtures/checking2/211_coercible_newtype_hidden/Main.purs new file mode 100644 index 00000000..80480037 --- /dev/null +++ b/tests-integration/fixtures/checking2/211_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/checking2/211_coercible_newtype_hidden/Main.snap b/tests-integration/fixtures/checking2/211_coercible_newtype_hidden/Main.snap new file mode 100644 index 00000000..b2700868 --- /dev/null +++ b/tests-integration/fixtures/checking2/211_coercible_newtype_hidden/Main.snap @@ -0,0 +1,46 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +coerceHidden :: Int -> HiddenAge +coerceQualified :: Int -> HiddenAge + +Types + +Errors +CheckError { + kind: CoercibleConstructorNotInScope { + file_id: Idx::(39), + item_id: Idx::(0), + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [], +} +CheckError { + kind: CoercibleConstructorNotInScope { + file_id: Idx::(39), + item_id: Idx::(0), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/212_coercible_newtype_qualified/Lib.purs b/tests-integration/fixtures/checking2/212_coercible_newtype_qualified/Lib.purs new file mode 100644 index 00000000..a4ad0b02 --- /dev/null +++ b/tests-integration/fixtures/checking2/212_coercible_newtype_qualified/Lib.purs @@ -0,0 +1,3 @@ +module Lib (Age(..)) where + +newtype Age = Age Int diff --git a/tests-integration/fixtures/checking2/212_coercible_newtype_qualified/Main.purs b/tests-integration/fixtures/checking2/212_coercible_newtype_qualified/Main.purs new file mode 100644 index 00000000..7bcc7502 --- /dev/null +++ b/tests-integration/fixtures/checking2/212_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/checking2/212_coercible_newtype_qualified/Main.snap b/tests-integration/fixtures/checking2/212_coercible_newtype_qualified/Main.snap new file mode 100644 index 00000000..064b70ba --- /dev/null +++ b/tests-integration/fixtures/checking2/212_coercible_newtype_qualified/Main.snap @@ -0,0 +1,10 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +coerceQualified :: Int -> Age +unwrapQualified :: Age -> Int + +Types diff --git a/tests-integration/fixtures/checking2/213_coercible_newtype_open_hidden/Lib.purs b/tests-integration/fixtures/checking2/213_coercible_newtype_open_hidden/Lib.purs new file mode 100644 index 00000000..4eb11d87 --- /dev/null +++ b/tests-integration/fixtures/checking2/213_coercible_newtype_open_hidden/Lib.purs @@ -0,0 +1,3 @@ +module Lib (HiddenAge) where + +newtype HiddenAge = HiddenAge Int diff --git a/tests-integration/fixtures/checking2/213_coercible_newtype_open_hidden/Main.purs b/tests-integration/fixtures/checking2/213_coercible_newtype_open_hidden/Main.purs new file mode 100644 index 00000000..86b390c1 --- /dev/null +++ b/tests-integration/fixtures/checking2/213_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/checking2/213_coercible_newtype_open_hidden/Main.snap b/tests-integration/fixtures/checking2/213_coercible_newtype_open_hidden/Main.snap new file mode 100644 index 00000000..ac0fe0eb --- /dev/null +++ b/tests-integration/fixtures/checking2/213_coercible_newtype_open_hidden/Main.snap @@ -0,0 +1,28 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +coerceOpen :: Int -> HiddenAge + +Types + +Errors +CheckError { + kind: CoercibleConstructorNotInScope { + file_id: Idx::(39), + item_id: Idx::(0), + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + ], +} +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/214_coercible_nested_records/Main.purs b/tests-integration/fixtures/checking2/214_coercible_nested_records/Main.purs new file mode 100644 index 00000000..75eec92f --- /dev/null +++ b/tests-integration/fixtures/checking2/214_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/checking2/214_coercible_nested_records/Main.snap b/tests-integration/fixtures/checking2/214_coercible_nested_records/Main.snap new file mode 100644 index 00000000..49bf8ebf --- /dev/null +++ b/tests-integration/fixtures/checking2/214_coercible_nested_records/Main.snap @@ -0,0 +1,34 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +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 -> RawCompany +fullyWrap :: RawCompany -> Company +unwrapPerson :: Person -> RawPerson +nestedFieldCoerce :: { person :: Person } -> { person :: RawPerson } +arrayOfRecords :: Array { name :: Name } -> Array { name :: String } + +Types +Name :: Type +Age :: Type +Person :: Type +Company :: Type +RawPerson :: Type +RawCompany :: Type + +Synonyms +type RawPerson = { age :: Int, name :: String } +type RawCompany = { ceo :: RawPerson, name :: String } + +Roles +Name = [] +Age = [] +Person = [] +Company = [] diff --git a/tests-integration/fixtures/checking2/215_coercible_higher_kinded_error/Main.purs b/tests-integration/fixtures/checking2/215_coercible_higher_kinded_error/Main.purs new file mode 100644 index 00000000..9702dacc --- /dev/null +++ b/tests-integration/fixtures/checking2/215_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/checking2/215_coercible_higher_kinded_error/Main.snap b/tests-integration/fixtures/checking2/215_coercible_higher_kinded_error/Main.snap new file mode 100644 index 00000000..f38793c0 --- /dev/null +++ b/tests-integration/fixtures/checking2/215_coercible_higher_kinded_error/Main.snap @@ -0,0 +1,29 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Nothing :: forall (a :: Type). Maybe (a :: Type) +Just :: forall (a :: Type). (a :: Type) -> Maybe (a :: Type) +Nil :: forall (a :: Type). List (a :: Type) +Cons :: forall (a :: Type). (a :: Type) -> List (a :: Type) -> List (a :: Type) +coerceContainerDifferent :: Container Maybe -> Container List + +Types +Maybe :: Type -> Type +List :: Type -> Type +Container :: (Type -> Type) -> Type + +Roles +Maybe = [Representational] +List = [Representational] +Container = [Representational] + +Errors +CheckError { + kind: NoInstanceFound { + constraint: Id(7), + }, + crumbs: [], +} diff --git a/tests-integration/fixtures/checking2/216_coercible_higher_kinded_multi/Main.purs b/tests-integration/fixtures/checking2/216_coercible_higher_kinded_multi/Main.purs new file mode 100644 index 00000000..d335a1b3 --- /dev/null +++ b/tests-integration/fixtures/checking2/216_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/checking2/216_coercible_higher_kinded_multi/Main.snap b/tests-integration/fixtures/checking2/216_coercible_higher_kinded_multi/Main.snap new file mode 100644 index 00000000..b98af321 --- /dev/null +++ b/tests-integration/fixtures/checking2/216_coercible_higher_kinded_multi/Main.snap @@ -0,0 +1,23 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Left :: forall (a :: Type) (b :: Type). (a :: Type) -> Either (a :: Type) (b :: Type) +Right :: forall (a :: Type) (b :: Type). (b :: Type) -> Either (a :: Type) (b :: Type) +EitherAlias :: + forall (a :: Type) (b :: Type). + Either (a :: Type) (b :: Type) -> EitherAlias (a :: Type) (b :: Type) +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) -> Type + +Roles +Either = [Representational, Representational] +EitherAlias = [Representational, Representational] +Container = [Representational] diff --git a/tests-integration/fixtures/checking2/217_coercible_higher_kinded_polykinded/Main.purs b/tests-integration/fixtures/checking2/217_coercible_higher_kinded_polykinded/Main.purs new file mode 100644 index 00000000..4d0535d2 --- /dev/null +++ b/tests-integration/fixtures/checking2/217_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/checking2/217_coercible_higher_kinded_polykinded/Main.snap b/tests-integration/fixtures/checking2/217_coercible_higher_kinded_polykinded/Main.snap new file mode 100644 index 00000000..f7a3a9db --- /dev/null +++ b/tests-integration/fixtures/checking2/217_coercible_higher_kinded_polykinded/Main.snap @@ -0,0 +1,28 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Just :: + forall (k :: Type) (n :: (k :: Type)) (a :: Type). + (a :: Type) -> Maybe @(k :: Type) (n :: (k :: Type)) (a :: Type) +Nothing :: + forall (k :: Type) (n :: (k :: Type)) (a :: Type). + Maybe @(k :: Type) (n :: (k :: Type)) (a :: Type) +MaybeAlias :: + forall (k :: Type) (n :: (k :: Type)) (a :: Type). + Maybe @(k :: Type) (n :: (k :: Type)) (a :: Type) -> + MaybeAlias @(k :: Type) (n :: (k :: Type)) (a :: Type) +coerceContainer :: Container (Maybe @Type) -> Container (MaybeAlias @Type) +coerceContainerReverse :: Container (MaybeAlias @Type) -> Container (Maybe @Type) + +Types +Maybe :: forall (k :: Type). (k :: Type) -> Type -> Type +MaybeAlias :: forall (k :: Type). (k :: Type) -> Type -> Type +Container :: (Type -> Type -> Type) -> Type + +Roles +Maybe = [Phantom, Representational] +MaybeAlias = [Representational, Representational] +Container = [Representational] diff --git a/tests-integration/fixtures/checking2/218_coercible_function_decomposition/Main.purs b/tests-integration/fixtures/checking2/218_coercible_function_decomposition/Main.purs new file mode 100644 index 00000000..841daa03 --- /dev/null +++ b/tests-integration/fixtures/checking2/218_coercible_function_decomposition/Main.purs @@ -0,0 +1,29 @@ +module Main where + +import Data.Newtype (class Newtype) +import Safe.Coerce (class Coercible, coerce) + +newtype Age = Age Int + +derive instance Newtype Age _ + +coerceFn :: (Age -> Int) -> (Int -> Age) +coerceFn = coerce + +over :: forall t a s b. Newtype t a => Newtype s b => (a -> t) -> (a -> b) -> t -> s +over _ = coerce + +under :: forall t a s b. Newtype t a => Newtype s b => (a -> t) -> (t -> s) -> a -> b +under _ = coerce + +alaF + :: forall f g t a s b + . Coercible (f t) (f a) + => Coercible (g s) (g b) + => Newtype t a + => Newtype s b + => (a -> t) + -> (f t -> g s) + -> f a + -> g b +alaF _ = coerce diff --git a/tests-integration/fixtures/checking2/218_coercible_function_decomposition/Main.snap b/tests-integration/fixtures/checking2/218_coercible_function_decomposition/Main.snap new file mode 100644 index 00000000..b587dbfe --- /dev/null +++ b/tests-integration/fixtures/checking2/218_coercible_function_decomposition/Main.snap @@ -0,0 +1,37 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Age :: Int -> Age +coerceFn :: (Age -> Int) -> Int -> Age +over :: + forall (t :: Type) (a :: Type) (s :: Type) (b :: Type). + Newtype @Type (t :: Type) (a :: Type) => + Newtype @Type (s :: Type) (b :: Type) => + ((a :: Type) -> (t :: Type)) -> ((a :: Type) -> (b :: Type)) -> (t :: Type) -> (s :: Type) +under :: + forall (t :: Type) (a :: Type) (s :: Type) (b :: Type). + Newtype @Type (t :: Type) (a :: Type) => + Newtype @Type (s :: Type) (b :: Type) => + ((a :: Type) -> (t :: Type)) -> ((t :: Type) -> (s :: Type)) -> (a :: Type) -> (b :: Type) +alaF :: + forall (t14 :: Type) (f :: Type -> Type) (g :: (t14 :: Type) -> Type) (t :: Type) (a :: Type) + (s :: (t14 :: Type)) (b :: (t14 :: Type)). + Coercible @Type ((f :: Type -> Type) (t :: Type)) ((f :: Type -> Type) (a :: Type)) => + Coercible @Type + ((g :: (t14 :: Type) -> Type) (s :: (t14 :: Type))) + ((g :: (t14 :: Type) -> Type) (b :: (t14 :: Type))) => + Newtype @Type (t :: Type) (a :: Type) => + Newtype @(t14 :: Type) (s :: (t14 :: Type)) (b :: (t14 :: Type)) => + ((a :: Type) -> (t :: Type)) -> + ((f :: Type -> Type) (t :: Type) -> (g :: (t14 :: Type) -> Type) (s :: (t14 :: Type))) -> + (f :: Type -> Type) (a :: Type) -> + (g :: (t14 :: Type) -> Type) (b :: (t14 :: Type)) + +Types +Age :: Type + +Roles +Age = [] diff --git a/tests-integration/fixtures/checking2/219_derive_newtype_not_constructor/Main.purs b/tests-integration/fixtures/checking2/219_derive_newtype_not_constructor/Main.purs new file mode 100644 index 00000000..801d8b76 --- /dev/null +++ b/tests-integration/fixtures/checking2/219_derive_newtype_not_constructor/Main.purs @@ -0,0 +1,5 @@ +module Main where + +import Data.Show (class Show) + +derive newtype instance Show (Int -> Int) diff --git a/tests-integration/fixtures/checking2/219_derive_newtype_not_constructor/Main.snap b/tests-integration/fixtures/checking2/219_derive_newtype_not_constructor/Main.snap new file mode 100644 index 00000000..75c7ab0f --- /dev/null +++ b/tests-integration/fixtures/checking2/219_derive_newtype_not_constructor/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types + +Errors +CheckError { + kind: CannotDeriveForType { + type_message: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/220_derive_newtype_class_not_constructor/Main.purs b/tests-integration/fixtures/checking2/220_derive_newtype_class_not_constructor/Main.purs new file mode 100644 index 00000000..eb2ece06 --- /dev/null +++ b/tests-integration/fixtures/checking2/220_derive_newtype_class_not_constructor/Main.purs @@ -0,0 +1,5 @@ +module Main where + +import Data.Newtype (class Newtype) + +derive instance Newtype (Int -> Int) _ diff --git a/tests-integration/fixtures/checking2/220_derive_newtype_class_not_constructor/Main.snap b/tests-integration/fixtures/checking2/220_derive_newtype_class_not_constructor/Main.snap new file mode 100644 index 00000000..ae3feb6c --- /dev/null +++ b/tests-integration/fixtures/checking2/220_derive_newtype_class_not_constructor/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types + +Errors +CheckError { + kind: CannotDeriveForType { + type_message: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/222_derive_newtype_not_local/Lib.purs b/tests-integration/fixtures/checking2/222_derive_newtype_not_local/Lib.purs new file mode 100644 index 00000000..07b9c606 --- /dev/null +++ b/tests-integration/fixtures/checking2/222_derive_newtype_not_local/Lib.purs @@ -0,0 +1,3 @@ +module Lib where + +newtype Wrapper = Wrapper Int diff --git a/tests-integration/fixtures/checking2/222_derive_newtype_not_local/Main.purs b/tests-integration/fixtures/checking2/222_derive_newtype_not_local/Main.purs new file mode 100644 index 00000000..ec294c25 --- /dev/null +++ b/tests-integration/fixtures/checking2/222_derive_newtype_not_local/Main.purs @@ -0,0 +1,6 @@ +module Main where + +import Data.Show (class Show) +import Lib (Wrapper) + +derive newtype instance Show Wrapper diff --git a/tests-integration/fixtures/checking2/222_derive_newtype_not_local/Main.snap b/tests-integration/fixtures/checking2/222_derive_newtype_not_local/Main.snap new file mode 100644 index 00000000..4e8b0a19 --- /dev/null +++ b/tests-integration/fixtures/checking2/222_derive_newtype_not_local/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types + +Errors +CheckError { + kind: NonLocalNewtype { + type_message: Id(7), + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/223_derive_newtype_class_not_local/Lib.purs b/tests-integration/fixtures/checking2/223_derive_newtype_class_not_local/Lib.purs new file mode 100644 index 00000000..07b9c606 --- /dev/null +++ b/tests-integration/fixtures/checking2/223_derive_newtype_class_not_local/Lib.purs @@ -0,0 +1,3 @@ +module Lib where + +newtype Wrapper = Wrapper Int diff --git a/tests-integration/fixtures/checking2/223_derive_newtype_class_not_local/Main.purs b/tests-integration/fixtures/checking2/223_derive_newtype_class_not_local/Main.purs new file mode 100644 index 00000000..d5229377 --- /dev/null +++ b/tests-integration/fixtures/checking2/223_derive_newtype_class_not_local/Main.purs @@ -0,0 +1,6 @@ +module Main where + +import Data.Newtype (class Newtype) +import Lib (Wrapper) + +derive instance Newtype Wrapper _ diff --git a/tests-integration/fixtures/checking2/223_derive_newtype_class_not_local/Main.snap b/tests-integration/fixtures/checking2/223_derive_newtype_class_not_local/Main.snap new file mode 100644 index 00000000..bdca8411 --- /dev/null +++ b/tests-integration/fixtures/checking2/223_derive_newtype_class_not_local/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types + +Errors +CheckError { + kind: NonLocalNewtype { + type_message: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/224_derive_newtype_insufficient_params/Main.purs b/tests-integration/fixtures/checking2/224_derive_newtype_insufficient_params/Main.purs new file mode 100644 index 00000000..a1574961 --- /dev/null +++ b/tests-integration/fixtures/checking2/224_derive_newtype_insufficient_params/Main.purs @@ -0,0 +1,5 @@ +module Main where + +import Data.Show (class Show) + +derive newtype instance Show diff --git a/tests-integration/fixtures/checking2/224_derive_newtype_insufficient_params/Main.snap b/tests-integration/fixtures/checking2/224_derive_newtype_insufficient_params/Main.snap new file mode 100644 index 00000000..82bb73dd --- /dev/null +++ b/tests-integration/fixtures/checking2/224_derive_newtype_insufficient_params/Main.snap @@ -0,0 +1,34 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types + +Errors +CheckError { + kind: DeriveInvalidArity { + class_file: Idx::(30), + class_id: Idx::(0), + expected: 1, + actual: 0, + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(7), + t2: Id(8), + }, + crumbs: [ + TermDeclaration( + Idx::(0), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/225_derive_newtype_class_insufficient_params/Main.purs b/tests-integration/fixtures/checking2/225_derive_newtype_class_insufficient_params/Main.purs new file mode 100644 index 00000000..99e44ce1 --- /dev/null +++ b/tests-integration/fixtures/checking2/225_derive_newtype_class_insufficient_params/Main.purs @@ -0,0 +1,7 @@ +module Main where + +import Data.Newtype (class Newtype) + +newtype Wrapper = Wrapper Int + +derive instance Newtype Wrapper diff --git a/tests-integration/fixtures/checking2/225_derive_newtype_class_insufficient_params/Main.snap b/tests-integration/fixtures/checking2/225_derive_newtype_class_insufficient_params/Main.snap new file mode 100644 index 00000000..e02cdf7f --- /dev/null +++ b/tests-integration/fixtures/checking2/225_derive_newtype_class_insufficient_params/Main.snap @@ -0,0 +1,39 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Wrapper :: Int -> Wrapper + +Types +Wrapper :: Type + +Roles +Wrapper = [] + +Errors +CheckError { + kind: DeriveInvalidArity { + class_file: Idx::(24), + class_id: Idx::(0), + expected: 2, + actual: 1, + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} +CheckError { + kind: CannotUnify { + t1: Id(8), + t2: Id(9), + }, + crumbs: [ + TermDeclaration( + Idx::(1), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/226_equation_synonym_expansion/Main.purs b/tests-integration/fixtures/checking2/226_equation_synonym_expansion/Main.purs new file mode 100644 index 00000000..4082ba07 --- /dev/null +++ b/tests-integration/fixtures/checking2/226_equation_synonym_expansion/Main.purs @@ -0,0 +1,11 @@ +module Main where + +type NaturalTransformation :: (Type -> Type) -> (Type -> Type) -> Type +type NaturalTransformation f g = forall a. f a -> g a + +infixr 5 type NaturalTransformation as ~> + +foreign import unsafeCoerce :: forall a b. a -> b + +test :: forall f. Array ~> f +test a = unsafeCoerce a diff --git a/tests-integration/fixtures/checking2/226_equation_synonym_expansion/Main.snap b/tests-integration/fixtures/checking2/226_equation_synonym_expansion/Main.snap new file mode 100644 index 00000000..d6b06870 --- /dev/null +++ b/tests-integration/fixtures/checking2/226_equation_synonym_expansion/Main.snap @@ -0,0 +1,15 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +unsafeCoerce :: forall (a :: Type) (b :: Type). (a :: Type) -> (b :: Type) +test :: forall (f :: Type -> Type). NaturalTransformation Array (f :: Type -> Type) + +Types +NaturalTransformation :: (Type -> Type) -> (Type -> Type) -> Type +~> :: (Type -> Type) -> (Type -> Type) -> Type + +Synonyms +type NaturalTransformation f g = forall (a :: Type). (f :: Type -> Type) (a :: Type) -> (g :: Type -> Type) (a :: Type) diff --git a/tests-integration/fixtures/checking2/227_derive_newtype_invalid_skolem_layout/Main.purs b/tests-integration/fixtures/checking2/227_derive_newtype_invalid_skolem_layout/Main.purs new file mode 100644 index 00000000..6222d1a4 --- /dev/null +++ b/tests-integration/fixtures/checking2/227_derive_newtype_invalid_skolem_layout/Main.purs @@ -0,0 +1,13 @@ +module Main where + +class Empty f where + empty :: f Int + +instance Empty Array where + empty = [] + +newtype Vector n a = Vector (Array a) +derive newtype instance Empty (Vector n) + +newtype InvalidVector a n = InvalidVector (Array a) +derive newtype instance Empty (InvalidVector Int) diff --git a/tests-integration/fixtures/checking2/227_derive_newtype_invalid_skolem_layout/Main.snap b/tests-integration/fixtures/checking2/227_derive_newtype_invalid_skolem_layout/Main.snap new file mode 100644 index 00000000..d2813993 --- /dev/null +++ b/tests-integration/fixtures/checking2/227_derive_newtype_invalid_skolem_layout/Main.snap @@ -0,0 +1,39 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +empty :: forall (f :: Type -> Type). Empty (f :: Type -> Type) => (f :: Type -> Type) Int +Vector :: + forall (t3 :: Type) (n :: (t3 :: Type)) (a :: Type). + Array (a :: Type) -> Vector @(t3 :: Type) (n :: (t3 :: Type)) (a :: Type) +InvalidVector :: + forall (t6 :: Type) (a :: Type) (n :: (t6 :: Type)). + Array (a :: Type) -> InvalidVector @(t6 :: Type) (a :: Type) (n :: (t6 :: Type)) + +Types +Empty :: (Type -> Type) -> Constraint +Vector :: forall (t3 :: Type). (t3 :: Type) -> Type -> Type +InvalidVector :: forall (t6 :: Type). Type -> (t6 :: Type) -> Type + +Classes +class forall (f :: Type -> Type). Empty (f :: Type -> Type) + empty :: forall (f :: Type -> Type). Empty (f :: Type -> Type) => (f :: Type -> Type) Int + +Instances +instance Empty Array + +Roles +Vector = [Phantom, Representational] +InvalidVector = [Representational, Phantom] + +Errors +CheckError { + kind: InvalidNewtypeDeriveSkolemArguments, + crumbs: [ + TermDeclaration( + Idx::(5), + ), + ], +} diff --git a/tests-integration/fixtures/checking2/228_instance_implicit_variable_freshening/Main.purs b/tests-integration/fixtures/checking2/228_instance_implicit_variable_freshening/Main.purs new file mode 100644 index 00000000..1c0155ac --- /dev/null +++ b/tests-integration/fixtures/checking2/228_instance_implicit_variable_freshening/Main.purs @@ -0,0 +1,10 @@ +module Main where + +data Proxy :: forall k. k -> Type +data Proxy a = Proxy + +class Top a where + top :: a + +instance topProxy :: Top (Proxy a) where + top = Proxy :: Proxy a diff --git a/tests-integration/fixtures/checking2/228_instance_implicit_variable_freshening/Main.snap b/tests-integration/fixtures/checking2/228_instance_implicit_variable_freshening/Main.snap new file mode 100644 index 00000000..4df934ed --- /dev/null +++ b/tests-integration/fixtures/checking2/228_instance_implicit_variable_freshening/Main.snap @@ -0,0 +1,22 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +Proxy :: forall (k :: Type) (a :: (k :: Type)). Proxy @(k :: Type) (a :: (k :: Type)) +top :: forall (a :: Type). Top (a :: Type) => (a :: Type) + +Types +Proxy :: forall (k :: Type). (k :: Type) -> Type +Top :: Type -> Constraint + +Classes +class forall (a :: Type). Top (a :: Type) + top :: forall (a :: Type). Top (a :: Type) => (a :: Type) + +Instances +instance forall (t4 :: Type) (t3 :: (t4 :: Type)). Top (Proxy @(t4 :: Type) (t3 :: (t4 :: Type))) + +Roles +Proxy = [Phantom] diff --git a/tests-integration/fixtures/checking2/229_type_operator_synonym_partial_arguments/Main.purs b/tests-integration/fixtures/checking2/229_type_operator_synonym_partial_arguments/Main.purs new file mode 100644 index 00000000..67ba5ac1 --- /dev/null +++ b/tests-integration/fixtures/checking2/229_type_operator_synonym_partial_arguments/Main.purs @@ -0,0 +1,18 @@ +module Main where + +type RowApply :: forall k. (Row k -> Row k) -> Row k -> Row k +type RowApply f a = f a + +infixr 0 type RowApply as + + +type AddField :: Type -> Row Type -> Row Type +type AddField a r = + ( field :: a + | r + ) + +type Bad :: Type -> Row Type +type Bad a = AddField a + () + +type Good :: Type -> Row Type +type Good a = RowApply (AddField a) () diff --git a/tests-integration/fixtures/checking2/229_type_operator_synonym_partial_arguments/Main.snap b/tests-integration/fixtures/checking2/229_type_operator_synonym_partial_arguments/Main.snap new file mode 100644 index 00000000..3f35256a --- /dev/null +++ b/tests-integration/fixtures/checking2/229_type_operator_synonym_partial_arguments/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms + +Types +RowApply :: + forall (k :: Type). (Row (k :: Type) -> Row (k :: Type)) -> Row (k :: Type) -> Row (k :: Type) ++ :: forall (k :: Type). (Row (k :: Type) -> Row (k :: Type)) -> Row (k :: Type) -> Row (k :: Type) +AddField :: Type -> Row Type -> Row Type +Bad :: Type -> Row Type +Good :: Type -> Row Type + +Synonyms +type RowApply f a = (f :: Row (k :: Type) -> Row (k :: Type)) (a :: Row (k :: Type)) +type AddField a r = ( field :: (a :: Type) | (r :: Row Type) ) +type Bad a = RowApply @Type (AddField (a :: Type)) () +type Good a = RowApply @Type (AddField (a :: Type)) () diff --git a/tests-integration/fixtures/checking2/230_type_synonym_higher_order/Main.purs b/tests-integration/fixtures/checking2/230_type_synonym_higher_order/Main.purs new file mode 100644 index 00000000..8ffb9753 --- /dev/null +++ b/tests-integration/fixtures/checking2/230_type_synonym_higher_order/Main.purs @@ -0,0 +1,16 @@ +module Main where + +type RowApply :: forall k. (Row k -> Row k) -> Row k -> Row k +type RowApply f a = f a + +type AddInt :: Row Type -> Row Type +type AddInt r = ( int :: Int | r ) + +type AddString :: Row Type -> Row Type +type AddString r = ( string :: String | r ) + +type Test :: Row Type +type Test = RowApply AddInt (RowApply AddString ()) + +test :: Record Test +test = { int: 42, string: "life" } diff --git a/tests-integration/fixtures/checking2/230_type_synonym_higher_order/Main.snap b/tests-integration/fixtures/checking2/230_type_synonym_higher_order/Main.snap new file mode 100644 index 00000000..21bcc581 --- /dev/null +++ b/tests-integration/fixtures/checking2/230_type_synonym_higher_order/Main.snap @@ -0,0 +1,20 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: {| Test } + +Types +RowApply :: + forall (k :: Type). (Row (k :: Type) -> Row (k :: Type)) -> Row (k :: Type) -> Row (k :: Type) +AddInt :: Row Type -> Row Type +AddString :: Row Type -> Row Type +Test :: Row Type + +Synonyms +type RowApply f a = (f :: Row (k :: Type) -> Row (k :: Type)) (a :: Row (k :: Type)) +type AddInt r = ( int :: Int | (r :: Row Type) ) +type AddString r = ( string :: String | (r :: Row Type) ) +type Test = RowApply @Type AddInt (RowApply @Type AddString ()) diff --git a/tests-integration/fixtures/checking2/231_type_synonym_higher_order_operator/Main.purs b/tests-integration/fixtures/checking2/231_type_synonym_higher_order_operator/Main.purs new file mode 100644 index 00000000..fe98b70d --- /dev/null +++ b/tests-integration/fixtures/checking2/231_type_synonym_higher_order_operator/Main.purs @@ -0,0 +1,18 @@ +module Main where + +type RowApply :: forall k. (Row k -> Row k) -> Row k -> Row k +type RowApply f a = f a + +infixr 0 type RowApply as + + +type AddInt :: Row Type -> Row Type +type AddInt r = ( int :: Int | r ) + +type AddString :: Row Type -> Row Type +type AddString r = ( string :: String | r ) + +type Test :: Row Type +type Test = AddInt + AddString + () + +test :: Record Test +test = { int: 42, string: "life" } diff --git a/tests-integration/fixtures/checking2/231_type_synonym_higher_order_operator/Main.snap b/tests-integration/fixtures/checking2/231_type_synonym_higher_order_operator/Main.snap new file mode 100644 index 00000000..7060e509 --- /dev/null +++ b/tests-integration/fixtures/checking2/231_type_synonym_higher_order_operator/Main.snap @@ -0,0 +1,21 @@ +--- +source: tests-integration/tests/checking2/generated.rs +assertion_line: 28 +expression: report +--- +Terms +test :: {| Test } + +Types +RowApply :: + forall (k :: Type). (Row (k :: Type) -> Row (k :: Type)) -> Row (k :: Type) -> Row (k :: Type) ++ :: forall (k :: Type). (Row (k :: Type) -> Row (k :: Type)) -> Row (k :: Type) -> Row (k :: Type) +AddInt :: Row Type -> Row Type +AddString :: Row Type -> Row Type +Test :: Row Type + +Synonyms +type RowApply f a = (f :: Row (k :: Type) -> Row (k :: Type)) (a :: Row (k :: Type)) +type AddInt r = ( int :: Int | (r :: Row Type) ) +type AddString r = ( string :: String | (r :: Row Type) ) +type Test = RowApply @Type AddInt (RowApply @Type AddString ()) diff --git a/tests-integration/src/generated/basic.rs b/tests-integration/src/generated/basic.rs index 21abe995..6f6a2d98 100644 --- a/tests-integration/src/generated/basic.rs +++ b/tests-integration/src/generated/basic.rs @@ -2,9 +2,12 @@ use std::fmt::Write; use analyzer::{QueryEngine, locate}; use checking::core::pretty; +use checking2::core::pretty as pretty2; +use checking2::{ExternalQueries, core as core2}; use diagnostics::{DiagnosticsContext, ToDiagnostics, format_rustc}; use files::FileId; use indexing::{ImportKind, TermItem, TypeItem, TypeItemId, TypeItemKind}; +use itertools::Itertools; use lowering::{ ExpressionKind, GraphNode, ImplicitTypeVariable, TermVariableResolution, TypeKind, TypeVariableResolution, @@ -12,130 +15,101 @@ use lowering::{ use rowan::ast::AstNode; use syntax::cst; -pub fn report_resolved(engine: &QueryEngine, id: FileId, name: &str) -> String { - let resolved = engine.resolved(id).unwrap(); +macro_rules! pos { + ($content:expr, $stabilized:expr, $id:expr) => {{ + let cst = $stabilized.ast_ptr($id).unwrap(); + let range = cst.syntax_node_ptr().text_range(); + let p = locate::offset_to_position($content, range.start()).unwrap(); + format!("{}:{}", p.line, p.character) + }}; +} - let mut buffer = String::default(); - writeln!(buffer, "module {name}").unwrap(); +fn heading(out: &mut String, title: &str) { + writeln!(out).unwrap(); + writeln!(out, "{title}").unwrap(); +} - writeln!(buffer).unwrap(); - writeln!(buffer, "Unqualified Imports:").unwrap(); - for import in resolved.unqualified.values().flatten() { - writeln!(buffer).unwrap(); - writeln!(buffer, "Terms:").unwrap(); - for (name, _, _, kind) in import.iter_terms() { +macro_rules! write_import_items { + ($out:expr, $title:expr, $iter:expr) => {{ + writeln!($out).unwrap(); + writeln!($out, "{}:", $title).unwrap(); + for (item_name, _, _, kind) in $iter { if matches!(kind, ImportKind::Hidden) { continue; } - writeln!(buffer, " - {name} is {kind:?}").unwrap(); + writeln!($out, " - {item_name} is {kind:?}").unwrap(); } + }}; +} - writeln!(buffer).unwrap(); - writeln!(buffer, "Types:").unwrap(); - for (name, _, _, kind) in import.iter_types() { - if matches!(kind, ImportKind::Hidden) { - continue; - } - writeln!(buffer, " - {name} is {kind:?}").unwrap(); - } +pub fn report_resolved(engine: &QueryEngine, id: FileId, name: &str) -> String { + let resolved = engine.resolved(id).unwrap(); - writeln!(buffer).unwrap(); - writeln!(buffer, "Classes:").unwrap(); - for (name, _, _, kind) in import.iter_classes() { - if matches!(kind, ImportKind::Hidden) { - continue; - } - writeln!(buffer, " - {name} is {kind:?}").unwrap(); - } + let mut out = String::default(); + writeln!(out, "module {name}").unwrap(); + + heading(&mut out, "Unqualified Imports:"); + for import in resolved.unqualified.values().flatten() { + write_import_items!(out, "Terms", import.iter_terms()); + write_import_items!(out, "Types", import.iter_types()); + write_import_items!(out, "Classes", import.iter_classes()); } - writeln!(buffer).unwrap(); - writeln!(buffer, "Qualified Imports:").unwrap(); - for (name, imports) in &resolved.qualified { + heading(&mut out, "Qualified Imports:"); + for (qualifier, imports) in &resolved.qualified { for import in imports { - writeln!(buffer).unwrap(); - writeln!(buffer, "{name} Terms:").unwrap(); - for (name, _, _, kind) in import.iter_terms() { - if matches!(kind, ImportKind::Hidden) { - continue; - } - writeln!(buffer, " - {name} is {kind:?}").unwrap(); - } - - writeln!(buffer).unwrap(); - writeln!(buffer, "{name} Types:").unwrap(); - for (name, _, _, kind) in import.iter_types() { - if matches!(kind, ImportKind::Hidden) { - continue; - } - writeln!(buffer, " - {name} is {kind:?}").unwrap(); - } - - writeln!(buffer).unwrap(); - writeln!(buffer, "{name} Classes:").unwrap(); - for (name, _, _, kind) in import.iter_classes() { - if matches!(kind, ImportKind::Hidden) { - continue; - } - writeln!(buffer, " - {name} is {kind:?}").unwrap(); - } + write_import_items!(out, format!("{qualifier} Terms"), import.iter_terms()); + write_import_items!(out, format!("{qualifier} Types"), import.iter_types()); + write_import_items!(out, format!("{qualifier} Classes"), import.iter_classes()); } } - writeln!(buffer).unwrap(); - writeln!(buffer, "Exported Terms:").unwrap(); + heading(&mut out, "Exported Terms:"); for (name, _, _) in resolved.exports.iter_terms() { - writeln!(buffer, " - {name}").unwrap(); + writeln!(out, " - {name}").unwrap(); } - writeln!(buffer).unwrap(); - writeln!(buffer, "Exported Types:").unwrap(); + heading(&mut out, "Exported Types:"); for (name, _, _) in resolved.exports.iter_types() { - writeln!(buffer, " - {name}").unwrap(); + writeln!(out, " - {name}").unwrap(); } - writeln!(buffer).unwrap(); - writeln!(buffer, "Exported Classes:").unwrap(); + heading(&mut out, "Exported Classes:"); for (name, _, _) in resolved.exports.iter_classes() { - writeln!(buffer, " - {name}").unwrap(); + writeln!(out, " - {name}").unwrap(); } - writeln!(buffer).unwrap(); - writeln!(buffer, "Local Terms:").unwrap(); + heading(&mut out, "Local Terms:"); for (name, _, _) in resolved.locals.iter_terms() { - writeln!(buffer, " - {name}").unwrap(); + writeln!(out, " - {name}").unwrap(); } - writeln!(buffer).unwrap(); - writeln!(buffer, "Local Types:").unwrap(); + heading(&mut out, "Local Types:"); for (name, _, _) in resolved.locals.iter_types() { - writeln!(buffer, " - {name}").unwrap(); + writeln!(out, " - {name}").unwrap(); } - writeln!(buffer).unwrap(); - writeln!(buffer, "Local Classes:").unwrap(); + heading(&mut out, "Local Classes:"); for (name, _, _) in resolved.locals.iter_classes() { - writeln!(buffer, " - {name}").unwrap(); + writeln!(out, " - {name}").unwrap(); } - writeln!(buffer).unwrap(); - writeln!(buffer, "Class Members:").unwrap(); + heading(&mut out, "Class Members:"); let indexed = engine.indexed(id).unwrap(); 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 = 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(); + writeln!(out, " - {class_name}.{member_name}{locality}").unwrap(); } - writeln!(buffer).unwrap(); - writeln!(buffer, "Errors:").unwrap(); + heading(&mut out, "Errors:"); for error in &resolved.errors { - writeln!(buffer, " - {error:?}").unwrap(); + writeln!(out, " - {error:?}").unwrap(); } - buffer + out } pub fn report_lowered(engine: &QueryEngine, id: FileId, name: &str) -> String { @@ -149,35 +123,35 @@ pub fn report_lowered(engine: &QueryEngine, id: FileId, name: &str) -> String { let info = &lowered.info; let graph = &lowered.graph; - let mut buffer = String::default(); - writeln!(buffer, "module {name}").unwrap(); + let mut out = String::default(); + writeln!(out, "module {name}").unwrap(); - writeln!(buffer).unwrap(); - writeln!(buffer, "Expressions:").unwrap(); - writeln!(buffer).unwrap(); + writeln!(out).unwrap(); + writeln!(out, "Expressions:").unwrap(); + writeln!(out).unwrap(); for (expression_id, _) in info.iter_expression() { let Some(kind) = info.get_expression_kind(expression_id) else { continue; }; if let ExpressionKind::Variable { resolution, .. } = kind { - report_on_term( + write_term_resolution( &content, &stabilized, &module, info, - &mut buffer, + &mut out, expression_id, resolution, ); } else if let ExpressionKind::Record { record } = kind { for field in record.iter() { if let lowering::ExpressionRecordItem::RecordPun { resolution, .. } = field { - report_on_term( + write_term_resolution( &content, &stabilized, &module, info, - &mut buffer, + &mut out, expression_id, resolution, ); @@ -188,16 +162,7 @@ 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(); - let range = cst.syntax_node_ptr().text_range(); - let p = locate::offset_to_position(&content, range.start()).unwrap(); - format!("{}:{}", p.line, p.character) - }}; - } + writeln!(out, "\nTypes:\n").unwrap(); for (type_id, _) in info.iter_type() { let Some(TypeKind::Variable { resolution, .. }) = info.get_type_kind(type_id) else { @@ -208,42 +173,184 @@ pub fn report_lowered(engine: &QueryEngine, id: FileId, name: &str) -> String { let node = cst.syntax_node_ptr().to_node(module.syntax()); let text = node.text().to_string(); - writeln!(buffer, "{}@{}", text.trim(), pos!(type_id)).unwrap(); + writeln!(out, "{}@{}", text.trim(), pos!(&content, &stabilized, type_id)).unwrap(); match resolution { Some(TypeVariableResolution::Forall(id)) => { - writeln!(buffer, " -> forall@{}", pos!(*id)).unwrap(); + writeln!(out, " -> forall@{}", pos!(&content, &stabilized, *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(); + writeln!(out, " 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(); + writeln!(out, " introduces a constraint variable {name:?}").unwrap(); } else { - writeln!(buffer, " -> constraint variable {name:?}").unwrap(); + writeln!(out, " -> constraint variable {name:?}").unwrap(); for &tid in type_ids { - writeln!(buffer, " {}", pos!(tid)).unwrap(); + writeln!(out, " {}", pos!(&content, &stabilized, tid)).unwrap(); } } } None => { - writeln!(buffer, " -> nothing").unwrap(); + writeln!(out, " -> nothing").unwrap(); } } } - buffer + out } -fn report_on_term( +pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { + let indexed = engine.indexed(id).unwrap(); + let checked = engine.checked(id).unwrap(); + + let mut out = String::default(); + + write_checked_output(&mut out, engine, id, &indexed, &checked); + write_checked_diagnostics(&mut out, engine, id, &indexed, &checked); + + out +} + +pub fn report_checked2(engine: &QueryEngine, id: FileId) -> String { + let indexed = engine.indexed(id).unwrap(); + let checked = engine.checked2(id).unwrap(); + + let name_text = |name: core2::Name| -> String { + checked + .lookup_name(name) + .map(|id| engine.lookup_smol_str(id).to_string()) + .unwrap_or_else(|| name.as_text().to_string()) + }; + + let pretty = |type_id| pretty2::Pretty::new(engine, &checked).render(type_id); + + let pretty_signature = |name: &str, type_id| { + pretty2::Pretty::new(engine, &checked).signature(name).render(type_id) + }; + + let mut out = String::default(); + + writeln!(out, "Terms").unwrap(); + for (id, TermItem { name, .. }) in indexed.items.iter_terms() { + let Some(name) = name else { continue }; + let Some(kind) = checked.lookup_term(id) else { continue }; + let signature = pretty_signature(name, kind); + writeln!(out, "{signature}").unwrap(); + } + + writeln!(out, "\nTypes").unwrap(); + for (id, TypeItem { name, .. }) in indexed.items.iter_types() { + let Some(name) = name else { continue }; + let Some(kind) = checked.lookup_type(id) else { continue }; + let signature = pretty_signature(name, kind); + writeln!(out, "{signature}").unwrap(); + } + + if !checked.synonyms.is_empty() { + writeln!(out, "\nSynonyms").unwrap(); + } + for (id, TypeItem { name, .. }) in indexed.items.iter_types() { + let Some(name) = name else { continue }; + let Some(definition) = checked.lookup_synonym(id) else { continue }; + let replacement = pretty(definition.synonym); + let binders = definition.parameters.iter().map(|b| name_text(b.name)).collect_vec(); + let binders_formatted = + if binders.is_empty() { String::new() } else { format!(" {}", binders.join(" ")) }; + writeln!(out, "type {name}{binders_formatted} = {replacement}").unwrap(); + } + + if !checked.classes.is_empty() { + writeln!(out, "\nClasses").unwrap(); + } + for (id, TypeItem { .. }) in indexed.items.iter_types() { + let Some(class) = checked.lookup_class(id) else { continue }; + + let class_binders = + class.kind_binders.iter().chain(class.type_parameters.iter()).copied().collect_vec(); + + let mut class_head = class.canonical; + while let core2::Type::Forall(_, inner) = engine.lookup_type(class_head) { + class_head = inner; + } + + let canonical = pretty(class_head); + let forall_prefix = if class_binders.is_empty() { + String::new() + } else { + let binders = class_binders + .iter() + .map(|&binder_id| { + let binder = engine.lookup_forall_binder(binder_id); + let text = name_text(binder.name); + let kind = pretty(binder.kind); + format!("({text} :: {kind})") + }) + .join(" "); + format!("forall {binders}. ") + }; + + if class.superclasses.is_empty() { + writeln!(out, "class {forall_prefix}{canonical}").unwrap(); + } else { + let superclasses = + class.superclasses.iter().map(|&superclass| pretty(superclass)).join(", "); + writeln!(out, "class {forall_prefix}{superclasses} <= {canonical}").unwrap(); + } + + for &mid in &class.members { + let Some(member_name) = indexed.items[mid].name.as_deref() else { continue }; + let Some(member_type) = checked.lookup_term(mid) else { continue }; + let signature = pretty_signature(member_name, member_type); + writeln!(out, " {signature}").unwrap(); + } + } + + if !checked.instances.is_empty() { + writeln!(out, "\nInstances").unwrap(); + } + let mut instance_entries: Vec<_> = checked.instances.iter().collect(); + instance_entries.sort_by_key(|(id, _)| *id); + for (_instance_id, instance) in instance_entries { + let canonical = pretty(instance.canonical); + writeln!(out, "instance {canonical}").unwrap(); + } + + if !checked.roles.is_empty() { + writeln!(out, "\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!(out, "{name} = [{}]", roles_str.join(", ")).unwrap(); + } + + if !checked.errors.is_empty() { + writeln!(out, "\nErrors").unwrap(); + } + for error in &checked.errors { + writeln!(out, "{error:#?}").unwrap(); + } + + out +} + +fn write_term_resolution( content: &str, stabilized: &stabilizing::StabilizedModule, module: &cst::Module, info: &lowering::LoweringInfo, - buffer: &mut String, + out: &mut String, expression_id: lowering::ExpressionId, resolution: &Option, ) { @@ -252,80 +359,72 @@ fn report_on_term( let text = node.text().to_string(); let position = locate::offset_to_position(content, node.text_range().start()).unwrap(); - writeln!(buffer, "{}@{}:{}", text.trim(), position.line, position.character).unwrap(); - - macro_rules! pos { - ($id:expr) => {{ - let cst = stabilized.ast_ptr($id).unwrap(); - let range = cst.syntax_node_ptr().text_range(); - let p = locate::offset_to_position(content, range.start()).unwrap(); - format!("{}:{}", p.line, p.character) - }}; - } + writeln!(out, "{}@{}:{}", text.trim(), position.line, position.character).unwrap(); match resolution { Some(TermVariableResolution::Binder(id)) => { - writeln!(buffer, " -> binder@{}", pos!(*id)).unwrap(); + writeln!(out, " -> binder@{}", pos!(content, stabilized, *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, " -> signature@{}", pos!(sig)).unwrap(); + writeln!(out, " -> signature@{}", pos!(content, stabilized, sig)).unwrap(); } for eq in let_binding.equations.iter() { - writeln!(buffer, " -> equation@{}", pos!(*eq)).unwrap(); + writeln!(out, " -> equation@{}", pos!(content, stabilized, *eq)).unwrap(); } } Some(TermVariableResolution::RecordPun(id)) => { - writeln!(buffer, " -> record pun@{}", pos!(*id)).unwrap(); + writeln!(out, " -> record pun@{}", pos!(content, stabilized, *id)).unwrap(); } Some(TermVariableResolution::Reference(..)) => { - writeln!(buffer, " -> top-level").unwrap(); + writeln!(out, " -> top-level").unwrap(); } None => { - writeln!(buffer, " -> nothing").unwrap(); + writeln!(out, " -> nothing").unwrap(); } } } -pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { - let indexed = engine.indexed(id).unwrap(); - let checked = engine.checked(id).unwrap(); - - let mut snapshot = String::default(); - - writeln!(snapshot, "Terms").unwrap(); +fn write_checked_output( + out: &mut String, + engine: &QueryEngine, + id: FileId, + indexed: &indexing::IndexedModule, + checked: &checking::CheckedModule, +) { + writeln!(out, "Terms").unwrap(); for (id, TermItem { name, .. }) in indexed.items.iter_terms() { let Some(n) = name else { continue }; let Some(t) = checked.lookup_term(id) else { continue }; let signature = pretty::print_signature_global(engine, n, t); - writeln!(snapshot, "{signature}").unwrap(); + writeln!(out, "{signature}").unwrap(); } - writeln!(snapshot, "\nTypes").unwrap(); + writeln!(out, "\nTypes").unwrap(); for (id, TypeItem { name, .. }) in indexed.items.iter_types() { let Some(n) = name else { continue }; let Some(t) = checked.lookup_type(id) else { continue }; let signature = pretty::print_signature_global(engine, n, t); - writeln!(snapshot, "{signature}").unwrap(); + writeln!(out, "{signature}").unwrap(); } if !checked.synonyms.is_empty() { - writeln!(snapshot, "\nSynonyms").unwrap(); + writeln!(out, "\nSynonyms").unwrap(); } for (id, TypeItem { name, .. }) in indexed.items.iter_types() { let Some(name) = name else { continue }; let Some(group) = checked.lookup_synonym(id) else { continue }; let synonym = pretty::print_global(engine, group.synonym_type); - writeln!(snapshot, "{name} = {synonym}").unwrap(); - writeln!(snapshot, " Quantified = {}", group.quantified_variables).unwrap(); - writeln!(snapshot, " Kind = {}", group.kind_variables).unwrap(); - writeln!(snapshot, " Type = {}", group.type_variables).unwrap(); - writeln!(snapshot).unwrap(); + writeln!(out, "{name} = {synonym}").unwrap(); + writeln!(out, " Quantified = {}", group.quantified_variables).unwrap(); + writeln!(out, " Kind = {}", group.kind_variables).unwrap(); + writeln!(out, " Type = {}", group.type_variables).unwrap(); + writeln!(out).unwrap(); } if !checked.data.is_empty() { - writeln!(snapshot, "\nData").unwrap(); + writeln!(out, "\nData").unwrap(); } for (id, TypeItem { name, kind, .. }) in indexed.items.iter_types() { let (TypeItemKind::Data { .. } | TypeItemKind::Newtype { .. }) = kind else { @@ -333,14 +432,14 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { }; 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(); + writeln!(out, "{name}").unwrap(); + writeln!(out, " Quantified = {}", data.quantified_variables).unwrap(); + writeln!(out, " Kind = {}", data.kind_variables).unwrap(); + writeln!(out).unwrap(); } if !checked.roles.is_empty() { - writeln!(snapshot, "\nRoles").unwrap(); + writeln!(out, "\nRoles").unwrap(); } for (id, TypeItem { name, kind, .. }) in indexed.items.iter_types() { let (TypeItemKind::Data { .. } @@ -352,11 +451,11 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { 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(); + writeln!(out, "{name} = [{}]", roles_str.join(", ")).unwrap(); } if !checked.classes.is_empty() { - writeln!(snapshot, "\nClasses").unwrap(); + writeln!(out, "\nClasses").unwrap(); } for (type_id, TypeItem { name, kind, .. }) in indexed.items.iter_types() { let TypeItemKind::Class { .. } = kind else { continue }; @@ -365,7 +464,6 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { 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 { @@ -379,56 +477,60 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { class_line.push_str(name); - // Print class type variables with their kinds. for (name, &kind) in class.type_variable_names.iter().zip(class.type_variable_kinds.iter()) { let kind_str = pretty::print_global(engine, kind); class_line.push_str(&format!(" ({} :: {kind_str})", name.text)); } - writeln!(snapshot, "class {class_line}").unwrap(); + writeln!(out, "class {class_line}").unwrap(); } if !checked.instances.is_empty() { - writeln!(snapshot, "\nInstances").unwrap(); + writeln!(out, "\nInstances").unwrap(); } let mut instance_entries: Vec<_> = checked.instances.iter().collect(); - instance_entries.sort_by_key(|(id, _)| format!("{:?}", id)); + instance_entries.sort_by_key(|(id, _)| *id); for (_instance_id, instance) in instance_entries { - let class_name = resolve_class_name(engine, &indexed, id, instance.resolution); + 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(); + writeln!(out, "instance {forall_prefix}{head}").unwrap(); if let checking::core::InstanceKind::Chain { position, .. } = instance.kind { - writeln!(snapshot, " chain: {}", position).unwrap(); + writeln!(out, " chain: {}", position).unwrap(); } } if !checked.derived.is_empty() { - writeln!(snapshot, "\nDerived").unwrap(); + writeln!(out, "\nDerived").unwrap(); } let mut derived_entries: Vec<_> = checked.derived.iter().collect(); - derived_entries.sort_by_key(|(id, _)| format!("{:?}", id)); + derived_entries.sort_by_key(|(id, _)| *id); for (_derive_id, instance) in derived_entries { - let class_name = resolve_class_name(engine, &indexed, id, instance.resolution); + 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(); + writeln!(out, "derive {forall_prefix}{head}").unwrap(); } +} +fn write_checked_diagnostics( + out: &mut String, + engine: &QueryEngine, + id: FileId, + indexed: &indexing::IndexedModule, + checked: &checking::CheckedModule, +) { let content = engine.content(id); - let (parsed, _) = engine.parsed(id).unwrap(); let root = parsed.syntax_node(); - let stabilized = engine.stabilized(id).unwrap(); let lowered = engine.lowered(id).unwrap(); let resolved = engine.resolved(id).unwrap(); - let context = - DiagnosticsContext::new(&content, &root, &stabilized, &indexed, &lowered, &checked); + let context = DiagnosticsContext::new(&content, &root, &stabilized, indexed, &lowered, checked); let mut all_diagnostics = vec![]; @@ -445,11 +547,9 @@ pub fn report_checked(engine: &QueryEngine, id: FileId) -> String { } if !all_diagnostics.is_empty() { - writeln!(snapshot, "\nDiagnostics").unwrap(); - snapshot.push_str(&format_rustc(&all_diagnostics, &content)); + writeln!(out, "\nDiagnostics").unwrap(); + out.push_str(&format_rustc(&all_diagnostics, &content)); } - - snapshot } fn resolve_class_name( diff --git a/tests-integration/src/lib.rs b/tests-integration/src/lib.rs index 795c2631..1308f8cf 100644 --- a/tests-integration/src/lib.rs +++ b/tests-integration/src/lib.rs @@ -40,7 +40,7 @@ pub fn load_compiler(folder: &Path) -> (QueryEngine, Files) { let mut files = Files::default(); prim::configure(&mut engine, &mut files); - if folder.starts_with("fixtures/checking/") { + if folder.starts_with("fixtures/checking/") || folder.starts_with("fixtures/checking2/") { let prelude = Path::new("fixtures/checking/prelude"); load_folder(prelude).for_each(|path| { load_file(&mut engine, &mut files, &path); diff --git a/tests-integration/tests/checking2.rs b/tests-integration/tests/checking2.rs new file mode 100644 index 00000000..45b89844 --- /dev/null +++ b/tests-integration/tests/checking2.rs @@ -0,0 +1,2 @@ +#[path = "checking2/generated.rs"] +mod generated; diff --git a/tests-integration/tests/checking2/generated.rs b/tests-integration/tests/checking2/generated.rs new file mode 100644 index 00000000..c21e0e4b --- /dev/null +++ b/tests-integration/tests/checking2/generated.rs @@ -0,0 +1,489 @@ +// Do not edit! See build.rs + +#[rustfmt::skip] +fn run_test(folder: &str, file: &str) { + let path = std::path::Path::new("fixtures/checking2").join(folder); + let (engine, _) = tests_integration::load_compiler(&path); + let Some(id) = engine.module_file(file) else { return }; + + let level = match std::env::var("TRACE_LEVEL").as_deref() { + Ok("debug") => tracing::Level::DEBUG, + _ => tracing::Level::WARN, + }; + + let target_dir = env!("CARGO_TARGET_TMPDIR"); + let test_name = format!("{}_{}", folder, file); + let (report, trace_path) = tests_integration::trace::with_file_trace( + level, + target_dir, + &test_name, + || tests_integration::generated::basic::report_checked2(&engine, id) + ); + + println!("trace: {}", trace_path.display()); + + let mut settings = insta::Settings::clone_current(); + settings.set_snapshot_path(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("fixtures/checking2").join(folder)); + settings.set_prepend_module_to_snapshot(false); + settings.bind(|| insta::assert_snapshot!(file, report)); +} + +#[rustfmt::skip] #[test] fn test_001_foreign_check_main() { run_test("001_foreign_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_002_foreign_recursive_main() { run_test("002_foreign_recursive", "Main"); } + +#[rustfmt::skip] #[test] fn test_003_data_check_main() { run_test("003_data_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_004_data_infer_main() { run_test("004_data_infer", "Main"); } + +#[rustfmt::skip] #[test] fn test_005_newtype_check_main() { run_test("005_newtype_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_006_newtype_infer_main() { run_test("006_newtype_infer", "Main"); } + +#[rustfmt::skip] #[test] fn test_007_synonym_check_main() { run_test("007_synonym_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_008_synonym_infer_main() { run_test("008_synonym_infer", "Main"); } + +#[rustfmt::skip] #[test] fn test_009_class_check_main() { run_test("009_class_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_010_class_infer_main() { run_test("010_class_infer", "Main"); } + +#[rustfmt::skip] #[test] fn test_011_class_superclass_main() { run_test("011_class_superclass", "Main"); } + +#[rustfmt::skip] #[test] fn test_012_class_polykind_check_main() { run_test("012_class_polykind_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_013_class_polykind_infer_main() { run_test("013_class_polykind_infer", "Main"); } + +#[rustfmt::skip] #[test] fn test_014_operator_alias_kind_main() { run_test("014_operator_alias_kind", "Main"); } + +#[rustfmt::skip] #[test] fn test_015_operator_alias_invalid_kind_main() { run_test("015_operator_alias_invalid_kind", "Main"); } + +#[rustfmt::skip] #[test] fn test_016_type_operator_chain_infer_main() { run_test("016_type_operator_chain_infer", "Main"); } + +#[rustfmt::skip] #[test] fn test_017_type_operator_chain_check_main() { run_test("017_type_operator_chain_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_018_type_operator_chain_polykind_main() { run_test("018_type_operator_chain_polykind", "Main"); } + +#[rustfmt::skip] #[test] fn test_019_type_operator_chain_precedence_main() { run_test("019_type_operator_chain_precedence", "Main"); } + +#[rustfmt::skip] #[test] fn test_020_type_operator_chain_kind_error_main() { run_test("020_type_operator_chain_kind_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_021_role_inference_phantom_main() { run_test("021_role_inference_phantom", "Main"); } + +#[rustfmt::skip] #[test] fn test_022_role_inference_representational_main() { run_test("022_role_inference_representational", "Main"); } + +#[rustfmt::skip] #[test] fn test_023_role_inference_nominal_parametric_main() { run_test("023_role_inference_nominal_parametric", "Main"); } + +#[rustfmt::skip] #[test] fn test_024_role_declaration_strengthen_main() { run_test("024_role_declaration_strengthen", "Main"); } + +#[rustfmt::skip] #[test] fn test_025_role_declaration_loosen_error_main() { run_test("025_role_declaration_loosen_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_026_role_declaration_foreign_main() { run_test("026_role_declaration_foreign", "Main"); } + +#[rustfmt::skip] #[test] fn test_027_foreign_check_main() { run_test("027_foreign_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_028_value_check_main() { run_test("028_value_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_029_operator_check_main() { run_test("029_operator_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_030_exhaustive_case_infer_main() { run_test("030_exhaustive_case_infer", "Main"); } + +#[rustfmt::skip] #[test] fn test_031_exhaustive_case_redundant_main() { run_test("031_exhaustive_case_redundant", "Main"); } + +#[rustfmt::skip] #[test] fn test_032_exhaustive_equation_signature_main() { run_test("032_exhaustive_equation_signature", "Main"); } + +#[rustfmt::skip] #[test] fn test_033_exhaustive_guards_otherwise_main() { run_test("033_exhaustive_guards_otherwise", "Main"); } + +#[rustfmt::skip] #[test] fn test_034_exhaustive_let_pattern_main() { run_test("034_exhaustive_let_pattern", "Main"); } + +#[rustfmt::skip] #[test] fn test_035_exhaustive_operator_constructor_main() { run_test("035_exhaustive_operator_constructor", "Main"); } + +#[rustfmt::skip] #[test] fn test_036_synonym_partial_defer_main() { run_test("036_synonym_partial_defer", "Main"); } + +#[rustfmt::skip] #[test] fn test_037_value_recursive_check_main() { run_test("037_value_recursive_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_038_value_recursive_infer_main() { run_test("038_value_recursive_infer", "Main"); } + +#[rustfmt::skip] #[test] fn test_039_value_mutual_check_main() { run_test("039_value_mutual_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_040_value_mutual_infer_main() { run_test("040_value_mutual_infer", "Main"); } + +#[rustfmt::skip] #[test] fn test_041_data_mutual_check_main() { run_test("041_data_mutual_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_042_data_mutual_infer_main() { run_test("042_data_mutual_infer", "Main"); } + +#[rustfmt::skip] #[test] fn test_043_instance_check_main() { run_test("043_instance_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_044_instance_constraint_solving_main() { run_test("044_instance_constraint_solving", "Main"); } + +#[rustfmt::skip] #[test] fn test_045_instance_functional_dependency_main() { run_test("045_instance_functional_dependency", "Main"); } + +#[rustfmt::skip] #[test] fn test_046_instance_given_constraint_main() { run_test("046_instance_given_constraint", "Main"); } + +#[rustfmt::skip] #[test] fn test_047_instance_constraint_generalization_main() { run_test("047_instance_constraint_generalization", "Main"); } + +#[rustfmt::skip] #[test] fn test_048_instance_superclass_elaboration_main() { run_test("048_instance_superclass_elaboration", "Main"); } + +#[rustfmt::skip] #[test] fn test_049_given_constraint_check_main() { run_test("049_given_constraint_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_050_given_constraint_let_check_main() { run_test("050_given_constraint_let_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_051_given_constraint_operator_check_main() { run_test("051_given_constraint_operator_check", "Main"); } + +#[rustfmt::skip] #[test] fn test_052_prim_int_main() { run_test("052_prim_int", "Main"); } + +#[rustfmt::skip] #[test] fn test_053_prim_int_compare_transitive_main() { run_test("053_prim_int_compare_transitive", "Main"); } + +#[rustfmt::skip] #[test] fn test_054_prim_symbol_main() { run_test("054_prim_symbol", "Main"); } + +#[rustfmt::skip] #[test] fn test_055_prim_solver_apart_main() { run_test("055_prim_solver_apart", "Main"); } + +#[rustfmt::skip] #[test] fn test_056_prim_row_list_main() { run_test("056_prim_row_list", "Main"); } + +#[rustfmt::skip] #[test] fn test_057_prim_row_main() { run_test("057_prim_row", "Main"); } + +#[rustfmt::skip] #[test] fn test_058_prim_row_apart_main() { run_test("058_prim_row_apart", "Main"); } + +#[rustfmt::skip] #[test] fn test_059_prim_row_open_main() { run_test("059_prim_row_open", "Main"); } + +#[rustfmt::skip] #[test] fn test_060_prim_row_generalization_main() { run_test("060_prim_row_generalization", "Main"); } + +#[rustfmt::skip] #[test] fn test_061_prim_row_nub_left_bias_main() { run_test("061_prim_row_nub_left_bias", "Main"); } + +#[rustfmt::skip] #[test] fn test_062_prim_row_record_main() { run_test("062_prim_row_record", "Main"); } + +#[rustfmt::skip] #[test] fn test_063_prim_reflectable_main() { run_test("063_prim_reflectable", "Main"); } + +#[rustfmt::skip] #[test] fn test_064_prim_type_error_warn_main() { run_test("064_prim_type_error_warn", "Main"); } + +#[rustfmt::skip] #[test] fn test_065_prim_type_error_fail_main() { run_test("065_prim_type_error_fail", "Main"); } + +#[rustfmt::skip] #[test] fn test_066_compiler_solved_superclass_given_main() { run_test("066_compiler_solved_superclass_given", "Main"); } + +#[rustfmt::skip] #[test] fn test_067_compiler_solved_superclass_minimisation_main() { run_test("067_compiler_solved_superclass_minimisation", "Main"); } + +#[rustfmt::skip] #[test] fn test_068_prim_coercible_reflexivity_main() { run_test("068_prim_coercible_reflexivity", "Main"); } + +#[rustfmt::skip] #[test] fn test_069_prim_coercible_newtype_main() { run_test("069_prim_coercible_newtype", "Main"); } + +#[rustfmt::skip] #[test] fn test_070_prim_coercible_roles_main() { run_test("070_prim_coercible_roles", "Main"); } + +#[rustfmt::skip] #[test] fn test_071_prim_coercible_apart_main() { run_test("071_prim_coercible_apart", "Main"); } + +#[rustfmt::skip] #[test] fn test_072_prim_coercible_hidden_constructor_main() { run_test("072_prim_coercible_hidden_constructor", "Main"); } + +#[rustfmt::skip] #[test] fn test_073_prim_coercible_higher_kinded_main() { run_test("073_prim_coercible_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_074_prim_coercible_transitivity_main() { run_test("074_prim_coercible_transitivity", "Main"); } + +#[rustfmt::skip] #[test] fn test_075_prim_coercible_given_symmetry_main() { run_test("075_prim_coercible_given_symmetry", "Main"); } + +#[rustfmt::skip] #[test] fn test_076_instance_member_functor_main() { run_test("076_instance_member_functor", "Main"); } + +#[rustfmt::skip] #[test] fn test_077_instance_member_signature_head_variable_main() { run_test("077_instance_member_signature_head_variable", "Main"); } + +#[rustfmt::skip] #[test] fn test_078_instance_member_missing_constraint_main() { run_test("078_instance_member_missing_constraint", "Main"); } + +#[rustfmt::skip] #[test] fn test_079_instance_member_signature_mismatch_main() { run_test("079_instance_member_signature_mismatch", "Main"); } + +#[rustfmt::skip] #[test] fn test_080_instance_member_higher_kinded_main() { run_test("080_instance_member_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_081_instance_member_too_many_binders_main() { run_test("081_instance_member_too_many_binders", "Main"); } + +#[rustfmt::skip] #[test] fn test_082_derive_pipeline_smoke_main() { run_test("082_derive_pipeline_smoke", "Main"); } + +#[rustfmt::skip] #[test] fn test_083_derive_eq_simple_main() { run_test("083_derive_eq_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_084_derive_eq_parameterized_main() { run_test("084_derive_eq_parameterized", "Main"); } + +#[rustfmt::skip] #[test] fn test_085_derive_eq_missing_instance_main() { run_test("085_derive_eq_missing_instance", "Main"); } + +#[rustfmt::skip] #[test] fn test_086_derive_ord_simple_main() { run_test("086_derive_ord_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_087_derive_ord_1_higher_kinded_main() { run_test("087_derive_ord_1_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_088_derive_eq_1_main() { run_test("088_derive_eq_1", "Main"); } + +#[rustfmt::skip] #[test] fn test_089_derive_ord_1_main() { run_test("089_derive_ord_1", "Main"); } + +#[rustfmt::skip] #[test] fn test_090_derive_functor_simple_main() { run_test("090_derive_functor_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_091_derive_functor_contravariant_error_main() { run_test("091_derive_functor_contravariant_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_092_derive_functor_insufficient_params_main() { run_test("092_derive_functor_insufficient_params", "Main"); } + +#[rustfmt::skip] #[test] fn test_093_derive_bifunctor_simple_main() { run_test("093_derive_bifunctor_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_094_derive_bifunctor_missing_functor_main() { run_test("094_derive_bifunctor_missing_functor", "Main"); } + +#[rustfmt::skip] #[test] fn test_095_derive_bifunctor_insufficient_params_main() { run_test("095_derive_bifunctor_insufficient_params", "Main"); } + +#[rustfmt::skip] #[test] fn test_096_derive_contravariant_simple_main() { run_test("096_derive_contravariant_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_097_derive_contravariant_error_main() { run_test("097_derive_contravariant_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_098_derive_profunctor_simple_main() { run_test("098_derive_profunctor_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_099_derive_profunctor_error_main() { run_test("099_derive_profunctor_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_100_derive_foldable_simple_main() { run_test("100_derive_foldable_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_101_derive_foldable_higher_kinded_main() { run_test("101_derive_foldable_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_102_derive_bifoldable_simple_main() { run_test("102_derive_bifoldable_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_103_derive_bifoldable_higher_kinded_main() { run_test("103_derive_bifoldable_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_104_derive_traversable_simple_main() { run_test("104_derive_traversable_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_105_derive_traversable_higher_kinded_main() { run_test("105_derive_traversable_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_106_derive_traversable_missing_superclass_main() { run_test("106_derive_traversable_missing_superclass", "Main"); } + +#[rustfmt::skip] #[test] fn test_107_derive_bitraversable_simple_main() { run_test("107_derive_bitraversable_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_108_derive_bitraversable_higher_kinded_main() { run_test("108_derive_bitraversable_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_109_derive_newtype_simple_main() { run_test("109_derive_newtype_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_110_derive_newtype_parameterized_main() { run_test("110_derive_newtype_parameterized", "Main"); } + +#[rustfmt::skip] #[test] fn test_111_derive_newtype_not_newtype_main() { run_test("111_derive_newtype_not_newtype", "Main"); } + +#[rustfmt::skip] #[test] fn test_112_derive_newtype_class_simple_main() { run_test("112_derive_newtype_class_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_113_derive_newtype_class_parameterized_main() { run_test("113_derive_newtype_class_parameterized", "Main"); } + +#[rustfmt::skip] #[test] fn test_114_derive_newtype_class_not_newtype_main() { run_test("114_derive_newtype_class_not_newtype", "Main"); } + +#[rustfmt::skip] #[test] fn test_115_derive_generic_simple_main() { run_test("115_derive_generic_simple", "Main"); } + +#[rustfmt::skip] #[test] fn test_116_derive_eq_mutual_visibility_same_module_main() { run_test("116_derive_eq_mutual_visibility_same_module", "Main"); } + +#[rustfmt::skip] #[test] fn test_117_derive_newtype_class_coercible_main() { run_test("117_derive_newtype_class_coercible", "Main"); } + +#[rustfmt::skip] #[test] fn test_118_derive_newtype_with_given_main() { run_test("118_derive_newtype_with_given", "Main"); } + +#[rustfmt::skip] #[test] fn test_119_derive_newtype_missing_instance_main() { run_test("119_derive_newtype_missing_instance", "Main"); } + +#[rustfmt::skip] #[test] fn test_120_derive_newtype_missing_given_main() { run_test("120_derive_newtype_missing_given", "Main"); } + +#[rustfmt::skip] #[test] fn test_121_derive_newtype_multi_param_main() { run_test("121_derive_newtype_multi_param", "Main"); } + +#[rustfmt::skip] #[test] fn test_122_derive_newtype_higher_kinded_main() { run_test("122_derive_newtype_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_123_derive_newtype_function_main() { run_test("123_derive_newtype_function", "Main"); } + +#[rustfmt::skip] #[test] fn test_124_derive_newtype_synonym_inner_main() { run_test("124_derive_newtype_synonym_inner", "Main"); } + +#[rustfmt::skip] #[test] fn test_125_derive_eq_1_higher_kinded_main() { run_test("125_derive_eq_1_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_126_derive_eq_partial_main() { run_test("126_derive_eq_partial", "Main"); } + +#[rustfmt::skip] #[test] fn test_127_derive_eq_ord_nested_higher_kinded_main() { run_test("127_derive_eq_ord_nested_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_128_derive_functor_higher_kinded_main() { run_test("128_derive_functor_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_129_derive_bifunctor_higher_kinded_main() { run_test("129_derive_bifunctor_higher_kinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_130_givens_retained_main() { run_test("130_givens_retained", "Main"); } + +#[rustfmt::skip] #[test] fn test_131_givens_scoped_main() { run_test("131_givens_scoped", "Main"); } + +#[rustfmt::skip] #[test] fn test_132_let_constraint_scoping_main() { run_test("132_let_constraint_scoping", "Main"); } + +#[rustfmt::skip] #[test] fn test_133_row_open_union_main() { run_test("133_row_open_union", "Main"); } + +#[rustfmt::skip] #[test] fn test_134_row_open_cons_main() { run_test("134_row_open_cons", "Main"); } + +#[rustfmt::skip] #[test] fn test_135_row_open_lacks_main() { run_test("135_row_open_lacks", "Main"); } + +#[rustfmt::skip] #[test] fn test_136_row_open_record_main() { run_test("136_row_open_record", "Main"); } + +#[rustfmt::skip] #[test] fn test_137_type_operator_synonym_expansion_main() { run_test("137_type_operator_synonym_expansion", "Main"); } + +#[rustfmt::skip] #[test] fn test_138_synonym_kind_application_main() { run_test("138_synonym_kind_application", "Main"); } + +#[rustfmt::skip] #[test] fn test_139_synonym_operator_alias_main() { run_test("139_synonym_operator_alias", "Main"); } + +#[rustfmt::skip] #[test] fn test_140_const_equation_forms_main() { run_test("140_const_equation_forms", "Main"); } + +#[rustfmt::skip] #[test] fn test_141_const_forms_synonym_arrow_main() { run_test("141_const_forms_synonym_arrow", "Main"); } + +#[rustfmt::skip] #[test] fn test_142_const_forms_synonym_forall_main() { run_test("142_const_forms_synonym_forall", "Main"); } + +#[rustfmt::skip] #[test] fn test_143_const_forms_fn_alias_main() { run_test("143_const_forms_fn_alias", "Main"); } + +#[rustfmt::skip] #[test] fn test_144_signature_synonym_data_equation_main() { run_test("144_signature_synonym_data_equation", "Main"); } + +#[rustfmt::skip] #[test] fn test_145_signature_synonym_class_equation_main() { run_test("145_signature_synonym_class_equation", "Main"); } + +#[rustfmt::skip] #[test] fn test_146_signature_synonym_type_equation_main() { run_test("146_signature_synonym_type_equation", "Main"); } + +#[rustfmt::skip] #[test] fn test_147_synonym_oversaturation_equations_main() { run_test("147_synonym_oversaturation_equations", "Main"); } + +#[rustfmt::skip] #[test] fn test_148_class_member_prenex_main() { run_test("148_class_member_prenex", "Main"); } + +#[rustfmt::skip] #[test] fn test_149_synonym_oversaturation_kind_main() { run_test("149_synonym_oversaturation_kind", "Main"); } + +#[rustfmt::skip] #[test] fn test_150_derive_generic_insufficient_params_main() { run_test("150_derive_generic_insufficient_params", "Main"); } + +#[rustfmt::skip] #[test] fn test_151_derive_generic_not_constructor_main() { run_test("151_derive_generic_not_constructor", "Main"); } + +#[rustfmt::skip] #[test] fn test_152_derive_generic_missing_rep_main() { run_test("152_derive_generic_missing_rep", "Main"); } + +#[rustfmt::skip] #[test] fn test_153_do_discard_main() { run_test("153_do_discard", "Main"); } + +#[rustfmt::skip] #[test] fn test_154_do_bind_main() { run_test("154_do_bind", "Main"); } + +#[rustfmt::skip] #[test] fn test_155_ado_discard_main() { run_test("155_ado_discard", "Main"); } + +#[rustfmt::skip] #[test] fn test_156_ado_bind_main() { run_test("156_ado_bind", "Main"); } + +#[rustfmt::skip] #[test] fn test_157_do_polymorphic_main() { run_test("157_do_polymorphic", "Main"); } + +#[rustfmt::skip] #[test] fn test_158_ado_polymorphic_main() { run_test("158_ado_polymorphic", "Main"); } + +#[rustfmt::skip] #[test] fn test_159_do_empty_block_main() { run_test("159_do_empty_block", "Main"); } + +#[rustfmt::skip] #[test] fn test_160_ado_empty_block_main() { run_test("160_ado_empty_block", "Main"); } + +#[rustfmt::skip] #[test] fn test_161_do_ado_constrained_main() { run_test("161_do_ado_constrained", "Main"); } + +#[rustfmt::skip] #[test] fn test_162_do_let_premature_solve_main() { run_test("162_do_let_premature_solve", "Main"); } + +#[rustfmt::skip] #[test] fn test_163_do_let_annotation_solve_main() { run_test("163_do_let_annotation_solve", "Main"); } + +#[rustfmt::skip] #[test] fn test_164_ado_let_premature_solve_main() { run_test("164_ado_let_premature_solve", "Main"); } + +#[rustfmt::skip] #[test] fn test_165_ado_let_annotation_solve_main() { run_test("165_ado_let_annotation_solve", "Main"); } + +#[rustfmt::skip] #[test] fn test_166_do_bind_only_main() { run_test("166_do_bind_only", "Main"); } + +#[rustfmt::skip] #[test] fn test_167_do_final_bind_main() { run_test("167_do_final_bind", "Main"); } + +#[rustfmt::skip] #[test] fn test_168_do_final_let_main() { run_test("168_do_final_let", "Main"); } + +#[rustfmt::skip] #[test] fn test_169_exhaustive_multiple_main() { run_test("169_exhaustive_multiple", "Main"); } + +#[rustfmt::skip] #[test] fn test_170_exhaustive_tuple_main() { run_test("170_exhaustive_tuple", "Main"); } + +#[rustfmt::skip] #[test] fn test_171_exhaustive_equation_redundant_main() { run_test("171_exhaustive_equation_redundant", "Main"); } + +#[rustfmt::skip] #[test] fn test_172_exhaustive_equation_guarded_main() { run_test("172_exhaustive_equation_guarded", "Main"); } + +#[rustfmt::skip] #[test] fn test_173_exhaustive_let_equation_main() { run_test("173_exhaustive_let_equation", "Main"); } + +#[rustfmt::skip] #[test] fn test_174_exhaustive_instance_equation_main() { run_test("174_exhaustive_instance_equation", "Main"); } + +#[rustfmt::skip] #[test] fn test_175_record_constructor_exhaustive_main() { run_test("175_record_constructor_exhaustive", "Main"); } + +#[rustfmt::skip] #[test] fn test_176_array_exhaustive_main() { run_test("176_array_exhaustive", "Main"); } + +#[rustfmt::skip] #[test] fn test_177_exhaustive_guards_otherwise_true_main() { run_test("177_exhaustive_guards_otherwise_true", "Main"); } + +#[rustfmt::skip] #[test] fn test_178_application_infix_chain_main() { run_test("178_application_infix_chain", "Main"); } + +#[rustfmt::skip] #[test] fn test_179_application_function_subtype_main() { run_test("179_application_function_subtype", "Main"); } + +#[rustfmt::skip] #[test] fn test_180_application_function_decomposition_main() { run_test("180_application_function_decomposition", "Main"); } + +#[rustfmt::skip] #[test] fn test_181_type_application_invalid_basic_main() { run_test("181_type_application_invalid_basic", "Main"); } + +#[rustfmt::skip] #[test] fn test_182_type_application_invalid_too_many_main() { run_test("182_type_application_invalid_too_many", "Main"); } + +#[rustfmt::skip] #[test] fn test_183_record_expression_exact_main() { run_test("183_record_expression_exact", "Main"); } + +#[rustfmt::skip] #[test] fn test_184_record_expression_missing_field_main() { run_test("184_record_expression_missing_field", "Main"); } + +#[rustfmt::skip] #[test] fn test_185_record_expression_additional_field_main() { run_test("185_record_expression_additional_field", "Main"); } + +#[rustfmt::skip] #[test] fn test_186_record_expression_missing_and_additional_main() { run_test("186_record_expression_missing_and_additional", "Main"); } + +#[rustfmt::skip] #[test] fn test_187_record_expression_nested_additional_main() { run_test("187_record_expression_nested_additional", "Main"); } + +#[rustfmt::skip] #[test] fn test_188_record_access_sections_main() { run_test("188_record_access_sections", "Main"); } + +#[rustfmt::skip] #[test] fn test_189_record_update_sections_main() { run_test("189_record_update_sections", "Main"); } + +#[rustfmt::skip] #[test] fn test_190_record_binder_shrinking_main() { run_test("190_record_binder_shrinking", "Main"); } + +#[rustfmt::skip] #[test] fn test_191_record_binder_additional_property_main() { run_test("191_record_binder_additional_property", "Main"); } + +#[rustfmt::skip] #[test] fn test_192_record_binder_additional_property_nested_main() { run_test("192_record_binder_additional_property_nested", "Main"); } + +#[rustfmt::skip] #[test] fn test_193_givens_matching_main() { run_test("193_givens_matching", "Main"); } + +#[rustfmt::skip] #[test] fn test_194_givens_functional_dependency_main() { run_test("194_givens_functional_dependency", "Main"); } + +#[rustfmt::skip] #[test] fn test_195_givens_superclass_where_binding_main() { run_test("195_givens_superclass_where_binding", "Main"); } + +#[rustfmt::skip] #[test] fn test_196_instance_record_matching_main() { run_test("196_instance_record_matching", "Main"); } + +#[rustfmt::skip] #[test] fn test_197_instance_record_open_row_main() { run_test("197_instance_record_open_row", "Main"); } + +#[rustfmt::skip] #[test] fn test_198_instance_head_invalid_row_main() { run_test("198_instance_head_invalid_row", "Main"); } + +#[rustfmt::skip] #[test] fn test_199_instance_kind_application_matching_main() { run_test("199_instance_kind_application_matching", "Main"); } + +#[rustfmt::skip] #[test] fn test_200_instance_bound_variable_unification_main() { run_test("200_instance_bound_variable_unification", "Main"); } + +#[rustfmt::skip] #[test] fn test_201_synonym_function_result_kind_main() { run_test("201_synonym_function_result_kind", "Main"); } + +#[rustfmt::skip] #[test] fn test_202_synonym_forall_expansion_main() { run_test("202_synonym_forall_expansion", "Main"); } + +#[rustfmt::skip] #[test] fn test_203_binder_instantiation_main() { run_test("203_binder_instantiation", "Main"); } + +#[rustfmt::skip] #[test] fn test_204_coercible_phantom_main() { run_test("204_coercible_phantom", "Main"); } + +#[rustfmt::skip] #[test] fn test_205_coercible_representational_main() { run_test("205_coercible_representational", "Main"); } + +#[rustfmt::skip] #[test] fn test_206_coercible_array_main() { run_test("206_coercible_array", "Main"); } + +#[rustfmt::skip] #[test] fn test_207_coercible_record_main() { run_test("207_coercible_record", "Main"); } + +#[rustfmt::skip] #[test] fn test_208_coercible_transitivity_main() { run_test("208_coercible_transitivity", "Main"); } + +#[rustfmt::skip] #[test] fn test_209_coercible_different_heads_error_main() { run_test("209_coercible_different_heads_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_210_coercible_nominal_main() { run_test("210_coercible_nominal", "Main"); } + +#[rustfmt::skip] #[test] fn test_211_coercible_newtype_hidden_main() { run_test("211_coercible_newtype_hidden", "Main"); } + +#[rustfmt::skip] #[test] fn test_212_coercible_newtype_qualified_main() { run_test("212_coercible_newtype_qualified", "Main"); } + +#[rustfmt::skip] #[test] fn test_213_coercible_newtype_open_hidden_main() { run_test("213_coercible_newtype_open_hidden", "Main"); } + +#[rustfmt::skip] #[test] fn test_214_coercible_nested_records_main() { run_test("214_coercible_nested_records", "Main"); } + +#[rustfmt::skip] #[test] fn test_215_coercible_higher_kinded_error_main() { run_test("215_coercible_higher_kinded_error", "Main"); } + +#[rustfmt::skip] #[test] fn test_216_coercible_higher_kinded_multi_main() { run_test("216_coercible_higher_kinded_multi", "Main"); } + +#[rustfmt::skip] #[test] fn test_217_coercible_higher_kinded_polykinded_main() { run_test("217_coercible_higher_kinded_polykinded", "Main"); } + +#[rustfmt::skip] #[test] fn test_218_coercible_function_decomposition_main() { run_test("218_coercible_function_decomposition", "Main"); } + +#[rustfmt::skip] #[test] fn test_219_derive_newtype_not_constructor_main() { run_test("219_derive_newtype_not_constructor", "Main"); } + +#[rustfmt::skip] #[test] fn test_220_derive_newtype_class_not_constructor_main() { run_test("220_derive_newtype_class_not_constructor", "Main"); } + +#[rustfmt::skip] #[test] fn test_222_derive_newtype_not_local_main() { run_test("222_derive_newtype_not_local", "Main"); } + +#[rustfmt::skip] #[test] fn test_223_derive_newtype_class_not_local_main() { run_test("223_derive_newtype_class_not_local", "Main"); } + +#[rustfmt::skip] #[test] fn test_224_derive_newtype_insufficient_params_main() { run_test("224_derive_newtype_insufficient_params", "Main"); } + +#[rustfmt::skip] #[test] fn test_225_derive_newtype_class_insufficient_params_main() { run_test("225_derive_newtype_class_insufficient_params", "Main"); } + +#[rustfmt::skip] #[test] fn test_226_equation_synonym_expansion_main() { run_test("226_equation_synonym_expansion", "Main"); } + +#[rustfmt::skip] #[test] fn test_227_derive_newtype_invalid_skolem_layout_main() { run_test("227_derive_newtype_invalid_skolem_layout", "Main"); } + +#[rustfmt::skip] #[test] fn test_228_instance_implicit_variable_freshening_main() { run_test("228_instance_implicit_variable_freshening", "Main"); } + +#[rustfmt::skip] #[test] fn test_229_type_operator_synonym_partial_arguments_main() { run_test("229_type_operator_synonym_partial_arguments", "Main"); } + +#[rustfmt::skip] #[test] fn test_230_type_synonym_higher_order_main() { run_test("230_type_synonym_higher_order", "Main"); } + +#[rustfmt::skip] #[test] fn test_231_type_synonym_higher_order_operator_main() { run_test("231_type_synonym_higher_order_operator", "Main"); }