Merge pull request #5668 from roc-lang/checkmate

Add "checkmate", a tool for the solver
This commit is contained in:
Ayaz 2023-07-19 12:38:20 -05:00 committed by GitHub
commit 6225b80e01
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 21512 additions and 423 deletions

3
.gitignore vendored
View file

@ -85,3 +85,6 @@ www/src/roc-tutorial
# snapshot tests temp file
*.pending-snap
# checkmate
checkmate_*.json

168
Cargo.lock generated
View file

@ -88,6 +88,21 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.3.2"
@ -473,6 +488,21 @@ dependencies = [
"num-traits",
]
[[package]]
name = "chrono"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"time 0.1.45",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "clap"
version = "2.34.0"
@ -1094,6 +1124,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
[[package]]
name = "dyn-clone"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272"
[[package]]
name = "either"
version = "1.8.1"
@ -1394,7 +1430,7 @@ dependencies = [
"cfg-if 1.0.0",
"js-sys",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
@ -1653,6 +1689,29 @@ dependencies = [
"tokio-rustls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
dependencies = [
"android_system_properties",
"core-foundation-sys 0.8.4",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "iced-x86"
version = "1.18.0"
@ -1975,7 +2034,7 @@ dependencies = [
"libc",
"log",
"thiserror",
"time",
"time 0.3.21",
"uuid",
]
@ -2106,7 +2165,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.45.0",
]
@ -3095,6 +3154,7 @@ dependencies = [
"page_size",
"roc_builtins",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_error_macros",
"roc_load",
@ -3105,6 +3165,7 @@ dependencies = [
"roc_region",
"roc_reporting",
"roc_solve",
"roc_solve_schema",
"roc_target",
"roc_types",
"roc_unify",
@ -3203,6 +3264,27 @@ dependencies = [
"ven_pretty",
]
[[package]]
name = "roc_checkmate"
version = "0.0.1"
dependencies = [
"chrono",
"roc_checkmate_schema",
"roc_module",
"roc_solve_schema",
"roc_types",
"serde_json",
]
[[package]]
name = "roc_checkmate_schema"
version = "0.0.1"
dependencies = [
"schemars",
"serde",
"serde_json",
]
[[package]]
name = "roc_cli"
version = "0.0.1"
@ -3311,11 +3393,13 @@ version = "0.0.1"
dependencies = [
"bumpalo",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_derive_key",
"roc_error_macros",
"roc_module",
"roc_region",
"roc_solve_schema",
"roc_types",
"roc_unify",
]
@ -3564,11 +3648,13 @@ version = "0.0.1"
dependencies = [
"bumpalo",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_derive",
"roc_error_macros",
"roc_module",
"roc_solve",
"roc_solve_schema",
"roc_types",
"roc_unify",
]
@ -3630,6 +3716,7 @@ dependencies = [
"pretty_assertions",
"roc_builtins",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_constrain",
"roc_debug_flags",
@ -3917,6 +4004,7 @@ dependencies = [
"regex",
"roc_builtins",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_debug_flags",
"roc_derive",
@ -3932,6 +4020,7 @@ dependencies = [
"roc_reporting",
"roc_solve",
"roc_solve_problem",
"roc_solve_schema",
"roc_target",
"roc_types",
"roc_unify",
@ -3952,6 +4041,13 @@ dependencies = [
"roc_types",
]
[[package]]
name = "roc_solve_schema"
version = "0.0.1"
dependencies = [
"bitflags",
]
[[package]]
name = "roc_std"
version = "0.0.1"
@ -4011,11 +4107,12 @@ dependencies = [
name = "roc_unify"
version = "0.0.1"
dependencies = [
"bitflags",
"roc_checkmate",
"roc_collections",
"roc_debug_flags",
"roc_error_macros",
"roc_module",
"roc_solve_schema",
"roc_tracing",
"roc_types",
]
@ -4161,6 +4258,30 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schemars"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
dependencies = [
"dyn-clone",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 1.0.109",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
@ -4249,6 +4370,17 @@ dependencies = [
"syn 2.0.16",
]
[[package]]
name = "serde_derive_internals"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "serde_json"
version = "1.0.96"
@ -4803,6 +4935,17 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "time"
version = "0.3.21"
@ -4947,7 +5090,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
dependencies = [
"crossbeam-channel",
"time",
"time 0.3.21",
"tracing-subscriber",
]
@ -5257,6 +5400,12 @@ dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@ -5581,6 +5730,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
name = "windows-sys"
version = "0.42.0"

View file

@ -82,6 +82,7 @@ bumpalo = { version = "3.12.0", features = ["collections"] }
bytemuck = { version = "1.13.1", features = ["derive"] }
capstone = { version = "0.11.0", default-features = false }
cgmath = "0.18.0"
chrono = "0.4.26"
clap = { version = "4.2.7", default-features = false, features = ["std", "color", "suggestions", "help", "usage", "error-context"] }
colored = "2.0.0"
console_error_panic_hook = "0.1.7"
@ -141,6 +142,7 @@ reqwest = { version = "0.11.14", default-features = false, features = ["blocking
rlimit = "0.9.1"
rustyline = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" }
rustyline-derive = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" }
schemars = "0.8.12"
serde = { version = "1.0.153", features = ["derive"] } # update roc_std/Cargo.toml on change
serde-xml-rs = "0.6.0"
serde_json = "1.0.94" # update roc_std/Cargo.toml on change

View file

@ -10,6 +10,7 @@ version.workspace = true
[dependencies]
roc_builtins = { path = "../compiler/builtins" }
roc_can = { path = "../compiler/can" }
roc_checkmate = { path = "../compiler/checkmate" }
roc_collections = { path = "../compiler/collections" }
roc_error_macros = { path = "../error_macros" }
roc_load = { path = "../compiler/load" }
@ -20,6 +21,7 @@ roc_problem = { path = "../compiler/problem" }
roc_region = { path = "../compiler/region" }
roc_reporting = { path = "../reporting" }
roc_solve = { path = "../compiler/solve" }
roc_solve_schema = { path = "../compiler/solve_schema" }
roc_target = { path = "../compiler/roc_target" }
roc_types = { path = "../compiler/types" }
roc_unify = { path = "../compiler/unify" }

View file

@ -2,12 +2,14 @@
#![allow(dead_code)]
use bumpalo::Bump;
use roc_can::expected::{Expected, PExpected};
use roc_checkmate::with_checkmate;
use roc_collections::all::{BumpMap, BumpMapDefault, MutMap};
use roc_error_macros::internal_error;
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_solve::module::Solved;
use roc_solve_schema::UnificationMode;
use roc_types::subs::{
self, AliasVariables, Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields,
Subs, SubsSlice, TagExt, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
@ -17,9 +19,8 @@ use roc_types::types::{
RecordField,
};
use roc_unify::unify::unify;
use roc_unify::unify::Env as UEnv;
use roc_unify::unify::Mode;
use roc_unify::unify::Unified::*;
use roc_unify::Env as UEnv;
use crate::constrain::{Constraint, PresenceConstraint};
use crate::lang::core::types::Type2;
@ -228,10 +229,13 @@ fn solve<'a>(
);
match unify(
&mut UEnv::new(subs),
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
actual,
expected,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_VALUE,
) {
Success {
@ -326,10 +330,13 @@ fn solve<'a>(
);
match unify(
&mut UEnv::new(subs),
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
actual,
expected,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_VALUE,
) {
Success {
@ -402,10 +409,13 @@ fn solve<'a>(
// TODO(ayazhafiz): presence constraints for Expr2/Type2
match unify(
&mut UEnv::new(subs),
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
actual,
expected,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_PATTERN,
) {
Success {
@ -718,10 +728,13 @@ fn solve<'a>(
let includes = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, &tag_ty);
match unify(
&mut UEnv::new(subs),
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
actual,
includes,
Mode::PRESENT,
UnificationMode::PRESENT,
Polarity::OF_PATTERN,
) {
Success {

View file

@ -0,0 +1,19 @@
[package]
name = "roc_checkmate"
description = "A framework for debugging the solver."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_checkmate_schema = { path = "../checkmate_schema" }
roc_module = { path = "../module" }
roc_solve_schema = { path = "../solve_schema" }
roc_types = { path = "../types" }
chrono.workspace = true
[build-dependencies]
roc_checkmate_schema = { path = "../checkmate_schema" }
serde_json.workspace = true

View file

@ -0,0 +1,5 @@
# `checkmate`
A tool to debug the solver (checker + inference + specialization engine).
See [the document](https://rwx.notion.site/Type-debugging-tools-de42260060784cacbaf08ea4d61e0eb9?pvs=4).

View file

@ -0,0 +1,13 @@
use std::fs;
use roc_checkmate_schema::AllEvents;
fn main() {
println!("cargo:rerun-if-changed=../checkmate_schema");
let schema = AllEvents::schema();
fs::write(
"schema.json",
serde_json::to_string_pretty(&schema).unwrap(),
)
.unwrap();
}

View file

@ -0,0 +1,907 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "AllEvents",
"type": "array",
"items": {
"$ref": "#/definitions/Event"
},
"definitions": {
"AliasKind": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Structural"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Opaque"
]
}
}
}
]
},
"AliasTypeVariables": {
"type": "object",
"required": [
"infer_ext_in_output_position_variables",
"lambda_set_variables",
"type_variables"
],
"properties": {
"infer_ext_in_output_position_variables": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
},
"lambda_set_variables": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
},
"type_variables": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
}
}
},
"ClosureType": {
"type": "object",
"required": [
"environment",
"function"
],
"properties": {
"environment": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
},
"function": {
"$ref": "#/definitions/Symbol"
}
}
},
"Content": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"name": {
"type": [
"string",
"null"
]
},
"type": {
"type": "string",
"enum": [
"Flex"
]
}
}
},
{
"type": "object",
"required": [
"name",
"type"
],
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"Rigid"
]
}
}
},
{
"type": "object",
"required": [
"abilities",
"type"
],
"properties": {
"abilities": {
"type": "array",
"items": {
"$ref": "#/definitions/Symbol"
}
},
"name": {
"type": [
"string",
"null"
]
},
"type": {
"type": "string",
"enum": [
"FlexAble"
]
}
}
},
{
"type": "object",
"required": [
"abilities",
"name",
"type"
],
"properties": {
"abilities": {
"type": "array",
"items": {
"$ref": "#/definitions/Symbol"
}
},
"name": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"RigidAble"
]
}
}
},
{
"type": "object",
"required": [
"structure",
"type"
],
"properties": {
"name": {
"type": [
"string",
"null"
]
},
"structure": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"Recursive"
]
}
}
},
{
"type": "object",
"required": [
"ambient_function",
"solved",
"type",
"unspecialized"
],
"properties": {
"ambient_function": {
"$ref": "#/definitions/Variable"
},
"recursion_var": {
"anyOf": [
{
"$ref": "#/definitions/Variable"
},
{
"type": "null"
}
]
},
"solved": {
"type": "array",
"items": {
"$ref": "#/definitions/ClosureType"
}
},
"type": {
"type": "string",
"enum": [
"LambdaSet"
]
},
"unspecialized": {
"type": "array",
"items": {
"$ref": "#/definitions/UnspecializedClosureType"
}
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"ErasedLambda"
]
}
}
},
{
"type": "object",
"required": [
"kind",
"name",
"real_variable",
"type",
"variables"
],
"properties": {
"kind": {
"$ref": "#/definitions/AliasKind"
},
"name": {
"$ref": "#/definitions/Symbol"
},
"real_variable": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"Alias"
]
},
"variables": {
"$ref": "#/definitions/AliasTypeVariables"
}
}
},
{
"type": "object",
"required": [
"symbol",
"type",
"variables"
],
"properties": {
"symbol": {
"$ref": "#/definitions/Symbol"
},
"type": {
"type": "string",
"enum": [
"Apply"
]
},
"variables": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
}
}
},
{
"type": "object",
"required": [
"arguments",
"lambda_type",
"ret",
"type"
],
"properties": {
"arguments": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
},
"lambda_type": {
"$ref": "#/definitions/Variable"
},
"ret": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"Function"
]
}
}
},
{
"type": "object",
"required": [
"extension",
"fields",
"type"
],
"properties": {
"extension": {
"$ref": "#/definitions/Variable"
},
"fields": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/RecordField"
}
},
"type": {
"type": "string",
"enum": [
"Record"
]
}
}
},
{
"type": "object",
"required": [
"elements",
"extension",
"type"
],
"properties": {
"elements": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Variable"
}
},
"extension": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"Tuple"
]
}
}
},
{
"type": "object",
"required": [
"extension",
"tags",
"type"
],
"properties": {
"extension": {
"$ref": "#/definitions/TagUnionExtension"
},
"tags": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
}
},
"type": {
"type": "string",
"enum": [
"TagUnion"
]
}
}
},
{
"type": "object",
"required": [
"extension",
"functions",
"tags",
"type"
],
"properties": {
"extension": {
"$ref": "#/definitions/TagUnionExtension"
},
"functions": {
"type": "array",
"items": {
"$ref": "#/definitions/Symbol"
}
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"type": {
"type": "string",
"enum": [
"FunctionOrTagUnion"
]
}
}
},
{
"type": "object",
"required": [
"extension",
"recursion_var",
"tags",
"type"
],
"properties": {
"extension": {
"$ref": "#/definitions/TagUnionExtension"
},
"recursion_var": {
"$ref": "#/definitions/Variable"
},
"tags": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
}
},
"type": {
"type": "string",
"enum": [
"RecursiveTagUnion"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"EmptyRecord"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"EmptyTuple"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"EmptyTagUnion"
]
}
}
},
{
"type": "object",
"required": [
"range",
"type"
],
"properties": {
"range": {
"$ref": "#/definitions/NumericRange"
},
"type": {
"type": "string",
"enum": [
"RangedNumber"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Error"
]
}
}
}
]
},
"Event": {
"oneOf": [
{
"type": "object",
"required": [
"left",
"mode",
"right",
"subevents",
"type"
],
"properties": {
"left": {
"$ref": "#/definitions/Variable"
},
"mode": {
"$ref": "#/definitions/UnificationMode"
},
"right": {
"$ref": "#/definitions/Variable"
},
"subevents": {
"type": "array",
"items": {
"$ref": "#/definitions/Event"
}
},
"success": {
"type": [
"boolean",
"null"
]
},
"type": {
"type": "string",
"enum": [
"Unification"
]
}
}
},
{
"type": "object",
"required": [
"from",
"to",
"type"
],
"properties": {
"from": {
"$ref": "#/definitions/Variable"
},
"to": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"VariableUnified"
]
}
}
},
{
"type": "object",
"required": [
"type",
"variable"
],
"properties": {
"content": {
"anyOf": [
{
"$ref": "#/definitions/Content"
},
{
"type": "null"
}
]
},
"rank": {
"anyOf": [
{
"$ref": "#/definitions/Rank"
},
{
"type": "null"
}
]
},
"type": {
"type": "string",
"enum": [
"VariableSetDescriptor"
]
},
"variable": {
"$ref": "#/definitions/Variable"
}
}
}
]
},
"NumericRange": {
"type": "object",
"required": [
"kind",
"min_width",
"signed"
],
"properties": {
"kind": {
"$ref": "#/definitions/NumericRangeKind"
},
"min_width": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"signed": {
"type": "boolean"
}
}
},
"NumericRangeKind": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Int"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"AnyNum"
]
}
}
}
]
},
"Rank": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"RecordField": {
"type": "object",
"required": [
"field_type",
"kind"
],
"properties": {
"field_type": {
"$ref": "#/definitions/Variable"
},
"kind": {
"$ref": "#/definitions/RecordFieldKind"
}
}
},
"RecordFieldKind": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Demanded"
]
}
}
},
{
"type": "object",
"required": [
"rigid",
"type"
],
"properties": {
"rigid": {
"type": "boolean"
},
"type": {
"type": "string",
"enum": [
"Required"
]
}
}
},
{
"type": "object",
"required": [
"rigid",
"type"
],
"properties": {
"rigid": {
"type": "boolean"
},
"type": {
"type": "string",
"enum": [
"Optional"
]
}
}
}
]
},
"Symbol": {
"type": "string"
},
"TagUnionExtension": {
"oneOf": [
{
"type": "object",
"required": [
"type",
"variable"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Openness"
]
},
"variable": {
"$ref": "#/definitions/Variable"
}
}
},
{
"type": "object",
"required": [
"type",
"variable"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Any"
]
},
"variable": {
"$ref": "#/definitions/Variable"
}
}
}
]
},
"UnificationMode": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Eq"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Present"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"LambdaSetSpecialization"
]
}
}
}
]
},
"UnspecializedClosureType": {
"type": "object",
"required": [
"ability_member",
"lambda_set_region",
"specialization"
],
"properties": {
"ability_member": {
"$ref": "#/definitions/Symbol"
},
"lambda_set_region": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"specialization": {
"$ref": "#/definitions/Variable"
}
}
},
"Variable": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
}

