mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-15 21:23:57 +00:00
First pass of checkmate schema
This commit is contained in:
parent
93513cffae
commit
40223a697d
6 changed files with 515 additions and 0 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -3203,6 +3203,16 @@ dependencies = [
|
|||
"ven_pretty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_checkmate"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"roc_module",
|
||||
"roc_types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_cli"
|
||||
version = "0.0.1"
|
||||
|
|
|
|||
15
crates/compiler/checkmate/Cargo.toml
Normal file
15
crates/compiler/checkmate/Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[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_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
5
crates/compiler/checkmate/README.md
Normal file
5
crates/compiler/checkmate/README.md
Normal 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).
|
||||
301
crates/compiler/checkmate/src/convert.rs
Normal file
301
crates/compiler/checkmate/src/convert.rs
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use roc_module::{ident, symbol};
|
||||
use roc_types::{
|
||||
num,
|
||||
subs::{self, GetSubsSlice, Subs, SubsIndex, SubsSlice, UnionLabels},
|
||||
types,
|
||||
};
|
||||
|
||||
use crate::schema::{
|
||||
AliasKind, AliasTypeVariables, ClosureType, Content, NumericRange, NumericRangeKind,
|
||||
RecordField, RecordFieldKind, Symbol, TagUnionExtension, UnspecializedClosureType, Variable,
|
||||
};
|
||||
|
||||
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::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()
|
||||
}
|
||||
}
|
||||
7
crates/compiler/checkmate/src/lib.rs
Normal file
7
crates/compiler/checkmate/src/lib.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
mod convert;
|
||||
mod schema;
|
||||
|
||||
pub fn is_checkmate_enabled() -> bool {
|
||||
let flag = std::env::var("ROC_CHECKMATE");
|
||||
flag.as_deref() == Ok("1")
|
||||
}
|
||||
177
crates/compiler/checkmate/src/schema.rs
Normal file
177
crates/compiler/checkmate/src/schema.rs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub enum Constraint {}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Variable(pub u32);
|
||||
|
||||
macro_rules! impl_content {
|
||||
($($name:ident { $($arg:ident: $ty:ty,)* },)*) => {
|
||||
#[derive(Serialize)]
|
||||
pub enum Content {
|
||||
$(
|
||||
$name {
|
||||
$($arg: $ty),*
|
||||
},
|
||||
)*
|
||||
}
|
||||
|
||||
impl Content {
|
||||
$(
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) 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,
|
||||
},
|
||||
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)]
|
||||
pub struct ClosureType {
|
||||
pub function: Symbol,
|
||||
pub environment: Vec<Variable>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UnspecializedClosureType {
|
||||
pub specialization: Variable,
|
||||
pub ability_member: Symbol,
|
||||
pub lambda_set_region: u8,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub enum AliasKind {
|
||||
Structural,
|
||||
Opaque,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
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)]
|
||||
pub struct RecordField {
|
||||
pub kind: RecordFieldKind,
|
||||
pub field_type: Variable,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum RecordFieldKind {
|
||||
Demanded,
|
||||
Required { rigid: bool },
|
||||
Optional { rigid: bool },
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum TagUnionExtension {
|
||||
Openness(Variable),
|
||||
Any(Variable),
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct NumericRange {
|
||||
pub kind: NumericRangeKind,
|
||||
pub signed: bool,
|
||||
pub min_width: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub enum NumericRangeKind {
|
||||
Int,
|
||||
AnyNum,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Rank(pub u32);
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Descriptor {
|
||||
pub content: Content,
|
||||
pub rank: Rank,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Symbol(
|
||||
// TODO: should this be module ID + symbol?
|
||||
pub String,
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue