mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
745 lines
25 KiB
Rust
745 lines
25 KiB
Rust
use crate::expr::{self, IntValue, WhenBranch};
|
|
use crate::pattern::DestructType;
|
|
use roc_collections::all::HumanIndex;
|
|
use roc_collections::VecMap;
|
|
use roc_error_macros::internal_error;
|
|
use roc_exhaustive::{
|
|
is_useful, Ctor, CtorName, Error, Guard, ListArity, Literal, Pattern, RenderAs, TagId, Union,
|
|
};
|
|
use roc_module::ident::{Lowercase, TagIdIntType, TagName};
|
|
use roc_module::symbol::Symbol;
|
|
use roc_region::all::{Loc, Region};
|
|
use roc_types::subs::{
|
|
Content, FlatType, GetSubsSlice, RedundantMark, SortedTagsIterator, Subs, SubsFmtContent,
|
|
Variable,
|
|
};
|
|
use roc_types::types::{gather_tags_unsorted_iter, AliasKind};
|
|
|
|
pub use roc_exhaustive::Context as ExhaustiveContext;
|
|
|
|
pub const GUARD_CTOR: &str = "#Guard";
|
|
pub const NONEXHAUSIVE_CTOR: &str = "#Open";
|
|
|
|
pub struct ExhaustiveSummary {
|
|
pub errors: Vec<Error>,
|
|
pub exhaustive: bool,
|
|
pub redundancies: Vec<RedundantMark>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct TypeError;
|
|
|
|
/// Exhaustiveness-checks [sketched rows][SketchedRows] against an expected type.
|
|
///
|
|
/// Returns an error if the sketch has a type error, in which case exhautiveness checking will not
|
|
/// have been performed.
|
|
pub fn check(
|
|
subs: &Subs,
|
|
real_var: Variable,
|
|
sketched_rows: SketchedRows,
|
|
context: ExhaustiveContext,
|
|
) -> Result<ExhaustiveSummary, TypeError> {
|
|
let overall_region = sketched_rows.overall_region;
|
|
let mut all_errors = Vec::with_capacity(1);
|
|
|
|
let NonRedundantSummary {
|
|
non_redundant_rows,
|
|
errors,
|
|
redundancies,
|
|
} = sketched_rows.reify_to_non_redundant(subs, real_var)?;
|
|
all_errors.extend(errors);
|
|
|
|
let exhaustive = match roc_exhaustive::check(overall_region, context, non_redundant_rows) {
|
|
Ok(()) => true,
|
|
Err(errors) => {
|
|
all_errors.extend(errors);
|
|
false
|
|
}
|
|
};
|
|
|
|
Ok(ExhaustiveSummary {
|
|
errors: all_errors,
|
|
exhaustive,
|
|
redundancies,
|
|
})
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
enum SketchedPattern {
|
|
Anything,
|
|
Literal(Literal),
|
|
/// A constructor whose expected union is not yet known.
|
|
/// We'll know the whole union when reifying the sketched pattern against an expected case type.
|
|
Ctor(TagName, Vec<SketchedPattern>),
|
|
KnownCtor(Union, TagId, Vec<SketchedPattern>),
|
|
List(ListArity, Vec<SketchedPattern>),
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
enum IndexCtor<'a> {
|
|
/// Index an opaque type. There should be one argument.
|
|
Opaque,
|
|
/// Index a record type. The arguments are the types of the record fields.
|
|
Record(&'a [Lowercase]),
|
|
/// Index a tuple type.
|
|
Tuple,
|
|
/// Index a guard constructor. The arguments are a faux guard pattern, and then the real
|
|
/// pattern being guarded. E.g. `A B if g` becomes Guard { [True, (A B)] }.
|
|
Guard,
|
|
/// Index a tag union with the given tag constructor.
|
|
Tag(&'a TagName),
|
|
/// Index a list type. The argument is the element type.
|
|
List,
|
|
}
|
|
|
|
impl<'a> IndexCtor<'a> {
|
|
fn of_union(un: &'a Union, tag_id: TagId) -> Self {
|
|
let Union {
|
|
alternatives,
|
|
render_as,
|
|
} = un;
|
|
|
|
match render_as {
|
|
RenderAs::Tag => {
|
|
let tag_name = alternatives
|
|
.iter()
|
|
.find(|ctor| ctor.tag_id == tag_id)
|
|
.map(|Ctor { name, .. }| match name {
|
|
CtorName::Tag(tag) => tag,
|
|
CtorName::Opaque(_) => {
|
|
internal_error!("tag union should never have opaque alternative")
|
|
}
|
|
})
|
|
.expect("indexable tag ID must be known to alternatives");
|
|
Self::Tag(tag_name)
|
|
}
|
|
RenderAs::Opaque => Self::Opaque,
|
|
RenderAs::Record(fields) => Self::Record(fields),
|
|
RenderAs::Tuple => Self::Tuple,
|
|
RenderAs::Guard => Self::Guard,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Index a variable as a certain constructor, to get the expected argument types of that constructor.
|
|
fn index_var(
|
|
subs: &Subs,
|
|
mut var: Variable,
|
|
ctor: IndexCtor,
|
|
render_as: &RenderAs,
|
|
) -> Result<Vec<Variable>, TypeError> {
|
|
if matches!(ctor, IndexCtor::Guard) {
|
|
// `A B if g` becomes Guard { [True, (A B)] }, so the arguments are a bool, and the type
|
|
// of the pattern.
|
|
return Ok(vec![Variable::BOOL, var]);
|
|
}
|
|
loop {
|
|
match subs.get_content_without_compacting(var) {
|
|
Content::FlexVar(_)
|
|
| Content::RigidVar(_)
|
|
| Content::FlexAbleVar(_, _)
|
|
| Content::RigidAbleVar(_, _)
|
|
| Content::LambdaSet(_)
|
|
| Content::ErasedLambda
|
|
| Content::RangedNumber(..) => return Err(TypeError),
|
|
Content::Error => return Err(TypeError),
|
|
Content::RecursionVar {
|
|
structure,
|
|
opt_name: _,
|
|
} => {
|
|
var = *structure;
|
|
}
|
|
Content::Structure(structure) => match structure {
|
|
FlatType::Func(_, _, _) => return Err(TypeError),
|
|
FlatType::Apply(Symbol::LIST_LIST, args) => {
|
|
match (subs.get_subs_slice(*args), ctor) {
|
|
([elem_var], IndexCtor::List) => {
|
|
return Ok(vec![*elem_var]);
|
|
}
|
|
_ => internal_error!("list types can only be indexed by list patterns"),
|
|
}
|
|
}
|
|
FlatType::Apply(..) => internal_error!("not an indexable constructor"),
|
|
FlatType::Record(fields, ext) => {
|
|
let fields_order = match render_as {
|
|
RenderAs::Record(fields) => fields,
|
|
_ => internal_error!(
|
|
"record constructors must always be rendered as records"
|
|
),
|
|
};
|
|
let iter = fields
|
|
.unsorted_iterator(subs, *ext)
|
|
.expect("should not have errors if performing exhautiveness checking");
|
|
|
|
let map: VecMap<_, _> = iter
|
|
.map(|(name, field)| (name, *field.as_inner()))
|
|
.collect();
|
|
|
|
let field_types = fields_order
|
|
.iter()
|
|
.map(|field| {
|
|
*map.get(&field)
|
|
.expect("field must be present during exhautiveness checking")
|
|
})
|
|
.collect();
|
|
|
|
return Ok(field_types);
|
|
}
|
|
FlatType::Tuple(elems, ext) => {
|
|
let elem_types = elems
|
|
.sorted_iterator(subs, *ext)
|
|
.map(|(_, elem)| elem)
|
|
.collect();
|
|
|
|
return Ok(elem_types);
|
|
}
|
|
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
|
|
let tag_ctor = match ctor {
|
|
IndexCtor::Tag(name) => name,
|
|
_ => {
|
|
internal_error!("constructor in a tag union must be tag")
|
|
}
|
|
};
|
|
let mut iter = tags.unsorted_iterator(subs, *ext);
|
|
let opt_vars = iter.find_map(|(tag, vars)| {
|
|
if tag == tag_ctor {
|
|
Some(vars.to_vec())
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
let vars = opt_vars.expect("constructor must be known in the indexable type if we are exhautiveness checking");
|
|
return Ok(vars);
|
|
}
|
|
FlatType::FunctionOrTagUnion(tags, _, _) => {
|
|
let tag_ctor = match ctor {
|
|
IndexCtor::Tag(name) => name,
|
|
_ => {
|
|
internal_error!("constructor in a tag union must be tag")
|
|
}
|
|
};
|
|
|
|
let tags = subs.get_subs_slice(*tags);
|
|
debug_assert!(tags.contains(tag_ctor), "constructor must be known in the indexable type if we are exhautiveness checking");
|
|
|
|
return Ok(vec![]);
|
|
}
|
|
FlatType::EmptyRecord => {
|
|
debug_assert!(matches!(ctor, IndexCtor::Record(..)));
|
|
// If there are optional record fields we don't unify them, but we need to
|
|
// cover them. Since optional fields correspond to "any" patterns, we can pass
|
|
// through arbitrary types.
|
|
let num_fields = match render_as {
|
|
RenderAs::Record(fields) => fields.len(),
|
|
_ => internal_error!(
|
|
"record constructors must always be rendered as records"
|
|
),
|
|
};
|
|
return Ok(std::iter::repeat(Variable::NULL).take(num_fields).collect());
|
|
}
|
|
FlatType::EmptyTagUnion => {
|
|
internal_error!("empty tag unions are not indexable")
|
|
}
|
|
},
|
|
Content::Alias(_, _, var, AliasKind::Opaque) => {
|
|
debug_assert!(matches!(ctor, IndexCtor::Opaque));
|
|
return Ok(vec![*var]);
|
|
}
|
|
Content::Alias(_, _, inner, AliasKind::Structural) => {
|
|
var = *inner;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SketchedPattern {
|
|
fn reify(self, subs: &Subs, real_var: Variable) -> Result<Pattern, TypeError> {
|
|
match self {
|
|
Self::Anything => Ok(Pattern::Anything),
|
|
Self::Literal(lit) => Ok(Pattern::Literal(lit)),
|
|
Self::KnownCtor(union, tag_id, patterns) => {
|
|
let index_ctor = IndexCtor::of_union(&union, tag_id);
|
|
let arg_vars = index_var(subs, real_var, index_ctor, &union.render_as)?;
|
|
|
|
debug_assert!(arg_vars.len() == patterns.len());
|
|
let args = (patterns.into_iter())
|
|
.zip(arg_vars)
|
|
.map(|(pat, var)| pat.reify(subs, var))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
Ok(Pattern::Ctor(union, tag_id, args))
|
|
}
|
|
Self::Ctor(tag_name, patterns) => {
|
|
let arg_vars =
|
|
index_var(subs, real_var, IndexCtor::Tag(&tag_name), &RenderAs::Tag)?;
|
|
let (union, tag_id) = convert_tag(subs, real_var, &tag_name);
|
|
|
|
debug_assert!(arg_vars.len() == patterns.len());
|
|
let args = (patterns.into_iter())
|
|
.zip(arg_vars)
|
|
.map(|(pat, var)| pat.reify(subs, var))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
Ok(Pattern::Ctor(union, tag_id, args))
|
|
}
|
|
Self::List(arity, patterns) => {
|
|
let elem_var = index_var(subs, real_var, IndexCtor::List, &RenderAs::Tag)?[0];
|
|
|
|
let patterns = patterns
|
|
.into_iter()
|
|
.map(|pat| pat.reify(subs, elem_var))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
Ok(Pattern::List(arity, patterns))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
struct SketchedRow {
|
|
patterns: Vec<SketchedPattern>,
|
|
region: Region,
|
|
guard: Guard,
|
|
redundant_mark: RedundantMark,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct SketchedRows {
|
|
rows: Vec<SketchedRow>,
|
|
overall_region: Region,
|
|
}
|
|
|
|
impl SketchedRows {
|
|
fn reify_to_non_redundant(
|
|
self,
|
|
subs: &Subs,
|
|
real_var: Variable,
|
|
) -> Result<NonRedundantSummary, TypeError> {
|
|
to_nonredundant_rows(subs, real_var, self)
|
|
}
|
|
}
|
|
|
|
fn sketch_pattern(pattern: &crate::pattern::Pattern) -> SketchedPattern {
|
|
use crate::pattern::Pattern::*;
|
|
use SketchedPattern as SP;
|
|
|
|
match pattern {
|
|
As(subpattern, _) => sketch_pattern(&subpattern.value),
|
|
&NumLiteral(_, _, IntValue::I128(n), _) | &IntLiteral(_, _, _, IntValue::I128(n), _) => {
|
|
SP::Literal(Literal::Int(n))
|
|
}
|
|
&NumLiteral(_, _, IntValue::U128(n), _) | &IntLiteral(_, _, _, IntValue::U128(n), _) => {
|
|
SP::Literal(Literal::U128(n))
|
|
}
|
|
&FloatLiteral(_, _, _, f, _) => SP::Literal(Literal::Float(f64::to_bits(f))),
|
|
StrLiteral(v) => SP::Literal(Literal::Str(v.clone())),
|
|
&SingleQuote(_, _, c, _) => SP::Literal(Literal::Byte(c as u8)),
|
|
RecordDestructure { destructs, .. } => {
|
|
let tag_id = TagId(0);
|
|
let mut patterns = std::vec::Vec::with_capacity(destructs.len());
|
|
let mut field_names = std::vec::Vec::with_capacity(destructs.len());
|
|
|
|
for Loc {
|
|
value: destruct,
|
|
region: _,
|
|
} in destructs
|
|
{
|
|
field_names.push(destruct.label.clone());
|
|
|
|
match &destruct.typ {
|
|
DestructType::Required | DestructType::Optional(..) => {
|
|
patterns.push(SP::Anything)
|
|
}
|
|
DestructType::Guard(_, guard) => patterns.push(sketch_pattern(&guard.value)),
|
|
}
|
|
}
|
|
|
|
let union = Union {
|
|
render_as: RenderAs::Record(field_names),
|
|
alternatives: vec![Ctor {
|
|
name: CtorName::Tag(TagName("#Record".into())),
|
|
tag_id,
|
|
arity: destructs.len(),
|
|
}],
|
|
};
|
|
|
|
SP::KnownCtor(union, tag_id, patterns)
|
|
}
|
|
|
|
TupleDestructure { destructs, .. } => {
|
|
let tag_id = TagId(0);
|
|
let mut patterns = std::vec::Vec::with_capacity(destructs.len());
|
|
|
|
for Loc {
|
|
value: destruct,
|
|
region: _,
|
|
} in destructs
|
|
{
|
|
patterns.push(sketch_pattern(&destruct.typ.1.value));
|
|
}
|
|
|
|
let union = Union {
|
|
render_as: RenderAs::Tuple,
|
|
alternatives: vec![Ctor {
|
|
name: CtorName::Tag(TagName("#Record".into())),
|
|
tag_id,
|
|
arity: destructs.len(),
|
|
}],
|
|
};
|
|
|
|
SP::KnownCtor(union, tag_id, patterns)
|
|
}
|
|
|
|
List {
|
|
patterns,
|
|
list_var: _,
|
|
elem_var: _,
|
|
} => {
|
|
let arity = patterns.arity();
|
|
|
|
let sketched_elem_patterns = patterns
|
|
.patterns
|
|
.iter()
|
|
.map(|p| sketch_pattern(&p.value))
|
|
.collect();
|
|
|
|
SP::List(arity, sketched_elem_patterns)
|
|
}
|
|
|
|
AppliedTag {
|
|
tag_name,
|
|
arguments,
|
|
..
|
|
} => {
|
|
let simplified_args: std::vec::Vec<_> = arguments
|
|
.iter()
|
|
.map(|(_, arg)| sketch_pattern(&arg.value))
|
|
.collect();
|
|
|
|
SP::Ctor(tag_name.clone(), simplified_args)
|
|
}
|
|
|
|
UnwrappedOpaque {
|
|
opaque, argument, ..
|
|
} => {
|
|
let (_, argument) = &(**argument);
|
|
|
|
let tag_id = TagId(0);
|
|
|
|
let union = Union {
|
|
render_as: RenderAs::Opaque,
|
|
alternatives: vec![Ctor {
|
|
name: CtorName::Opaque(*opaque),
|
|
tag_id,
|
|
arity: 1,
|
|
}],
|
|
};
|
|
|
|
SP::KnownCtor(union, tag_id, vec![sketch_pattern(&argument.value)])
|
|
}
|
|
|
|
// Treat this like a literal so we mark it as non-exhaustive
|
|
MalformedPattern(..) => SP::Literal(Literal::Byte(1)),
|
|
|
|
Underscore
|
|
| Identifier(_)
|
|
| AbilityMemberSpecialization { .. }
|
|
| Shadowed(..)
|
|
| OpaqueNotInScope(..)
|
|
| UnsupportedPattern(..) => SP::Anything,
|
|
}
|
|
}
|
|
|
|
pub fn sketch_when_branches(region: Region, patterns: &[expr::WhenBranch]) -> SketchedRows {
|
|
let mut rows: Vec<SketchedRow> = Vec::with_capacity(patterns.len());
|
|
|
|
// If any of the branches has a guard, e.g.
|
|
//
|
|
// when x is
|
|
// y if y < 10 -> "foo"
|
|
// _ -> "bar"
|
|
//
|
|
// then we treat it as a pattern match on the pattern and a boolean, wrapped in the #Guard
|
|
// constructor. We can use this special constructor name to generate better error messages.
|
|
// This transformation of the pattern match only works because we only report exhaustiveness
|
|
// errors: the Pattern created in this file is not used for code gen.
|
|
//
|
|
// when x is
|
|
// #Guard y True -> "foo"
|
|
// #Guard _ _ -> "bar"
|
|
let any_has_guard = patterns.iter().any(|branch| branch.guard.is_some());
|
|
|
|
use SketchedPattern as SP;
|
|
for WhenBranch {
|
|
patterns,
|
|
guard,
|
|
value: _,
|
|
redundant,
|
|
} in patterns
|
|
{
|
|
let guard = if guard.is_some() {
|
|
Guard::HasGuard
|
|
} else {
|
|
Guard::NoGuard
|
|
};
|
|
|
|
for loc_pat in patterns {
|
|
// Decompose each pattern in the branch into its own row.
|
|
|
|
let patterns = if any_has_guard {
|
|
let guard_pattern = match guard {
|
|
Guard::HasGuard => SP::Literal(Literal::Bit(true)),
|
|
Guard::NoGuard => SP::Anything,
|
|
};
|
|
|
|
let tag_id = TagId(0);
|
|
|
|
let union = Union {
|
|
render_as: RenderAs::Guard,
|
|
alternatives: vec![Ctor {
|
|
tag_id,
|
|
name: CtorName::Tag(TagName(GUARD_CTOR.into())),
|
|
arity: 2,
|
|
}],
|
|
};
|
|
|
|
vec![SP::KnownCtor(
|
|
union,
|
|
tag_id,
|
|
// NB: ordering the guard pattern first seems to be better at catching
|
|
// non-exhaustive constructors in the second argument; see the paper to see if
|
|
// there is a way to improve this in general.
|
|
vec![guard_pattern, sketch_pattern(&loc_pat.pattern.value)],
|
|
)]
|
|
} else {
|
|
// Simple case
|
|
vec![sketch_pattern(&loc_pat.pattern.value)]
|
|
};
|
|
|
|
let row = SketchedRow {
|
|
patterns,
|
|
region: loc_pat.pattern.region,
|
|
guard,
|
|
redundant_mark: *redundant,
|
|
};
|
|
rows.push(row);
|
|
}
|
|
}
|
|
|
|
SketchedRows {
|
|
rows,
|
|
overall_region: region,
|
|
}
|
|
}
|
|
|
|
pub fn sketch_pattern_to_rows(region: Region, pattern: &crate::pattern::Pattern) -> SketchedRows {
|
|
let row = SketchedRow {
|
|
patterns: vec![sketch_pattern(pattern)],
|
|
region,
|
|
// A single row cannot be redundant!
|
|
redundant_mark: RedundantMark::known_non_redundant(),
|
|
guard: Guard::NoGuard,
|
|
};
|
|
SketchedRows {
|
|
rows: vec![row],
|
|
overall_region: region,
|
|
}
|
|
}
|
|
|
|
/// REDUNDANT PATTERNS
|
|
|
|
struct NonRedundantSummary {
|
|
non_redundant_rows: Vec<Vec<Pattern>>,
|
|
redundancies: Vec<RedundantMark>,
|
|
errors: Vec<Error>,
|
|
}
|
|
|
|
/// INVARIANT: Produces a list of rows where (forall row. length row == 1)
|
|
fn to_nonredundant_rows(
|
|
subs: &Subs,
|
|
real_var: Variable,
|
|
rows: SketchedRows,
|
|
) -> Result<NonRedundantSummary, TypeError> {
|
|
let SketchedRows {
|
|
rows,
|
|
overall_region,
|
|
} = rows;
|
|
let mut checked_rows = Vec::with_capacity(rows.len());
|
|
|
|
let mut redundancies = vec![];
|
|
let mut errors = vec![];
|
|
|
|
for (
|
|
row_number,
|
|
SketchedRow {
|
|
patterns,
|
|
guard,
|
|
region,
|
|
redundant_mark,
|
|
},
|
|
) in rows.into_iter().enumerate()
|
|
{
|
|
let next_row: Vec<Pattern> = patterns
|
|
.into_iter()
|
|
.map(|pattern| pattern.reify(subs, real_var))
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
let redundant_err = if !is_inhabited_row(&next_row) {
|
|
Some(Error::Unmatchable {
|
|
overall_region,
|
|
branch_region: region,
|
|
index: HumanIndex::zero_based(row_number),
|
|
})
|
|
} else if !(matches!(guard, Guard::HasGuard)
|
|
|| is_useful(checked_rows.clone(), next_row.clone()))
|
|
{
|
|
Some(Error::Redundant {
|
|
overall_region,
|
|
branch_region: region,
|
|
index: HumanIndex::zero_based(row_number),
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
|
|
match redundant_err {
|
|
None => {
|
|
checked_rows.push(next_row);
|
|
}
|
|
Some(err) => {
|
|
redundancies.push(redundant_mark);
|
|
errors.push(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(NonRedundantSummary {
|
|
non_redundant_rows: checked_rows,
|
|
redundancies,
|
|
errors,
|
|
})
|
|
}
|
|
|
|
fn is_inhabited_row(patterns: &[Pattern]) -> bool {
|
|
patterns.iter().any(is_inhabited_pattern)
|
|
}
|
|
|
|
fn is_inhabited_pattern(pat: &Pattern) -> bool {
|
|
let mut stack = vec![pat];
|
|
while let Some(pat) = stack.pop() {
|
|
match pat {
|
|
Pattern::Anything => {}
|
|
Pattern::Literal(_) => {}
|
|
Pattern::Ctor(union, id, pats) => {
|
|
if !union.alternatives.iter().any(|alt| alt.tag_id == *id) {
|
|
// The tag ID was dropped from the union, which means that this tag ID is one
|
|
// that is not material to the union, and so is uninhabited!
|
|
return false;
|
|
}
|
|
stack.extend(pats);
|
|
}
|
|
Pattern::List(_, pats) => {
|
|
// List is uninhabited if any element is uninhabited.
|
|
stack.extend(pats);
|
|
}
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
fn convert_tag(subs: &Subs, whole_var: Variable, this_tag: &TagName) -> (Union, TagId) {
|
|
let content = subs.get_content_without_compacting(whole_var);
|
|
|
|
use {Content::*, FlatType::*};
|
|
|
|
let (sorted_tags, ext) = match dealias_tag(subs, content) {
|
|
Structure(TagUnion(tags, ext) | RecursiveTagUnion(_, tags, ext)) => {
|
|
let (sorted_tags, ext) = tags.sorted_iterator_and_ext(subs, *ext);
|
|
(sorted_tags, ext)
|
|
}
|
|
Structure(FunctionOrTagUnion(tags, _, ext)) => {
|
|
let (ext_tags, ext) = gather_tags_unsorted_iter(subs, Default::default(), *ext)
|
|
.unwrap_or_else(|_| {
|
|
internal_error!("Content is not a tag union: {:?}", subs.dbg(whole_var))
|
|
});
|
|
let mut all_tags: Vec<(TagName, &[Variable])> = Vec::with_capacity(tags.len());
|
|
for tag in subs.get_subs_slice(*tags) {
|
|
all_tags.push((tag.clone(), &[]));
|
|
}
|
|
for (tag, vars) in ext_tags {
|
|
debug_assert!(vars.is_empty());
|
|
all_tags.push((tag.clone(), &[]));
|
|
}
|
|
(Box::new(all_tags.into_iter()) as SortedTagsIterator, ext)
|
|
}
|
|
_ => internal_error!(
|
|
"Content is not a tag union: {:?}",
|
|
SubsFmtContent(content, subs)
|
|
),
|
|
};
|
|
|
|
let mut num_tags = sorted_tags.len();
|
|
|
|
// DEVIATION: model openness by attaching a #Open constructor, that can never
|
|
// be matched unless there's an `Anything` pattern.
|
|
let opt_openness_tag = match subs.get_content_without_compacting(ext.var()) {
|
|
FlexVar(_) | RigidVar(_) => {
|
|
let openness_tag = TagName(NONEXHAUSIVE_CTOR.into());
|
|
num_tags += 1;
|
|
Some((openness_tag, &[] as _))
|
|
}
|
|
Structure(EmptyTagUnion) => None,
|
|
// Anything else is erroneous and we ignore
|
|
_ => None,
|
|
};
|
|
|
|
// High tag ID if we're out-of-bounds.
|
|
let mut my_tag_id = TagId(num_tags as TagIdIntType);
|
|
|
|
let mut alternatives = Vec::with_capacity(num_tags);
|
|
let alternatives_iter = sorted_tags.into_iter().chain(opt_openness_tag);
|
|
|
|
let mut index = 0;
|
|
for (tag, args) in alternatives_iter {
|
|
let is_inhabited = args.iter().all(|v| subs.is_inhabited(*v));
|
|
if !is_inhabited {
|
|
// This constructor is not material; we don't need to match over it!
|
|
continue;
|
|
}
|
|
|
|
let tag_id = TagId(index as TagIdIntType);
|
|
index += 1;
|
|
|
|
if this_tag == &tag {
|
|
my_tag_id = tag_id;
|
|
}
|
|
alternatives.push(Ctor {
|
|
name: CtorName::Tag(tag),
|
|
tag_id,
|
|
arity: args.len(),
|
|
});
|
|
}
|
|
|
|
let union = Union {
|
|
alternatives,
|
|
render_as: RenderAs::Tag,
|
|
};
|
|
|
|
(union, my_tag_id)
|
|
}
|
|
|
|
pub fn dealias_tag<'a>(subs: &'a Subs, content: &'a Content) -> &'a Content {
|
|
use Content::*;
|
|
let mut result = content;
|
|
loop {
|
|
match result {
|
|
Alias(_, _, real_var, AliasKind::Structural)
|
|
| RecursionVar {
|
|
structure: real_var,
|
|
..
|
|
} => result = subs.get_content_without_compacting(*real_var),
|
|
_ => return result,
|
|
}
|
|
}
|
|
}
|