View file

@ -0,0 +1,175 @@
use std::error::Error;
use roc_checkmate_schema::{AllEvents, Event};
use roc_types::subs as s;
use crate::convert::AsSchema;
#[derive(Debug)]
pub struct Collector {
events: AllEvents,
current_event_path: Vec<usize>,
}
impl Default for Collector {
fn default() -> Self {
Self::new()
}
}
impl Collector {
pub fn new() -> Self {
Self {
events: AllEvents(Vec::new()),
current_event_path: Vec::new(),
}
}
pub fn unify(&mut self, subs: &s::Subs, from: s::Variable, to: s::Variable) {
let to = to.as_schema(subs);
let from = from.as_schema(subs);
self.add_event(Event::VariableUnified { to, from });
}
pub fn set_content(&mut self, subs: &s::Subs, var: s::Variable, content: s::Content) {
let variable = var.as_schema(subs);
let content = content.as_schema(subs);
self.add_event(Event::VariableSetDescriptor {
variable,
content: Some(content),
rank: None,
});
}
pub fn set_rank(&mut self, subs: &s::Subs, var: s::Variable, rank: s::Rank) {
let variable = var.as_schema(subs);
let rank = rank.as_schema(subs);
self.add_event(Event::VariableSetDescriptor {
variable,
rank: Some(rank),
content: None,
});
}
pub fn set_descriptor(&mut self, subs: &s::Subs, var: s::Variable, descriptor: s::Descriptor) {
let variable = var.as_schema(subs);
let rank = descriptor.rank.as_schema(subs);
let content = descriptor.content.as_schema(subs);
self.add_event(Event::VariableSetDescriptor {
variable,
rank: Some(rank),
content: Some(content),
});
}
pub fn start_unification(
&mut self,
subs: &s::Subs,
left: s::Variable,
right: s::Variable,
mode: roc_solve_schema::UnificationMode,
) {
let left = left.as_schema(subs);
let right = right.as_schema(subs);
let mode = mode.as_schema(subs);
let subevents = Vec::new();
self.add_event(Event::Unification {
left,
right,
mode,
subevents,
success: None,
});
}
pub fn end_unification(
&mut self,
subs: &s::Subs,
left: s::Variable,
right: s::Variable,
success: bool,
) {
let current_event = self.get_path_event();
match current_event {
EventW::Sub(Event::Unification {
left: l,
right: r,
success: s,
..
}) => {
assert_eq!(left.as_schema(subs), *l);
assert_eq!(right.as_schema(subs), *r);
assert!(s.is_none());
*s = Some(success);
}
_ => panic!("end_unification called when not in a unification"),
}
self.current_event_path.pop();
}
pub fn write(&self, writer: impl std::io::Write) -> Result<(), Box<dyn Error>> {
self.events.write(writer)?;
Ok(())
}
fn add_event(&mut self, event: impl Into<Event>) {
let mut event = event.into();
let is_appendable = EventW::Sub(&mut event).appendable();
let event = event;
let path_event = self.get_path_event();
let new_event_index = path_event.append(event);
if is_appendable {
self.current_event_path.push(new_event_index);
}
}
fn get_path_event(&mut self) -> EventW {
let mut event = EventW::Top(&mut self.events);
for i in &self.current_event_path {
event = event.index(*i);
}
event
}
}
enum EventW<'a> {
Top(&'a mut AllEvents),
Sub(&'a mut Event),
}
impl<'a> EventW<'a> {
fn append(self, event: Event) -> usize {
let list = self.subevents_mut().unwrap();
let index = list.len();
list.push(event);
index
}
fn appendable(self) -> bool {
self.subevents().is_some()
}
fn index(self, index: usize) -> EventW<'a> {
Self::Sub(&mut self.subevents_mut().unwrap()[index])
}
}
impl<'a> EventW<'a> {
fn subevents(self) -> Option<&'a Vec<Event>> {
use EventW::*;
match self {
Top(events) => Some(&events.0),
Sub(Event::Unification { subevents, .. }) => Some(subevents),
Sub(Event::VariableUnified { .. } | Event::VariableSetDescriptor { .. }) => None,
}
}
fn subevents_mut(self) -> Option<&'a mut Vec<Event>> {
use EventW::*;
match self {
Top(events) => Some(&mut events.0),
Sub(Event::Unification { subevents, .. }) => Some(subevents),
Sub(Event::VariableUnified { .. } | Event::VariableSetDescriptor { .. }) => None,
}
}
}

