mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 11:52:19 +00:00
Merge pull request #4499 from roc-lang/debug-soa-types
Pretty-printer for debug representation of SoA types
This commit is contained in:
commit
2cb72eb622
4 changed files with 304 additions and 1 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4134,6 +4134,7 @@ dependencies = [
|
|||
"roc_region",
|
||||
"roc_serialize",
|
||||
"static_assertions",
|
||||
"ven_pretty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -15,3 +15,4 @@ roc_debug_flags = {path="../debug_flags"}
|
|||
roc_serialize = {path="../serialize"}
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
static_assertions = "1.1.0"
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
|
|
|
@ -36,7 +36,7 @@ impl NumericRange {
|
|||
width.signedness_and_width().1 >= at_least_width.signedness_and_width().1
|
||||
}
|
||||
|
||||
fn width(&self) -> IntLitWidth {
|
||||
pub(crate) fn width(&self) -> IntLitWidth {
|
||||
use NumericRange::*;
|
||||
match self {
|
||||
IntAtLeastSigned(w)
|
||||
|
|
|
@ -373,6 +373,7 @@ pub struct AliasShared {
|
|||
pub infer_ext_in_output_variables: Slice<TypeTag>,
|
||||
}
|
||||
|
||||
/// The tag (head constructor) of a canonical type stored in [Types].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TypeTag {
|
||||
EmptyRecord,
|
||||
|
@ -431,6 +432,8 @@ pub enum TypeTag {
|
|||
Record(RecordFields),
|
||||
}
|
||||
|
||||
/// Look-aside slice of types used in [Types], when the slice does not correspond to the direct
|
||||
/// type arguments of a [TypeTag].
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct AsideTypeSlice(Slice<TypeTag>);
|
||||
|
@ -449,6 +452,10 @@ impl AsideTypeSlice {
|
|||
}
|
||||
}
|
||||
|
||||
/// Memory-dense storage of canonicalized types, sitting between the user-facing type syntax and
|
||||
/// the [type solving representation][crate::subs::Content] of types.
|
||||
///
|
||||
/// See [TypeTag].
|
||||
pub struct Types {
|
||||
// main storage. Each type is represented by a tag, which is identified by its index.
|
||||
// `tags_slices` is a parallel array (so these two vectors always have the same size), that
|
||||
|
@ -505,6 +512,11 @@ impl Types {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn dbg(&self, tag: Index<TypeTag>) -> impl std::fmt::Debug + '_ {
|
||||
debug_types::DebugTag(self, tag)
|
||||
}
|
||||
|
||||
pub fn get_type_arguments(&self, tag: Index<TypeTag>) -> Slice<TypeTag> {
|
||||
self.tags_slices[tag.index()]
|
||||
}
|
||||
|
@ -1198,6 +1210,295 @@ impl Types {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
mod debug_types {
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{
|
||||
subs::UnionLabels,
|
||||
types::{AliasShared, RecordField, Uls},
|
||||
};
|
||||
|
||||
use super::{TypeTag, Types};
|
||||
use roc_collections::soa::{Index, Slice};
|
||||
use roc_module::ident::TagName;
|
||||
use ven_pretty::{Arena, DocAllocator, DocBuilder};
|
||||
|
||||
pub struct DebugTag<'a>(pub &'a Types, pub Index<TypeTag>);
|
||||
|
||||
impl<'a> std::fmt::Debug for DebugTag<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let DebugTag(types, idx) = self;
|
||||
let fmt = Arena::new();
|
||||
typ(types, &fmt, TPrec::Free, *idx).1.pretty(80).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, PartialOrd)]
|
||||
enum TPrec {
|
||||
Free,
|
||||
Arg,
|
||||
}
|
||||
|
||||
macro_rules! maybe_paren {
|
||||
($paren_if_above:expr, $my_prec:expr, $doc:expr) => {
|
||||
maybe_paren!($paren_if_above, $my_prec, || true, $doc)
|
||||
};
|
||||
($paren_if_above:expr, $my_prec:expr, $extra_cond:expr, $doc:expr) => {
|
||||
if $my_prec > $paren_if_above && $extra_cond() {
|
||||
$doc.parens().group()
|
||||
} else {
|
||||
$doc
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn typ<'a>(
|
||||
types: &'a Types,
|
||||
f: &'a Arena<'a>,
|
||||
p: TPrec,
|
||||
tag: Index<TypeTag>,
|
||||
) -> DocBuilder<'a, Arena<'a>> {
|
||||
use TPrec::*;
|
||||
let group = match types[tag] {
|
||||
TypeTag::EmptyRecord => f.text("{}"),
|
||||
TypeTag::EmptyTagUnion => f.text("[]"),
|
||||
TypeTag::Function(clos, ret) => {
|
||||
let args = types.get_type_arguments(tag);
|
||||
maybe_paren!(
|
||||
Free,
|
||||
p,
|
||||
f.intersperse(
|
||||
args.into_iter().map(|a| typ(types, f, Arg, a)),
|
||||
f.text(", "),
|
||||
)
|
||||
.append(f.text(" -"))
|
||||
.append(typ(types, f, Free, clos))
|
||||
.append(f.text("->"))
|
||||
.append(f.line())
|
||||
.append(typ(types, f, Arg, ret))
|
||||
.nest(2)
|
||||
)
|
||||
}
|
||||
TypeTag::ClosureTag {
|
||||
name,
|
||||
ambient_function,
|
||||
} => {
|
||||
let captures = types.get_type_arguments(tag);
|
||||
f.text("[")
|
||||
.append(
|
||||
f.intersperse(
|
||||
Some(f.text(format!("{name:?}")))
|
||||
.into_iter()
|
||||
.chain(captures.into_iter().map(|c| typ(types, f, Free, c))),
|
||||
f.text(" "),
|
||||
),
|
||||
)
|
||||
.append(f.text(format!(", ^{ambient_function:?}")))
|
||||
.append(f.text("]"))
|
||||
}
|
||||
TypeTag::FunctionOrTagUnion(_) => {
|
||||
let tag_name = types.get_tag_name(&tag);
|
||||
f.text(tag_name.0.as_str())
|
||||
}
|
||||
TypeTag::UnspecializedLambdaSet {
|
||||
unspecialized: Uls(var, sym, region),
|
||||
} => f
|
||||
.text("[")
|
||||
.append(f.text(format!("{var:?}:{sym:?}:{region}")))
|
||||
.append(f.text("]")),
|
||||
TypeTag::DelayedAlias { shared } => {
|
||||
maybe_paren!(Free, p, alias(types, f, tag, shared))
|
||||
}
|
||||
TypeTag::StructuralAlias { shared, actual }
|
||||
| TypeTag::OpaqueAlias { shared, actual }
|
||||
| TypeTag::HostExposedAlias {
|
||||
shared,
|
||||
actual_type: actual,
|
||||
actual_variable: _,
|
||||
} => maybe_paren!(
|
||||
Free,
|
||||
p,
|
||||
alias(types, f, tag, shared)
|
||||
.append(f.line())
|
||||
.append(f.text("==> "))
|
||||
.append(typ(types, f, Free, actual).align())
|
||||
.nest(2)
|
||||
),
|
||||
TypeTag::Apply {
|
||||
symbol,
|
||||
type_argument_regions: _,
|
||||
region: _,
|
||||
} => {
|
||||
let args = types.get_type_arguments(tag);
|
||||
let fmt_args = args.into_iter().map(|arg| typ(types, f, Arg, arg));
|
||||
maybe_paren!(
|
||||
Free,
|
||||
p,
|
||||
f.intersperse(
|
||||
Some(f.text(format!("{symbol:?}")))
|
||||
.into_iter()
|
||||
.chain(fmt_args),
|
||||
f.text(" "),
|
||||
)
|
||||
)
|
||||
}
|
||||
TypeTag::Variable(var) => f.text(format!("{var:?}")),
|
||||
TypeTag::RangedNumber(range) => ranged(f, range),
|
||||
TypeTag::Error => f.text("ERROR"),
|
||||
TypeTag::TagUnion(tags) => {
|
||||
tag_union(types, f, f.nil(), tags, types.get_type_arguments(tag))
|
||||
}
|
||||
TypeTag::RecursiveTagUnion(rec, tags) => tag_union(
|
||||
types,
|
||||
f,
|
||||
f.text(format!("<rec {rec:?}>")),
|
||||
tags,
|
||||
types.get_type_arguments(tag),
|
||||
),
|
||||
TypeTag::Record(fields) => {
|
||||
let (names, kind, tys) = types.record_fields_slices(fields);
|
||||
let fmt_fields = names
|
||||
.into_iter()
|
||||
.zip(kind.into_iter())
|
||||
.zip(tys.into_iter())
|
||||
.map(|((name, kind), ty)| {
|
||||
let (name, kind) = (&types[name], types[kind]);
|
||||
let fmt_kind = f.text(match kind {
|
||||
RecordField::Demanded(_) | RecordField::Required(_) => ":",
|
||||
RecordField::Optional(_) => "?",
|
||||
RecordField::RigidRequired(_) => "!:",
|
||||
RecordField::RigidOptional(_) => "!?",
|
||||
});
|
||||
f.text(name.as_str().to_owned())
|
||||
.append(fmt_kind)
|
||||
.append(f.text(" "))
|
||||
.append(typ(types, f, Free, ty))
|
||||
});
|
||||
f.text("{").append(
|
||||
f.intersperse(fmt_fields, f.reflow(", "))
|
||||
.append(
|
||||
f.text("}")
|
||||
.append(ext(types, f, types.get_type_arguments(tag))),
|
||||
)
|
||||
.group()
|
||||
.align(),
|
||||
)
|
||||
}
|
||||
};
|
||||
group.group()
|
||||
}
|
||||
|
||||
fn ext<'a>(
|
||||
types: &'a Types,
|
||||
f: &'a Arena<'a>,
|
||||
ext_slice: Slice<TypeTag>,
|
||||
) -> DocBuilder<'a, Arena<'a>> {
|
||||
f.intersperse(
|
||||
ext_slice.into_iter().map(|e| typ(types, f, TPrec::Free, e)),
|
||||
f.nil(),
|
||||
)
|
||||
.group()
|
||||
}
|
||||
|
||||
fn tag_union<'a>(
|
||||
types: &'a Types,
|
||||
f: &'a Arena<'a>,
|
||||
prefix: DocBuilder<'a, Arena<'a>>,
|
||||
tags: UnionLabels<TagName>,
|
||||
ext_slice: Slice<TypeTag>,
|
||||
) -> DocBuilder<'a, Arena<'a>> {
|
||||
let (tags, payload_slices) = types.union_tag_slices(tags);
|
||||
let fmt_tags =
|
||||
tags.into_iter()
|
||||
.zip(payload_slices.into_iter())
|
||||
.map(|(tag, payload_slice_index)| {
|
||||
let payload_slice = types[payload_slice_index];
|
||||
let fmt_payloads = payload_slice
|
||||
.into_iter()
|
||||
.map(|p| typ(types, f, TPrec::Arg, p));
|
||||
let iter = Some(f.text(types[tag].0.to_string()))
|
||||
.into_iter()
|
||||
.chain(fmt_payloads);
|
||||
f.intersperse(iter, f.text(" "))
|
||||
});
|
||||
|
||||
prefix.append(f.text("[")).append(
|
||||
f.intersperse(fmt_tags, f.reflow(", "))
|
||||
.append(f.text("]"))
|
||||
.append(ext(types, f, ext_slice))
|
||||
.group()
|
||||
.align(),
|
||||
)
|
||||
}
|
||||
|
||||
fn alias<'a>(
|
||||
types: &'a Types,
|
||||
f: &'a Arena<'a>,
|
||||
tag: Index<TypeTag>,
|
||||
shared: Index<AliasShared>,
|
||||
) -> DocBuilder<'a, Arena<'a>> {
|
||||
use TPrec::*;
|
||||
|
||||
let AliasShared {
|
||||
symbol,
|
||||
type_argument_abilities,
|
||||
type_argument_regions: _,
|
||||
lambda_set_variables: _,
|
||||
infer_ext_in_output_variables: _,
|
||||
} = types[shared];
|
||||
let args = types.get_type_arguments(tag);
|
||||
let fmt_args = args
|
||||
.into_iter()
|
||||
.zip(type_argument_abilities.into_iter())
|
||||
.map(|(arg, abilities)| {
|
||||
let abilities = &types[abilities];
|
||||
let arg = typ(types, f, Arg, arg);
|
||||
if abilities.is_empty() {
|
||||
return arg;
|
||||
}
|
||||
arg.append(f.text(" (+ "))
|
||||
.append(f.intersperse(
|
||||
abilities.sorted_iter().map(|ab| f.text(format!("{ab:?}"))),
|
||||
f.text(", "),
|
||||
))
|
||||
.append(f.text(")"))
|
||||
});
|
||||
f.intersperse(
|
||||
Some(f.text(format!("{symbol:?}")))
|
||||
.into_iter()
|
||||
.chain(fmt_args),
|
||||
f.text(" "),
|
||||
)
|
||||
}
|
||||
|
||||
fn ranged<'a>(f: &'a Arena<'a>, range: crate::num::NumericRange) -> DocBuilder<'a, Arena<'a>> {
|
||||
use crate::num::IntLitWidth::*;
|
||||
use crate::num::NumericRange::*;
|
||||
|
||||
let fmt_width = f.text(match range.width() {
|
||||
U8 | I8 => "8",
|
||||
U16 | I16 => "16",
|
||||
U32 | I32 => "32",
|
||||
U64 | I64 => "64",
|
||||
U128 | I128 => "128",
|
||||
Nat => "Nat",
|
||||
F32 => "F32",
|
||||
F64 => "F64",
|
||||
Dec => "Dec",
|
||||
});
|
||||
|
||||
let pre = match range {
|
||||
IntAtLeastSigned(_) => "Int(- >=",
|
||||
IntAtLeastEitherSign(_) => "Int(+/- >=",
|
||||
NumAtLeastSigned(_) => "Num(- >=",
|
||||
NumAtLeastEitherSign(_) => "Num(+/- >=",
|
||||
};
|
||||
|
||||
f.text(pre).append(fmt_width).append(f.text(")"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Polarity {
|
||||
pub const OF_VALUE: Polarity = Polarity::Pos;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue