Merge pull request #4499 from roc-lang/debug-soa-types

Pretty-printer for debug representation of SoA types
This commit is contained in:
Ayaz 2022-11-10 02:01:18 -06:00 committed by GitHub
commit 2cb72eb622
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 304 additions and 1 deletions

1
Cargo.lock generated
View file

@ -4134,6 +4134,7 @@ dependencies = [
"roc_region",
"roc_serialize",
"static_assertions",
"ven_pretty",
]
[[package]]

View file

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

View file

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

View file

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