View file

@ -0,0 +1,323 @@
use std::collections::HashMap;
use roc_module::{ident, symbol};
use roc_types::{
num,
subs::{self, GetSubsSlice, Subs, SubsIndex, SubsSlice, UnionLabels},
types,
};
use roc_checkmate_schema::{
AliasKind, AliasTypeVariables, ClosureType, Content, NumericRange, NumericRangeKind, Rank,
RecordField, RecordFieldKind, Symbol, TagUnionExtension, UnificationMode,
UnspecializedClosureType, Variable,
};
pub trait AsSchema<T> {
fn as_schema(&self, subs: &Subs) -> T;
}
impl<T, U> AsSchema<Option<U>> for Option<T>
where
T: AsSchema<U>,
T: Copy,
{
fn as_schema(&self, subs: &Subs) -> Option<U> {
self.map(|i| i.as_schema(subs))
}
}
impl<T, U> AsSchema<Vec<U>> for &[T]
where
T: AsSchema<U>,
{
fn as_schema(&self, subs: &Subs) -> Vec<U> {
self.iter().map(|i| i.as_schema(subs)).collect()
}
}
impl<T, U> AsSchema<U> for SubsIndex<T>
where
Subs: std::ops::Index<SubsIndex<T>, Output = T>,
T: AsSchema<U>,
{
fn as_schema(&self, subs: &Subs) -> U {
subs[*self].as_schema(subs)
}
}
impl<T, U> AsSchema<Vec<U>> for SubsSlice<T>
where
Subs: GetSubsSlice<T>,
T: AsSchema<U>,
{
fn as_schema(&self, subs: &Subs) -> Vec<U> {
subs.get_subs_slice(*self)
.iter()
.map(|i| i.as_schema(subs))
.collect()
}
}
impl AsSchema<Content> for subs::Content {
fn as_schema(&self, subs: &Subs) -> Content {
use {subs::Content as A, Content as B};
match self {
A::FlexVar(name) => B::Flex(name.as_schema(subs)),
A::RigidVar(name) => B::Rigid(name.as_schema(subs)),
A::FlexAbleVar(name, abilities) => {
B::FlexAble(name.as_schema(subs), abilities.as_schema(subs))
}
A::RigidAbleVar(name, abilities) => {
B::RigidAble(name.as_schema(subs), abilities.as_schema(subs))
}
A::RecursionVar {
structure,
opt_name,
} => B::Recursive(opt_name.as_schema(subs), structure.as_schema(subs)),
A::LambdaSet(lambda_set) => lambda_set.as_schema(subs),
A::ErasedLambda => B::ErasedLambda(),
A::Structure(flat_type) => flat_type.as_schema(subs),
A::Alias(name, type_vars, real_var, kind) => B::Alias(
name.as_schema(subs),
type_vars.as_schema(subs),
real_var.as_schema(subs),
kind.as_schema(subs),
),
A::RangedNumber(range) => B::RangedNumber(range.as_schema(subs)),
A::Error => B::Error(),
}
}
}
impl AsSchema<Content> for subs::FlatType {
fn as_schema(&self, subs: &Subs) -> Content {
match self {
subs::FlatType::Apply(symbol, variables) => {
Content::Apply(symbol.as_schema(subs), variables.as_schema(subs))
}
subs::FlatType::Func(arguments, closure, ret) => Content::Function(
arguments.as_schema(subs),
closure.as_schema(subs),
ret.as_schema(subs),
),
subs::FlatType::Record(fields, ext) => {
Content::Record(fields.as_schema(subs), ext.as_schema(subs))
}
subs::FlatType::Tuple(elems, ext) => {
Content::Tuple(elems.as_schema(subs), ext.as_schema(subs))
}
subs::FlatType::TagUnion(tags, ext) => {
Content::TagUnion(tags.as_schema(subs), ext.as_schema(subs))
}
subs::FlatType::FunctionOrTagUnion(tags, functions, ext) => {
Content::FunctionOrTagUnion(
functions.as_schema(subs),
tags.as_schema(subs),
ext.as_schema(subs),
)
}
subs::FlatType::RecursiveTagUnion(rec_var, tags, ext) => Content::RecursiveTagUnion(
rec_var.as_schema(subs),
tags.as_schema(subs),
ext.as_schema(subs),
),
subs::FlatType::EmptyRecord => Content::EmptyRecord(),
subs::FlatType::EmptyTuple => Content::EmptyTuple(),
subs::FlatType::EmptyTagUnion => Content::EmptyTagUnion(),
}
}
}
impl AsSchema<Content> for subs::LambdaSet {
fn as_schema(&self, subs: &Subs) -> Content {
let subs::LambdaSet {
solved,
unspecialized,
recursion_var,
ambient_function,
} = self;
Content::LambdaSet(
solved.as_schema(subs),
unspecialized.as_schema(subs),
recursion_var.as_schema(subs),
ambient_function.as_schema(subs),
)
}
}
impl AsSchema<String> for ident::Lowercase {
fn as_schema(&self, _subs: &Subs) -> String {
self.to_string()
}
}
impl AsSchema<Symbol> for symbol::Symbol {
fn as_schema(&self, _subs: &Subs) -> Symbol {
Symbol(format!("{:#?}", self))
}
}
impl AsSchema<Variable> for subs::Variable {
fn as_schema(&self, _subs: &Subs) -> Variable {
Variable(self.index())
}
}
impl AsSchema<Option<Variable>> for subs::OptVariable {
fn as_schema(&self, _subs: &Subs) -> Option<Variable> {
self.into_variable().map(|i| i.as_schema(_subs))
}
}
impl AsSchema<Vec<ClosureType>> for UnionLabels<symbol::Symbol> {
fn as_schema(&self, subs: &Subs) -> Vec<ClosureType> {
self.iter_from_subs(subs)
.map(|(function, environment)| ClosureType {
function: function.as_schema(subs),
environment: environment.as_schema(subs),
})
.collect()
}
}
impl AsSchema<UnspecializedClosureType> for types::Uls {
fn as_schema(&self, subs: &Subs) -> UnspecializedClosureType {
let types::Uls(specialization, ability_member, lambda_set_region) = self;
UnspecializedClosureType {
specialization: specialization.as_schema(subs),
ability_member: ability_member.as_schema(subs),
lambda_set_region: *lambda_set_region,
}
}
}
impl AsSchema<AliasTypeVariables> for subs::AliasVariables {
fn as_schema(&self, subs: &Subs) -> AliasTypeVariables {
let type_variables = self.type_variables().as_schema(subs);
let lambda_set_variables = self.lambda_set_variables().as_schema(subs);
let infer_ext_in_output_position_variables =
self.infer_ext_in_output_variables().as_schema(subs);
AliasTypeVariables {
type_variables,
lambda_set_variables,
infer_ext_in_output_position_variables,
}
}
}
impl AsSchema<AliasKind> for types::AliasKind {
fn as_schema(&self, _subs: &Subs) -> AliasKind {
match self {
types::AliasKind::Structural => AliasKind::Structural,
types::AliasKind::Opaque => AliasKind::Opaque,
}
}
}
impl AsSchema<HashMap<String, RecordField>> for subs::RecordFields {
fn as_schema(&self, subs: &Subs) -> HashMap<String, RecordField> {
let mut map = HashMap::new();
for (name, var, field) in self.iter_all() {
let name = name.as_schema(subs);
let field_type = var.as_schema(subs);
let kind = field.as_schema(subs);
map.insert(name, RecordField { field_type, kind });
}
map
}
}
impl AsSchema<RecordFieldKind> for types::RecordField<()> {
fn as_schema(&self, _subs: &Subs) -> RecordFieldKind {
match self {
types::RecordField::Demanded(_) => RecordFieldKind::Demanded,
types::RecordField::Required(_) => RecordFieldKind::Required { rigid: false },
types::RecordField::Optional(_) => RecordFieldKind::Optional { rigid: false },
types::RecordField::RigidRequired(_) => RecordFieldKind::Required { rigid: true },
types::RecordField::RigidOptional(_) => RecordFieldKind::Optional { rigid: true },
}
}
}
impl AsSchema<HashMap<u32, Variable>> for subs::TupleElems {
fn as_schema(&self, subs: &Subs) -> HashMap<u32, Variable> {
let mut map = HashMap::new();
for (index, var) in self.iter_all() {
let name = subs[index] as _;
let var = var.as_schema(subs);
map.insert(name, var);
}
map
}
}
impl AsSchema<HashMap<String, Vec<Variable>>> for subs::UnionTags {
fn as_schema(&self, subs: &Subs) -> HashMap<String, Vec<Variable>> {
let mut map = HashMap::new();
for (tag, payloads) in self.iter_from_subs(subs) {
map.insert(tag.as_schema(subs), payloads.as_schema(subs));
}
map
}
}
impl AsSchema<TagUnionExtension> for subs::TagExt {
fn as_schema(&self, subs: &Subs) -> TagUnionExtension {
match self {
subs::TagExt::Openness(var) => TagUnionExtension::Openness(var.as_schema(subs)),
subs::TagExt::Any(var) => TagUnionExtension::Any(var.as_schema(subs)),
}
}
}
impl AsSchema<NumericRange> for num::NumericRange {
fn as_schema(&self, _subs: &Subs) -> NumericRange {
let kind =
match self {
num::NumericRange::IntAtLeastSigned(_)
| num::NumericRange::IntAtLeastEitherSign(_) => NumericRangeKind::Int,
num::NumericRange::NumAtLeastSigned(_)
| num::NumericRange::NumAtLeastEitherSign(_) => NumericRangeKind::AnyNum,
};
let min_width = self.min_width();
let (signedness, width) = min_width.signedness_and_width();
let signed = signedness.is_signed();
NumericRange {
kind,
signed,
min_width: width,
}
}
}
impl AsSchema<String> for ident::TagName {
fn as_schema(&self, _subs: &Subs) -> String {
self.0.to_string()
}
}
impl AsSchema<Rank> for subs::Rank {
fn as_schema(&self, _subs: &Subs) -> Rank {
Rank(self.into_usize() as _)
}
}
impl AsSchema<UnificationMode> for roc_solve_schema::UnificationMode {
fn as_schema(&self, _subs: &Subs) -> UnificationMode {
if self.is_eq() {
UnificationMode::Eq
} else if self.is_present() {
UnificationMode::Present
} else if self.is_lambda_set_specialization() {
UnificationMode::LambdaSetSpecialization
} else {
unreachable!()
}
}
}

