First pass of checkmate schema

This commit is contained in:
Ayaz Hafiz 2023-07-15 23:26:19 -05:00
parent 93513cffae
commit 40223a697d
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
6 changed files with 515 additions and 0 deletions

10
Cargo.lock generated
View file

@ -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"

View 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

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,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()
}
}

View 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")
}

View 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,
);