View file

@ -0,0 +1,62 @@
mod collector;
mod convert;
pub use collector::Collector;
pub fn is_checkmate_enabled() -> bool {
#[cfg(debug_assertions)]
{
let flag = std::env::var("ROC_CHECKMATE");
flag.as_deref() == Ok("1")
}
#[cfg(not(debug_assertions))]
{
false
}
}
#[macro_export]
macro_rules! debug_checkmate {
($opt_collector:expr, $cm:ident => $expr:expr) => {
#[cfg(debug_assertions)]
{
if let Some($cm) = $opt_collector.as_mut() {
$expr
}
}
};
}
#[macro_export]
macro_rules! dump_checkmate {
($opt_collector:expr) => {
#[cfg(debug_assertions)]
{
if let Some(cm) = $opt_collector.as_ref() {
$crate::dump_checkmate(cm);
}
}
};
}
pub fn dump_checkmate(collector: &Collector) {
let timestamp = chrono::Local::now().format("%Y%m%d_%H-%M-%S");
let filename = format!("checkmate_{timestamp}.json");
let fi = std::fs::File::create(&filename).unwrap();
collector.write(fi).unwrap();
eprintln!("Wrote checkmate output to {filename}");
}
#[macro_export]
macro_rules! with_checkmate {
({ on => $on:expr, off => $off:expr, }) => {{
#[cfg(debug_assertions)]
{
$on
}
#[cfg(not(debug_assertions))]
{
$off
}
}};
}

View file

@ -0,0 +1,17 @@
/node_modules
/.pnp
.pnp.js
/coverage
/build
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,45 @@
{
"name": "checkmate",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"scripts": {
"start": "react-scripts start",
"build": "build:codegen && react-scripts build",
"build:codegen": "node ./scripts/gen_schema_dts.js",
"check": "tsc",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.38",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"json-schema-to-typescript": "^13.0.2",
"react-scripts": "5.0.1",
"tailwindcss": "^3.3.3",
"typescript": "^4.9.5"
}
}

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="checkmate in N" />
<title>Checkmate</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,24 @@
const {compileFromFile} = require("json-schema-to-typescript");
const fs = require("node:fs/promises");
const path = require("node:path");
const SCHEMA_PATH = path.resolve(
__dirname,
"..",
"..",
"schema.json"
);
const DTS_PATH = path.resolve(
__dirname,
"..",
"src",
"schema.d.ts"
);
async function main() {
const result = await compileFromFile(SCHEMA_PATH);
await fs.writeFile(DTS_PATH, result);
}
main().catch(console.error);

View file

@ -0,0 +1,13 @@
import React from "react";
export default function App() {
return (
<div className="App">
<header className="App-header">
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
</header>
</div>
);
}

View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -0,0 +1,13 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View file

@ -0,0 +1,239 @@
/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/
export type Event =
| {
left: Variable;
mode: UnificationMode;
right: Variable;
subevents: Event[];
success?: boolean | null;
type: "Unification";
[k: string]: unknown;
}
| {
from: Variable;
to: Variable;
type: "VariableUnified";
[k: string]: unknown;
}
| {
content?: Content | null;
rank?: Rank | null;
type: "VariableSetDescriptor";
variable: Variable;
[k: string]: unknown;
};
export type Variable = number;
export type UnificationMode =
| {
type: "Eq";
[k: string]: unknown;
}
| {
type: "Present";
[k: string]: unknown;
}
| {
type: "LambdaSetSpecialization";
[k: string]: unknown;
};
export type Content =
| {
name?: string | null;
type: "Flex";
[k: string]: unknown;
}
| {
name: string;
type: "Rigid";
[k: string]: unknown;
}
| {
abilities: Symbol[];
name?: string | null;
type: "FlexAble";
[k: string]: unknown;
}
| {
abilities: Symbol[];
name: string;
type: "RigidAble";
[k: string]: unknown;
}
| {
name?: string | null;
structure: Variable;
type: "Recursive";
[k: string]: unknown;
}
| {
ambient_function: Variable;
recursion_var?: Variable | null;
solved: ClosureType[];
type: "LambdaSet";
unspecialized: UnspecializedClosureType[];
[k: string]: unknown;
}
| {
kind: AliasKind;
name: Symbol;
real_variable: Variable;
type: "Alias";
variables: AliasTypeVariables;
[k: string]: unknown;
}
| {
symbol: Symbol;
type: "Apply";
variables: Variable[];
[k: string]: unknown;
}
| {
arguments: Variable[];
lambda_type: Variable;
ret: Variable;
type: "Function";
[k: string]: unknown;
}
| {
extension: Variable;
fields: {
[k: string]: RecordField;
};
type: "Record";
[k: string]: unknown;
}
| {
elements: {
[k: string]: Variable;
};
extension: Variable;
type: "Tuple";
[k: string]: unknown;
}
| {
extension: TagUnionExtension;
tags: {
[k: string]: Variable[];
};
type: "TagUnion";
[k: string]: unknown;
}
| {
extension: TagUnionExtension;
functions: Symbol[];
tags: string[];
type: "FunctionOrTagUnion";
[k: string]: unknown;
}
| {
extension: TagUnionExtension;
recursion_var: Variable;
tags: {
[k: string]: Variable[];
};
type: "RecursiveTagUnion";
[k: string]: unknown;
}
| {
type: "EmptyRecord";
[k: string]: unknown;
}
| {
type: "EmptyTuple";
[k: string]: unknown;
}
| {
type: "EmptyTagUnion";
[k: string]: unknown;
}
| {
range: NumericRange;
type: "RangedNumber";
[k: string]: unknown;
}
| {
type: "Error";
[k: string]: unknown;
};
export type Symbol = string;
export type AliasKind =
| {
type: "Structural";
[k: string]: unknown;
}
| {
type: "Opaque";
[k: string]: unknown;
};
export type RecordFieldKind =
| {
type: "Demanded";
[k: string]: unknown;
}
| {
rigid: boolean;
type: "Required";
[k: string]: unknown;
}
| {
rigid: boolean;
type: "Optional";
[k: string]: unknown;
};
export type TagUnionExtension =
| {
type: "Openness";
variable: Variable;
[k: string]: unknown;
}
| {
type: "Any";
variable: Variable;
[k: string]: unknown;
};
export type NumericRangeKind =
| {
type: "Int";
[k: string]: unknown;
}
| {
type: "AnyNum";
[k: string]: unknown;
};
export type Rank = number;
export type AllEvents = Event[];
export interface ClosureType {
environment: Variable[];
function: Symbol;
[k: string]: unknown;
}
export interface UnspecializedClosureType {
ability_member: Symbol;
lambda_set_region: number;
specialization: Variable;
[k: string]: unknown;
}
export interface AliasTypeVariables {
infer_ext_in_output_position_variables: Variable[];
lambda_set_variables: Variable[];
type_variables: Variable[];
[k: string]: unknown;
}
export interface RecordField {
field_type: Variable;
kind: RecordFieldKind;
[k: string]: unknown;
}
export interface NumericRange {
kind: NumericRangeKind;
min_width: number;
signed: boolean;
[k: string]: unknown;
}

View file

@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
mode: 'jit',
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

View file

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}

View file

@ -0,0 +1,13 @@
[package]
name = "roc_checkmate_schema"
description = "Schema for checkmate."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
serde.workspace = true
serde_json.workspace = true
schemars.workspace = true

View file

@ -0,0 +1,225 @@
use std::collections::HashMap;
use schemars::{schema::RootSchema, schema_for, JsonSchema};
use serde::Serialize;
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum Constraint {}
#[derive(Serialize, JsonSchema, Debug, PartialEq)]
pub struct Variable(pub u32);
macro_rules! impl_content {
($($name:ident { $($arg:ident: $ty:ty,)* },)*) => {
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum Content {
$(
$name {
$($arg: $ty),*
},
)*
}
impl Content {
$(
#[allow(non_snake_case)]
pub fn $name($($arg: $ty),*) -> Self {
Self::$name { $($arg),* }
}
)*
}
};
}
impl_content! {
Flex {
name: Option<String>,
},
Rigid {
name: String,
},
FlexAble {
name: Option<String>,
abilities: Vec<Symbol>,
},
RigidAble {
name: String,
abilities: Vec<Symbol>,
},
Recursive {
name: Option<String>,
structure: Variable,
},
LambdaSet {
solved: Vec<ClosureType>,
unspecialized: Vec<UnspecializedClosureType>,
recursion_var: Option<Variable>,
ambient_function: Variable,
},
ErasedLambda {},
Alias {
name: Symbol,
variables: AliasTypeVariables,
real_variable: Variable,
kind: AliasKind,
},
Apply {
symbol: Symbol,
variables: Vec<Variable>,
},
Function {
arguments: Vec<Variable>,
lambda_type: Variable,
ret: Variable,
},
Record {
fields: HashMap<String, RecordField>,
extension: Variable,
},
Tuple {
elements: HashMap<u32, Variable>,
extension: Variable,
},
TagUnion {
tags: HashMap<String, Vec<Variable>>,
extension: TagUnionExtension,
},
FunctionOrTagUnion {
functions: Vec<Symbol>,
tags: Vec<String>,
extension: TagUnionExtension,
},
RecursiveTagUnion {
recursion_var: Variable,
tags: HashMap<String, Vec<Variable>>,
extension: TagUnionExtension,
},
EmptyRecord {},
EmptyTuple {},
EmptyTagUnion {},
RangedNumber {
range: NumericRange,
},
Error {},
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct ClosureType {
pub function: Symbol,
pub environment: Vec<Variable>,
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct UnspecializedClosureType {
pub specialization: Variable,
pub ability_member: Symbol,
pub lambda_set_region: u8,
}
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum AliasKind {
Structural,
Opaque,
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct AliasTypeVariables {
pub type_variables: Vec<Variable>,
pub lambda_set_variables: Vec<Variable>,
pub infer_ext_in_output_position_variables: Vec<Variable>,
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct RecordField {
pub kind: RecordFieldKind,
pub field_type: Variable,
}
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum RecordFieldKind {
Demanded,
Required { rigid: bool },
Optional { rigid: bool },
}
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type", content = "variable")]
pub enum TagUnionExtension {
Openness(Variable),
Any(Variable),
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct NumericRange {
pub kind: NumericRangeKind,
pub signed: bool,
pub min_width: u32,
}
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum NumericRangeKind {
Int,
AnyNum,
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct Rank(pub u32);
#[derive(Serialize, JsonSchema, Debug)]
pub struct Descriptor {
pub content: Content,
pub rank: Rank,
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct Symbol(
// TODO: should this be module ID + symbol?
pub String,
);
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum UnificationMode {
Eq,
Present,
LambdaSetSpecialization,
}
#[derive(Serialize, JsonSchema, Debug)]
#[serde(tag = "type")]
pub enum Event {
Unification {
left: Variable,
right: Variable,
mode: UnificationMode,
success: Option<bool>,
subevents: Vec<Event>,
},
VariableUnified {
from: Variable,
to: Variable,
},
VariableSetDescriptor {
variable: Variable,
rank: Option<Rank>,
content: Option<Content>,
},
}
#[derive(Serialize, JsonSchema, Debug)]
pub struct AllEvents(pub Vec<Event>);
impl AllEvents {
pub fn schema() -> RootSchema {
schema_for!(AllEvents)
}
pub fn write(&self, writer: impl std::io::Write) -> Result<(), serde_json::Error> {
serde_json::to_writer(writer, self)
}
}

View file

@ -9,11 +9,13 @@ version.workspace = true
[dependencies]
roc_can = { path = "../can" }
roc_checkmate = { path = "../checkmate" }
roc_collections = { path = "../collections" }
roc_derive_key = { path = "../derive_key" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_region = { path = "../region" }
roc_solve_schema = { path = "../solve_schema" }
roc_types = { path = "../types" }
roc_unify = { path = "../unify" }

View file

@ -1,6 +1,8 @@
use roc_can::{abilities::SpecializationLambdaSets, module::ExposedByModule};
use roc_checkmate::with_checkmate;
use roc_error_macros::internal_error;
use roc_module::symbol::{IdentIds, Symbol};
use roc_solve_schema::UnificationMode;
use roc_types::{
subs::{instantiate_rigids, Subs, Variable},
types::Polarity,
@ -71,13 +73,20 @@ impl Env<'_> {
}
pub fn unify(&mut self, left: Variable, right: Variable) {
use roc_unify::unify::{unify, Env, Mode, Unified};
use roc_unify::{
unify::{unify, Unified},
Env,
};
let unified = unify(
&mut Env::new(self.subs),
// TODO(checkmate): pass checkmate through
&mut with_checkmate!({
on => Env::new(self.subs, None),
off => Env::new(self.subs),
}),
left,
right,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_PATTERN,
);
@ -103,15 +112,22 @@ impl Env<'_> {
specialization_type: Variable,
ability_member: Symbol,
) -> SpecializationLambdaSets {
use roc_unify::unify::{unify_introduced_ability_specialization, Env, Mode, Unified};
use roc_unify::{
unify::{unify_introduced_ability_specialization, Unified},
Env,
};
let member_signature = self.import_builtin_symbol_var(ability_member);
let unified = unify_introduced_ability_specialization(
&mut Env::new(self.subs),
// TODO(checkmate): pass checkmate through
&mut with_checkmate!({
on => Env::new(self.subs, None),
off => Env::new(self.subs),
}),
member_signature,
specialization_type,
Mode::EQ,
UnificationMode::EQ,
);
match unified {

View file

@ -1890,7 +1890,7 @@ fn build_tag<'a, 'ctx>(
let roc_union =
RocUnion::untagged_from_slices(layout_interner, env.context, &[other_fields]);
if tag_id == *nullable_id as _ {
if tag_id == *nullable_id as u16 {
let output_type = roc_union.struct_type().ptr_type(AddressSpace::default());
return output_type.const_null().into();

View file

@ -800,7 +800,7 @@ fn build_clone_tag_help<'a, 'ctx>(
let mut cases = Vec::with_capacity_in(other_tags.len(), env.arena);
for i in 0..other_tags.len() + 1 {
if i == nullable_id as _ {
if i == nullable_id as usize {
continue;
}

View file

@ -9,11 +9,13 @@ version.workspace = true
[dependencies]
roc_can = { path = "../can" }
roc_checkmate = { path = "../checkmate" }
roc_collections = { path = "../collections" }
roc_derive = { path = "../derive" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_solve = { path = "../solve" }
roc_solve_schema = { path = "../solve_schema" }
roc_types = { path = "../types" }
roc_unify = { path = "../unify" }

View file

@ -6,6 +6,7 @@ use std::sync::{Arc, RwLock};
use bumpalo::Bump;
use roc_can::abilities::AbilitiesStore;
use roc_can::module::ExposedByModule;
use roc_checkmate::with_checkmate;
use roc_collections::MutMap;
use roc_derive::SharedDerivedModule;
use roc_error_macros::internal_error;
@ -15,11 +16,13 @@ use roc_solve::ability::AbilityResolver;
use roc_solve::specialize::{compact_lambda_sets_of_vars, Phase};
use roc_solve::Pools;
use roc_solve::{DerivedEnv, SolveEnv};
use roc_solve_schema::UnificationMode;
use roc_types::subs::{get_member_lambda_sets_at_region, Content, FlatType, LambdaSet};
use roc_types::subs::{ExposedTypesStorageSubs, Subs, Variable};
use roc_types::types::Polarity;
use roc_unify::unify::MetaCollector;
use roc_unify::unify::{Env as UEnv, Mode, Unified};
use roc_unify::unify::Unified;
use roc_unify::Env as UEnv;
pub use roc_solve::ability::{ResolveError, Resolved};
pub use roc_types::subs::instantiate_rigids;
@ -360,10 +363,14 @@ pub fn unify(
"derived module can only unify its subs in its own context!"
);
let unified = roc_unify::unify::unify_with_collector::<ChangedVariableCollector>(
&mut UEnv::new(subs),
// TODO(checkmate): pass checkmate through
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
left,
right,
Mode::EQ,
UnificationMode::EQ,
Polarity::Pos,
);
@ -388,6 +395,9 @@ pub fn unify(
derived_env: &derived_env,
arena,
pools: &mut pools,
#[cfg(debug_assertions)]
checkmate: &mut None,
};
compact_lambda_sets_of_vars(&mut env, lambda_sets_to_specialize, &late_phase)

View file

@ -10,6 +10,7 @@ version.workspace = true
[dependencies]
roc_builtins = { path = "../builtins" }
roc_can = { path = "../can" }
roc_checkmate = { path = "../checkmate" }
roc_collections = { path = "../collections" }
roc_constrain = { path = "../constrain" }
roc_debug_flags = { path = "../debug_flags" }

View file

@ -331,6 +331,13 @@ fn start_phase<'a>(
let derived_module = SharedDerivedModule::clone(&state.derived_module);
#[cfg(debug_assertions)]
let checkmate = if roc_checkmate::is_checkmate_enabled() {
Some(roc_checkmate::Collector::new())
} else {
None
};
BuildTask::solve_module(
module,
ident_ids,
@ -347,6 +354,9 @@ fn start_phase<'a>(
declarations,
state.cached_types.clone(),
derived_module,
//
#[cfg(debug_assertions)]
checkmate,
)
}
Phase::FindSpecializations => {
@ -361,6 +371,9 @@ fn start_phase<'a>(
ident_ids,
abilities_store,
expectations,
//
#[cfg(debug_assertions)]
checkmate: _,
} = typechecked;
let mut imported_module_thunks = bumpalo::collections::Vec::new_in(arena);
@ -567,6 +580,9 @@ enum Msg<'a> {
abilities_store: AbilitiesStore,
loc_expects: LocExpects,
loc_dbgs: LocDbgs,
#[cfg(debug_assertions)]
checkmate: Option<roc_checkmate::Collector>,
},
FinishedAllTypeChecking {
solved_subs: Solved<Subs>,
@ -577,6 +593,9 @@ enum Msg<'a> {
dep_idents: IdentIdsByModule,
documentation: VecMap<ModuleId, ModuleDocumentation>,
abilities_store: AbilitiesStore,
#[cfg(debug_assertions)]
checkmate: Option<roc_checkmate::Collector>,
},
FoundSpecializations {
module_id: ModuleId,
@ -878,6 +897,9 @@ enum BuildTask<'a> {
dep_idents: IdentIdsByModule,
cached_subs: CachedTypeState,
derived_module: SharedDerivedModule,
#[cfg(debug_assertions)]
checkmate: Option<roc_checkmate::Collector>,
},
BuildPendingSpecializations {
module_timing: ModuleTiming,
@ -1393,6 +1415,9 @@ fn state_thread_step<'a>(
dep_idents,
documentation,
abilities_store,
#[cfg(debug_assertions)]
checkmate,
} => {
// We're done! There should be no more messages pending.
debug_assert!(msg_rx.is_empty());
@ -1412,6 +1437,9 @@ fn state_thread_step<'a>(
dep_idents,
documentation,
abilities_store,
//
#[cfg(debug_assertions)]
checkmate,
);
Ok(ControlFlow::Break(LoadResult::TypeChecked(typechecked)))
@ -2387,6 +2415,9 @@ fn update<'a>(
abilities_store,
loc_expects,
loc_dbgs,
#[cfg(debug_assertions)]
checkmate,
} => {
log!("solved types for {:?}", module_id);
module_timing.end_time = Instant::now();
@ -2496,6 +2527,9 @@ fn update<'a>(
dep_idents,
documentation,
abilities_store,
#[cfg(debug_assertions)]
checkmate,
})
.map_err(|_| LoadingProblem::MsgChannelDied)?;
@ -2529,6 +2563,9 @@ fn update<'a>(
ident_ids,
abilities_store,
expectations: opt_expectations,
#[cfg(debug_assertions)]
checkmate,
};
state
@ -3176,6 +3213,8 @@ fn finish(
dep_idents: IdentIdsByModule,
documentation: VecMap<ModuleId, ModuleDocumentation>,
abilities_store: AbilitiesStore,
//
#[cfg(debug_assertions)] checkmate: Option<roc_checkmate::Collector>,
) -> LoadedModule {
let module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
@ -3207,6 +3246,8 @@ fn finish(
let exposed_values = exposed_vars_by_symbol.iter().map(|x| x.0).collect();
roc_checkmate::dump_checkmate!(checkmate);
LoadedModule {
module_id: state.root_id,
interns,
@ -4441,6 +4482,8 @@ impl<'a> BuildTask<'a> {
declarations: Declarations,
cached_subs: CachedTypeState,
derived_module: SharedDerivedModule,
#[cfg(debug_assertions)] checkmate: Option<roc_checkmate::Collector>,
) -> Self {
let exposed_by_module = exposed_types.retain_modules(imported_modules.keys());
@ -4463,6 +4506,9 @@ impl<'a> BuildTask<'a> {
module_timing,
cached_subs,
derived_module,
#[cfg(debug_assertions)]
checkmate,
}
}
}
@ -4718,6 +4764,9 @@ struct SolveResult {
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
problems: Vec<TypeError>,
abilities_store: AbilitiesStore,
#[cfg(debug_assertions)]
checkmate: Option<roc_checkmate::Collector>,
}
#[allow(clippy::complexity)]
@ -4731,6 +4780,8 @@ fn run_solve_solve(
var_store: VarStore,
module: Module,
derived_module: SharedDerivedModule,
#[cfg(debug_assertions)] checkmate: Option<roc_checkmate::Collector>,
) -> SolveResult {
let Module {
exposed_symbols,
@ -4782,6 +4833,8 @@ fn run_solve_solve(
pending_derives,
exposed_by_module: &exposed_for_module.exposed_by_module,
derived_module,
#[cfg(debug_assertions)]
checkmate,
};
let solve_output = roc_solve::module::run_solve(
@ -4824,6 +4877,9 @@ fn run_solve_solve(
scope: _,
errors,
resolved_abilities_store,
#[cfg(debug_assertions)]
checkmate,
} = solve_output;
SolveResult {
@ -4832,6 +4888,9 @@ fn run_solve_solve(
exposed_vars_by_symbol,
problems: errors,
abilities_store: resolved_abilities_store,
#[cfg(debug_assertions)]
checkmate,
}
}
@ -4850,6 +4909,8 @@ fn run_solve<'a>(
dep_idents: IdentIdsByModule,
cached_types: CachedTypeState,
derived_module: SharedDerivedModule,
#[cfg(debug_assertions)] checkmate: Option<roc_checkmate::Collector>,
) -> Msg<'a> {
let solve_start = Instant::now();
@ -4876,6 +4937,9 @@ fn run_solve<'a>(
var_store,
module,
derived_module,
//
#[cfg(debug_assertions)]
checkmate,
),
Some(TypeState {
subs,
@ -4888,6 +4952,9 @@ fn run_solve<'a>(
exposed_vars_by_symbol,
problems: vec![],
abilities_store: abilities,
#[cfg(debug_assertions)]
checkmate: None,
},
}
} else {
@ -4901,6 +4968,9 @@ fn run_solve<'a>(
var_store,
module,
derived_module,
//
#[cfg(debug_assertions)]
checkmate,
)
}
};
@ -4911,6 +4981,9 @@ fn run_solve<'a>(
exposed_vars_by_symbol,
problems,
abilities_store,
#[cfg(debug_assertions)]
checkmate,
} = solve_result;
let exposed_types = roc_solve::module::exposed_types_storage_subs(
@ -4945,6 +5018,9 @@ fn run_solve<'a>(
abilities_store,
loc_expects,
loc_dbgs,
#[cfg(debug_assertions)]
checkmate,
}
}
@ -6160,6 +6236,9 @@ fn run_task<'a>(
dep_idents,
cached_subs,
derived_module,
#[cfg(debug_assertions)]
checkmate,
} => Ok(run_solve(
module,
ident_ids,
@ -6175,6 +6254,9 @@ fn run_task<'a>(
dep_idents,
cached_subs,
derived_module,
//
#[cfg(debug_assertions)]
checkmate,
)),
BuildPendingSpecializations {
module_id,

View file

@ -125,6 +125,9 @@ pub struct TypeCheckedModule<'a> {
pub ident_ids: IdentIds,
pub abilities_store: AbilitiesStore,
pub expectations: Option<Expectations>,
#[cfg(debug_assertions)]
pub checkmate: Option<roc_checkmate::Collector>,
}
#[derive(Debug)]

View file

@ -910,7 +910,7 @@ fn get_tag_id_payloads(union_layout: UnionLayout, tag_id: TagIdIntType) -> TagPa
nullable_id,
other_fields,
} => {
if tag_id == nullable_id as _ {
if tag_id == nullable_id as u16 {
TagPayloads::Payloads(&[])
} else {
check_tag_id_oob!(2);

View file

@ -1139,7 +1139,7 @@ fn to_relevant_branch_help<'a>(
EnumLiteral { tag_id, .. } => match test {
IsByte {
tag_id: test_id, ..
} if tag_id == *test_id as _ => {
} if tag_id == *test_id as u8 => {
start.extend(end);
Some(Branch {
goal: branch.goal,

View file

@ -9,6 +9,7 @@ version.workspace = true
[dependencies]
roc_can = { path = "../can" }
roc_checkmate = { path = "../checkmate" }
roc_collections = { path = "../collections" }
roc_debug_flags = { path = "../debug_flags" }
roc_derive = { path = "../derive" }
@ -20,6 +21,7 @@ roc_packaging = { path = "../../packaging" }
roc_problem = { path = "../problem" }
roc_region = { path = "../region" }
roc_solve_problem = { path = "../solve_problem" }
roc_solve_schema = { path = "../solve_schema" }
roc_types = { path = "../types" }
roc_unify = { path = "../unify" }

View file

@ -1,5 +1,6 @@
use roc_can::abilities::AbilitiesStore;
use roc_can::expr::PendingDerives;
use roc_checkmate::with_checkmate;
use roc_collections::{VecMap, VecSet};
use roc_debug_flags::dbg_do;
#[cfg(debug_assertions)]
@ -12,14 +13,16 @@ use roc_solve_problem::{
NotDerivableContext, NotDerivableDecode, NotDerivableEncode, NotDerivableEq, TypeError,
UnderivableReason, Unfulfilled,
};
use roc_solve_schema::UnificationMode;
use roc_types::num::NumericRange;
use roc_types::subs::{
instantiate_rigids, Content, FlatType, GetSubsSlice, Rank, RecordFields, Subs, SubsSlice,
TupleElems, Variable,
};
use roc_types::types::{AliasKind, Category, MemberImpl, PatternCategory, Polarity, Types};
use roc_unify::unify::{Env as UEnv, MustImplementConstraints};
use roc_unify::unify::MustImplementConstraints;
use roc_unify::unify::{MustImplementAbility, Obligated};
use roc_unify::Env as UEnv;
use crate::env::InferenceEnv;
use crate::{aliases::Aliases, to_var::type_to_var};
@ -1285,16 +1288,19 @@ impl DerivableVisitor for DeriveEq {
subs: &mut Subs,
content_var: Variable,
) -> Result<Descend, NotDerivable> {
use roc_unify::unify::{unify, Mode};
use roc_unify::unify::unify;
// Of the floating-point types,
// only Dec implements Eq.
let mut env = UEnv::new(subs);
// TODO(checkmate): pass checkmate through
let unified = unify(
&mut env,
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
content_var,
Variable::DECIMAL,
Mode::EQ,
UnificationMode::EQ,
Polarity::Pos,
);
match unified {
@ -1412,7 +1418,7 @@ pub fn resolve_ability_specialization<R: AbilityResolver>(
ability_member: Symbol,
specialization_var: Variable,
) -> Result<Resolved, ResolveError> {
use roc_unify::unify::{unify, Mode};
use roc_unify::unify::unify;
let (parent_ability, signature_var) = resolver
.member_parent_and_signature_var(ability_member, subs)
@ -1423,10 +1429,14 @@ pub fn resolve_ability_specialization<R: AbilityResolver>(
instantiate_rigids(subs, signature_var);
let (_vars, must_implement_ability, _lambda_sets_to_specialize, _meta) = unify(
&mut UEnv::new(subs),
// TODO(checkmate): pass checkmate through
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
specialization_var,
signature_var,
Mode::EQ,
UnificationMode::EQ,
Polarity::Pos,
)
.expect_success(

View file

@ -1,8 +1,9 @@
use bumpalo::Bump;
use roc_can::{constraint::Constraints, module::ExposedByModule};
use roc_checkmate::with_checkmate;
use roc_derive::SharedDerivedModule;
use roc_types::subs::{Content, Descriptor, Mark, OptVariable, Rank, Subs, Variable};
use roc_unify::unify::Env as UEnv;
use roc_unify::Env as UEnv;
use crate::{FunctionKind, Pools};
@ -18,6 +19,8 @@ pub struct SolveEnv<'a> {
pub derived_env: &'a DerivedEnv<'a>,
pub subs: &'a mut Subs,
pub pools: &'a mut Pools,
#[cfg(debug_assertions)]
pub checkmate: &'a mut Option<roc_checkmate::Collector>,
}
/// Environment necessary for inference.
@ -28,6 +31,8 @@ pub struct InferenceEnv<'a> {
pub derived_env: &'a DerivedEnv<'a>,
pub subs: &'a mut Subs,
pub pools: &'a mut Pools,
#[cfg(debug_assertions)]
pub checkmate: Option<roc_checkmate::Collector>,
}
impl<'a> SolveEnv<'a> {
@ -39,7 +44,10 @@ impl<'a> SolveEnv<'a> {
/// Retrieves an environment for unification.
pub fn uenv(&mut self) -> UEnv {
UEnv::new(self.subs)
with_checkmate!({
on => UEnv::new(self.subs, self.checkmate.as_mut()),
off => UEnv::new(self.subs),
})
}
}
@ -93,7 +101,10 @@ impl<'a> InferenceEnv<'a> {
/// Retrieves an environment for unification.
pub fn uenv(&mut self) -> UEnv {
UEnv::new(self.subs)
with_checkmate!({
on => UEnv::new(self.subs, self.checkmate.as_mut()),
off => UEnv::new(self.subs),
})
}
pub fn as_solve_env(&mut self) -> SolveEnv {
@ -102,6 +113,8 @@ impl<'a> InferenceEnv<'a> {
derived_env: self.derived_env,
subs: self.subs,
pools: self.pools,
#[cfg(debug_assertions)]
checkmate: &mut self.checkmate,
}
}
}

View file

@ -1,3 +1,4 @@
use crate::solve::RunSolveOutput;
use crate::FunctionKind;
use crate::{aliases::Aliases, solve};
use roc_can::abilities::{AbilitiesStore, ResolvedImpl};
@ -75,6 +76,10 @@ pub struct SolveConfig<'a> {
/// Needed during solving to resolve lambda sets from derived implementations that escape into
/// the user module.
pub derived_module: SharedDerivedModule,
#[cfg(debug_assertions)]
/// The checkmate collector for this module.
pub checkmate: Option<roc_checkmate::Collector>,
}
pub struct SolveOutput {
@ -82,6 +87,9 @@ pub struct SolveOutput {
pub scope: solve::Scope,
pub errors: Vec<TypeError>,
pub resolved_abilities_store: AbilitiesStore,
#[cfg(debug_assertions)]
pub checkmate: Option<roc_checkmate::Collector>,
}
pub fn run_solve(
@ -108,7 +116,12 @@ pub fn run_solve(
let mut problems = Vec::new();
// Run the solver to populate Subs.
let (solved_subs, solved_scope) = solve::run(
let RunSolveOutput {
solved,
scope,
#[cfg(debug_assertions)]
checkmate,
} = solve::run(
config,
&mut problems,
subs,
@ -117,10 +130,12 @@ pub fn run_solve(
);
SolveOutput {
subs: solved_subs,
scope: solved_scope,
subs: solved,
scope,
errors: problems,
resolved_abilities_store: abilities_store,
#[cfg(debug_assertions)]
checkmate,
}
}

View file

@ -24,13 +24,14 @@ use roc_module::symbol::Symbol;
use roc_problem::can::CycleEntry;
use roc_region::all::Loc;
use roc_solve_problem::TypeError;
use roc_solve_schema::UnificationMode;
use roc_types::subs::{
self, Content, FlatType, GetSubsSlice, Mark, OptVariable, Rank, Subs, TagExt, UlsOfVar,
Variable,
};
use roc_types::types::{Category, Polarity, Reason, RecordField, Type, TypeExtension, Types, Uls};
use roc_unify::unify::{
unify, unify_introduced_ability_specialization, Mode, Obligated, SpecializationLsetCollector,
unify, unify_introduced_ability_specialization, Obligated, SpecializationLsetCollector,
Unified::*,
};
@ -93,27 +94,32 @@ struct State {
mark: Mark,
}
pub struct RunSolveOutput {
pub solved: Solved<Subs>,
pub scope: Scope,
#[cfg(debug_assertions)]
pub checkmate: Option<roc_checkmate::Collector>,
}
pub fn run(
config: SolveConfig,
problems: &mut Vec<TypeError>,
mut subs: Subs,
subs: Subs,
aliases: &mut Aliases,
abilities_store: &mut AbilitiesStore,
) -> (Solved<Subs>, Scope) {
let env = run_in_place(config, problems, &mut subs, aliases, abilities_store);
(Solved(subs), env)
) -> RunSolveOutput {
run_help(config, problems, subs, aliases, abilities_store)
}
/// Modify an existing subs in-place instead
#[allow(clippy::too_many_arguments)] // TODO: put params in a context/env var
fn run_in_place(
fn run_help(
config: SolveConfig,
problems: &mut Vec<TypeError>,
subs: &mut Subs,
mut owned_subs: Subs,
aliases: &mut Aliases,
abilities_store: &mut AbilitiesStore,
) -> Scope {
) -> RunSolveOutput {
let subs = &mut owned_subs;
let SolveConfig {
home: _,
constraints,
@ -123,6 +129,7 @@ fn run_in_place(
exposed_by_module,
derived_module,
function_kind,
..
} = config;
let mut pools = Pools::default();
@ -149,6 +156,8 @@ fn run_in_place(
derived_env: &derived_env,
subs,
pools: &mut pools,
#[cfg(debug_assertions)]
checkmate: config.checkmate,
};
let pending_derives = PendingDerivesTable::new(
@ -179,7 +188,12 @@ fn run_in_place(
&mut awaiting_specializations,
);
state.scope
RunSolveOutput {
scope: state.scope,
#[cfg(debug_assertions)]
checkmate: env.checkmate,
solved: Solved(owned_subs),
}
}
#[derive(Debug)]
@ -489,7 +503,7 @@ fn solve(
&mut env.uenv(),
actual,
expected,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_VALUE,
) {
Success {
@ -600,7 +614,7 @@ fn solve(
&mut env.uenv(),
actual,
expected,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_VALUE,
) {
Success {
@ -699,8 +713,8 @@ fn solve(
);
let mode = match constraint {
PatternPresence(..) => Mode::PRESENT,
_ => Mode::EQ,
PatternPresence(..) => UnificationMode::PRESENT,
_ => UnificationMode::EQ,
};
match unify(
@ -919,7 +933,7 @@ fn solve(
&mut env.uenv(),
actual,
includes,
Mode::PRESENT,
UnificationMode::PRESENT,
Polarity::OF_PATTERN,
) {
Success {
@ -1053,7 +1067,7 @@ fn solve(
&mut env.uenv(),
branches_var,
real_var,
Mode::EQ,
UnificationMode::EQ,
cond_polarity,
);
@ -1103,7 +1117,7 @@ fn solve(
&mut env.uenv(),
real_var,
branches_var,
Mode::EQ,
UnificationMode::EQ,
cond_polarity,
),
Success { .. }
@ -1121,7 +1135,7 @@ fn solve(
&mut env.uenv(),
real_var,
branches_var,
Mode::EQ,
UnificationMode::EQ,
cond_polarity,
) {
Failure(vars, actual_type, expected_type, _bad_impls) => {
@ -1320,7 +1334,7 @@ fn solve(
&mut env.uenv(),
actual,
Variable::LIST_U8,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_VALUE,
) {
// List U8 always valid.
@ -1340,7 +1354,7 @@ fn solve(
&mut env.uenv(),
actual,
Variable::STR,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_VALUE,
) {
Success {
@ -1591,7 +1605,7 @@ fn check_ability_specialization(
&mut env.uenv(),
root_signature_var,
symbol_loc_var.value,
Mode::EQ,
UnificationMode::EQ,
);
let resolved_mark = match unified {

View file

@ -10,6 +10,7 @@ use roc_debug_flags::ROC_TRACE_COMPACTION;
use roc_derive_key::{DeriveError, DeriveKey};
use roc_error_macros::{internal_error, todo_abilities};
use roc_module::symbol::{ModuleId, Symbol};
use roc_solve_schema::UnificationMode;
use roc_types::{
subs::{
get_member_lambda_sets_at_region, Content, Descriptor, GetSubsSlice, LambdaSet, Mark,
@ -17,7 +18,7 @@ use roc_types::{
},
types::{AliasKind, MemberImpl, Polarity, Uls},
};
use roc_unify::unify::{unify, Mode, MustImplementConstraints};
use roc_unify::unify::{unify, MustImplementConstraints};
use crate::{
ability::builtin_module_with_unlisted_ability_impl,
@ -577,7 +578,7 @@ fn compact_lambda_set<P: Phase>(
&mut env.uenv(),
t_f1,
t_f2,
Mode::LAMBDA_SET_SPECIALIZATION,
UnificationMode::LAMBDA_SET_SPECIALIZATION,
Polarity::Pos,
)
.expect_success("ambient functions don't unify");

View file

@ -6,6 +6,7 @@ use roc_error_macros::internal_error;
use roc_module::{ident::TagName, symbol::Symbol};
use roc_region::all::Loc;
use roc_solve_problem::TypeError;
use roc_solve_schema::UnificationMode;
use roc_types::{
subs::{
self, AliasVariables, Content, FlatType, GetSubsSlice, LambdaSet, OptVariable, Rank,
@ -17,7 +18,7 @@ use roc_types::{
Category, ExtImplicitOpenness, Polarity, TypeTag, Types,
},
};
use roc_unify::unify::{unify, Mode, Unified};
use roc_unify::unify::{unify, Unified};
use crate::{
ability::{AbilityImplError, ObligationCache},
@ -875,7 +876,7 @@ pub(crate) fn type_to_var_help(
&mut env.uenv(),
var,
flex_ability,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_VALUE,
) {
Unified::Success {

View file

@ -0,0 +1,11 @@
[package]
name = "roc_solve_schema"
description = "Types used in the solver."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
bitflags.workspace = true

View file

@ -0,0 +1,3 @@
mod unify;
pub use unify::UnificationMode;

View file

@ -0,0 +1,50 @@
use bitflags::bitflags;
bitflags! {
pub struct UnificationMode : u8 {
/// Instructs the unifier to solve two types for equality.
///
/// For example, { n : Str }a ~ { n: Str, m : Str } will solve "a" to "{ m : Str }".
const EQ = 1 << 0;
/// Instructs the unifier to treat the right-hand-side of a constraint as
/// present in the left-hand-side, rather than strictly equal.
///
/// For example, t1 += [A Str] says we should "add" the tag "A Str" to the type of "t1".
const PRESENT = 1 << 1;
/// Like [`UnificationMode::EQ`], but also instructs the unifier that the ambient lambda set
/// specialization algorithm is running. This has implications for the unification of
/// unspecialized lambda sets; see [`unify_unspecialized_lambdas`].
const LAMBDA_SET_SPECIALIZATION = UnificationMode::EQ.bits | (1 << 2);
}
}
impl UnificationMode {
pub fn is_eq(&self) -> bool {
debug_assert!(!self.contains(UnificationMode::EQ | UnificationMode::PRESENT));
self.contains(UnificationMode::EQ)
}
pub fn is_present(&self) -> bool {
debug_assert!(!self.contains(UnificationMode::EQ | UnificationMode::PRESENT));
self.contains(UnificationMode::PRESENT)
}
pub fn is_lambda_set_specialization(&self) -> bool {
debug_assert!(!self.contains(UnificationMode::EQ | UnificationMode::PRESENT));
self.contains(UnificationMode::LAMBDA_SET_SPECIALIZATION)
}
pub fn as_eq(self) -> Self {
(self - UnificationMode::PRESENT) | UnificationMode::EQ
}
pub fn pretty_print(&self) -> &str {
if self.contains(UnificationMode::EQ) {
"~"
} else if self.contains(UnificationMode::PRESENT) {
"+="
} else {
unreachable!("Bad mode!")
}
}
}

View file

@ -433,6 +433,9 @@ fn check_derived_typechecks_and_golden(
pending_derives: Default::default(),
exposed_by_module: &exposed_for_module.exposed_by_module,
derived_module: Default::default(),
#[cfg(debug_assertions)]
checkmate: None,
};
let SolveOutput {

View file

@ -38,7 +38,7 @@ impl NumericRange {
width.signedness_and_width().1 >= at_least_width.signedness_and_width().1
}
pub(crate) fn width(&self) -> IntLitWidth {
pub fn min_width(&self) -> IntLitWidth {
use NumericRange::*;
match self {
IntAtLeastSigned(w)
@ -52,7 +52,7 @@ impl NumericRange {
/// `None` if there is no common lower bound.
pub fn intersection(&self, other: &Self) -> Option<Self> {
use NumericRange::*;
let (left, right) = (self.width(), other.width());
let (left, right) = (self.min_width(), other.min_width());
let (constructor, is_negative): (fn(IntLitWidth) -> NumericRange, _) = match (self, other) {
// Matching against a signed int, the intersection must also be a signed int
(IntAtLeastSigned(_), _) | (_, IntAtLeastSigned(_)) => (IntAtLeastSigned, true),
@ -154,11 +154,17 @@ impl NumericRange {
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum IntSignedness {
pub enum IntSignedness {
Unsigned,
Signed,
}
impl IntSignedness {
pub fn is_signed(&self) -> bool {
matches!(self, IntSignedness::Signed)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntLitWidth {
U8,
@ -184,7 +190,7 @@ pub enum IntLitWidth {
impl IntLitWidth {
/// Returns the `IntSignedness` and bit width of a variant.
fn signedness_and_width(&self) -> (IntSignedness, u32) {
pub fn signedness_and_width(&self) -> (IntSignedness, u32) {
use IntLitWidth::*;
use IntSignedness::*;
match self {
@ -207,7 +213,7 @@ impl IntLitWidth {
}
fn is_signed(&self) -> bool {
self.signedness_and_width().0 == IntSignedness::Signed
self.signedness_and_width().0.is_signed()
}
pub fn type_str(&self) -> &'static str {

View file

@ -1609,7 +1609,7 @@ mod debug_types {
use crate::num::IntLitWidth::*;
use crate::num::NumericRange::*;
let fmt_width = f.text(match range.width() {
let fmt_width = f.text(match range.min_width() {
U8 | I8 => "8",
U16 | I16 => "16",
U32 | I32 => "32",

View file

@ -438,7 +438,7 @@ fn write_source_with_answers<W: io::Write>(
Some(InferredQuery {
source_line_column,
..
}) if source_line_column.line == i as _
}) if source_line_column.line == i as u32
) {
let inferred = sorted_queries.pop().unwrap();

View file

@ -8,7 +8,6 @@ license.workspace = true
version.workspace = true
[dependencies]
bitflags.workspace = true
[dependencies.roc_collections]
path = "../collections"
@ -27,3 +26,9 @@ path = "../debug_flags"
[dependencies.roc_tracing]
path = "../../tracing"
[dependencies.roc_checkmate]
path = "../checkmate"
[dependencies.roc_solve_schema]
path = "../solve_schema"

View file

@ -0,0 +1,132 @@
#[cfg(debug_assertions)]
use roc_checkmate::debug_checkmate;
use roc_collections::VecSet;
use roc_types::subs::{Descriptor, Subs, Variable};
pub struct Env<'a> {
subs: &'a mut Subs,
#[cfg(debug_assertions)]
cm: Option<&'a mut roc_checkmate::Collector>,
seen_recursion: VecSet<(Variable, Variable)>,
fixed_variables: VecSet<Variable>,
}
impl std::ops::Deref for Env<'_> {
type Target = Subs;
fn deref(&self) -> &Self::Target {
self.subs
}
}
impl std::ops::DerefMut for Env<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.subs
}
}
impl<'a> Env<'a> {
#[cfg(debug_assertions)]
pub fn new(subs: &'a mut Subs, cm: Option<&'a mut roc_checkmate::Collector>) -> Self {
Self {
subs,
cm,
seen_recursion: Default::default(),
fixed_variables: Default::default(),
}
}
#[cfg(not(debug_assertions))]
pub fn new(subs: &'a mut Subs) -> Self {
Self {
subs,
seen_recursion: Default::default(),
fixed_variables: Default::default(),
}
}
pub(crate) fn add_recursion_pair(&mut self, var1: Variable, var2: Variable) {
let pair = (
self.subs.get_root_key_without_compacting(var1),
self.subs.get_root_key_without_compacting(var2),
);
let already_seen = self.seen_recursion.insert(pair);
debug_assert!(!already_seen);
}
pub(crate) fn remove_recursion_pair(&mut self, var1: Variable, var2: Variable) {
#[cfg(debug_assertions)]
let size_before = self.seen_recursion.len();
self.seen_recursion.retain(|(v1, v2)| {
let is_recursion_pair = self.subs.equivalent_without_compacting(*v1, var1)
&& self.subs.equivalent_without_compacting(*v2, var2);
!is_recursion_pair
});
#[cfg(debug_assertions)]
let size_after = self.seen_recursion.len();
#[cfg(debug_assertions)]
debug_assert!(size_after < size_before, "nothing was removed");
}
pub(crate) fn seen_recursion_pair(&self, var1: Variable, var2: Variable) -> bool {
let (var1, var2) = (
self.subs.get_root_key_without_compacting(var1),
self.subs.get_root_key_without_compacting(var2),
);
self.seen_recursion.contains(&(var1, var2))
}
pub(crate) fn was_fixed(&self, var: Variable) -> bool {
self.fixed_variables
.iter()
.any(|fixed_var| self.subs.equivalent_without_compacting(*fixed_var, var))
}
pub(crate) fn extend_fixed_variables(&mut self, vars: impl IntoIterator<Item = Variable>) {
self.fixed_variables.extend(vars);
}
#[cfg(debug_assertions)]
pub(crate) fn union(&mut self, left: Variable, right: Variable, desc: Descriptor) {
let left_root = self.subs.get_root_key_without_compacting(left);
let right_root = self.subs.get_root_key_without_compacting(right);
self.subs.union(left, right, desc);
debug_checkmate!(self.cm, cm => {
let new_root = self.subs.get_root_key_without_compacting(left);
cm.set_descriptor(self.subs, new_root, desc);
cm.unify(self.subs, left_root, new_root);
cm.unify(self.subs, right_root, new_root);
});
}
#[cfg(not(debug_assertions))]
pub(crate) fn union(&mut self, left: Variable, right: Variable, desc: Descriptor) {
self.subs.union(left, right, desc);
}
#[cfg(debug_assertions)]
pub(crate) fn debug_start_unification(
&mut self,
left: Variable,
right: Variable,
mode: roc_solve_schema::UnificationMode,
) {
debug_checkmate!(self.cm, cm => {
cm.start_unification(self.subs, left, right, mode);
});
}
#[cfg(debug_assertions)]
pub(crate) fn debug_end_unification(&mut self, left: Variable, right: Variable, success: bool) {
debug_checkmate!(self.cm, cm => {
cm.end_unification(self.subs, left, right, success);
});
}
}

View file

@ -4,5 +4,7 @@
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
mod env;
mod fix;
pub mod unify;
pub use env::Env;

File diff suppressed because it is too large Load diff

View file

@ -16,6 +16,7 @@ use roc_parse::parser::{SourceError, SyntaxError};
use roc_problem::can::Problem;
use roc_region::all::Loc;
use roc_solve::module::SolveConfig;
use roc_solve::solve::RunSolveOutput;
use roc_solve::{solve, Aliases, FunctionKind};
use roc_solve_problem::TypeError;
use roc_types::subs::{Content, Subs, VarStore, Variable};
@ -50,9 +51,12 @@ pub fn infer_expr(
exposed_by_module: &Default::default(),
derived_module,
function_kind: FunctionKind::LambdaSet,
#[cfg(debug_assertions)]
checkmate: None,
};
let (solved, _) = solve::run(config, problems, subs, aliases, abilities_store);
let RunSolveOutput { solved, .. } =
solve::run(config, problems, subs, aliases, abilities_store);
let content = *solved.inner().get_content_without_compacting(expr_var);

View file

@ -1,3 +1,3 @@
[files]
extend-exclude = ["crates/vendor/", "examples/static-site-gen/input/", "COPYRIGHT"]
extend-exclude = ["crates/vendor/", "examples/static-site-gen/input/", "COPYRIGHT", "crates/compiler/checkmate/www/package-lock.json"]