mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
Canonicalize deriving
This commit is contained in:
parent
09acc5458b
commit
9712cfe342
6 changed files with 397 additions and 171 deletions
|
@ -100,12 +100,19 @@ impl PendingValueDef<'_> {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
enum PendingTypeDef<'a> {
|
||||
/// A structural or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively.
|
||||
/// A structural type alias, e.g. `Ints : List Int`
|
||||
Alias {
|
||||
name: Loc<Symbol>,
|
||||
vars: Vec<Loc<Lowercase>>,
|
||||
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
||||
kind: AliasKind,
|
||||
},
|
||||
|
||||
/// An opaque type alias, e.g. `Age := U32`.
|
||||
Opaque {
|
||||
name: Loc<Symbol>,
|
||||
vars: Vec<Loc<Lowercase>>,
|
||||
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
||||
derived: Option<&'a Loc<ast::Derived<'a>>>,
|
||||
},
|
||||
|
||||
Ability {
|
||||
|
@ -145,6 +152,17 @@ impl PendingTypeDef<'_> {
|
|||
|
||||
Some((name.value, region))
|
||||
}
|
||||
PendingTypeDef::Opaque {
|
||||
name,
|
||||
vars: _,
|
||||
ann,
|
||||
derived,
|
||||
} => {
|
||||
let end = derived.map(|d| d.region).unwrap_or(ann.region);
|
||||
let region = Region::span_across(&name.region, &end);
|
||||
|
||||
Some((name.value, region))
|
||||
}
|
||||
PendingTypeDef::Ability { name, .. } => Some((name.value, name.region)),
|
||||
PendingTypeDef::InvalidAlias { symbol, region, .. } => Some((*symbol, *region)),
|
||||
PendingTypeDef::ShadowedAlias { .. } => None,
|
||||
|
@ -206,6 +224,179 @@ fn sort_type_defs_before_introduction(
|
|||
.collect()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn canonicalize_alias<'a>(
|
||||
env: &mut Env<'a>,
|
||||
output: &mut Output,
|
||||
var_store: &mut VarStore,
|
||||
scope: &mut Scope,
|
||||
abilities_in_scope: &[Symbol],
|
||||
|
||||
name: Loc<Symbol>,
|
||||
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
||||
vars: &[Loc<Lowercase>],
|
||||
kind: AliasKind,
|
||||
) -> Result<Alias, ()> {
|
||||
let symbol = name.value;
|
||||
let can_ann = canonicalize_annotation(
|
||||
env,
|
||||
scope,
|
||||
&ann.value,
|
||||
ann.region,
|
||||
var_store,
|
||||
abilities_in_scope,
|
||||
);
|
||||
|
||||
// Record all the annotation's references in output.references.lookups
|
||||
for symbol in can_ann.references {
|
||||
output.references.insert_type_lookup(symbol);
|
||||
}
|
||||
|
||||
let mut can_vars: Vec<Loc<AliasVar>> = Vec::with_capacity(vars.len());
|
||||
let mut is_phantom = false;
|
||||
|
||||
let IntroducedVariables {
|
||||
named,
|
||||
able,
|
||||
wildcards,
|
||||
inferred,
|
||||
..
|
||||
} = can_ann.introduced_variables;
|
||||
|
||||
let mut named: Vec<_> = (named.into_iter().map(OwnedNamedOrAble::Named))
|
||||
.chain(able.into_iter().map(OwnedNamedOrAble::Able))
|
||||
.collect();
|
||||
for loc_lowercase in vars.iter() {
|
||||
let opt_index = named
|
||||
.iter()
|
||||
.position(|nv| nv.ref_name() == &loc_lowercase.value);
|
||||
match opt_index {
|
||||
Some(index) => {
|
||||
// This is a valid lowercase rigid var for the type def.
|
||||
let named_variable = named.swap_remove(index);
|
||||
let var = named_variable.variable();
|
||||
let opt_bound_ability = named_variable.opt_ability();
|
||||
let name = named_variable.name();
|
||||
|
||||
can_vars.push(Loc {
|
||||
value: AliasVar {
|
||||
name,
|
||||
var,
|
||||
opt_bound_ability,
|
||||
},
|
||||
region: loc_lowercase.region,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
is_phantom = true;
|
||||
|
||||
env.problems.push(Problem::PhantomTypeArgument {
|
||||
typ: symbol,
|
||||
variable_region: loc_lowercase.region,
|
||||
variable_name: loc_lowercase.value.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if is_phantom {
|
||||
// Bail out
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let num_unbound = named.len() + wildcards.len() + inferred.len();
|
||||
if num_unbound > 0 {
|
||||
let one_occurrence = named
|
||||
.iter()
|
||||
.map(|nv| Loc::at(nv.first_seen(), nv.variable()))
|
||||
.chain(wildcards)
|
||||
.chain(inferred)
|
||||
.next()
|
||||
.unwrap()
|
||||
.region;
|
||||
|
||||
env.problems.push(Problem::UnboundTypeVariable {
|
||||
typ: symbol,
|
||||
num_unbound,
|
||||
one_occurrence,
|
||||
kind,
|
||||
});
|
||||
|
||||
// Bail out
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(create_alias(
|
||||
symbol,
|
||||
name.region,
|
||||
can_vars.clone(),
|
||||
can_ann.typ,
|
||||
kind,
|
||||
))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn canonicalize_opaque<'a>(
|
||||
env: &mut Env<'a>,
|
||||
output: &mut Output,
|
||||
var_store: &mut VarStore,
|
||||
scope: &mut Scope,
|
||||
abilities_in_scope: &[Symbol],
|
||||
|
||||
name: Loc<Symbol>,
|
||||
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
||||
vars: &[Loc<Lowercase>],
|
||||
derives: Option<&'a Loc<ast::Derived<'a>>>,
|
||||
) -> Result<(Alias, Vec<(Symbol, Region)>), ()> {
|
||||
let alias = canonicalize_alias(
|
||||
env,
|
||||
output,
|
||||
var_store,
|
||||
scope,
|
||||
abilities_in_scope,
|
||||
name,
|
||||
ann,
|
||||
vars,
|
||||
AliasKind::Opaque,
|
||||
)?;
|
||||
|
||||
if let Some(derives) = derives {
|
||||
let derives = derives.value.collection();
|
||||
|
||||
let mut can_derives = vec![];
|
||||
|
||||
for derived in derives.items {
|
||||
let region = derived.region;
|
||||
let can_derived = canonicalize_annotation(
|
||||
env,
|
||||
scope,
|
||||
&derived.value,
|
||||
region,
|
||||
var_store,
|
||||
abilities_in_scope,
|
||||
);
|
||||
match can_derived.typ {
|
||||
Type::Apply(ability, args, _)
|
||||
if ability.is_builtin_ability() && args.is_empty() =>
|
||||
{
|
||||
can_derives.push((ability, region));
|
||||
}
|
||||
_ => {
|
||||
// Register the problem but keep going, we may still be able to compile the
|
||||
// program even if a derive is missing.
|
||||
env.problem(Problem::IllegalDerive(region));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((alias, can_derives))
|
||||
} else {
|
||||
Ok((alias, vec![]))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn canonicalize_defs<'a>(
|
||||
env: &mut Env<'a>,
|
||||
|
@ -250,11 +441,16 @@ pub(crate) fn canonicalize_defs<'a>(
|
|||
}
|
||||
|
||||
enum TypeDef<'a> {
|
||||
AliasLike(
|
||||
Alias(
|
||||
Loc<Symbol>,
|
||||
Vec<Loc<Lowercase>>,
|
||||
&'a Loc<ast::TypeAnnotation<'a>>,
|
||||
AliasKind,
|
||||
),
|
||||
Opaque(
|
||||
Loc<Symbol>,
|
||||
Vec<Loc<Lowercase>>,
|
||||
&'a Loc<ast::TypeAnnotation<'a>>,
|
||||
Option<&'a Loc<ast::Derived<'a>>>,
|
||||
),
|
||||
Ability(Loc<Symbol>, &'a [AbilityMember<'a>]),
|
||||
}
|
||||
|
@ -273,17 +469,27 @@ pub(crate) fn canonicalize_defs<'a>(
|
|||
}
|
||||
|
||||
match pending_def {
|
||||
PendingTypeDef::Alias {
|
||||
name,
|
||||
vars,
|
||||
ann,
|
||||
kind,
|
||||
} => {
|
||||
PendingTypeDef::Alias { name, vars, ann } => {
|
||||
let referenced_symbols = find_type_def_symbols(scope, &ann.value);
|
||||
|
||||
referenced_type_symbols.insert(name.value, referenced_symbols);
|
||||
|
||||
type_defs.insert(name.value, TypeDef::AliasLike(name, vars, ann, kind));
|
||||
type_defs.insert(name.value, TypeDef::Alias(name, vars, ann));
|
||||
}
|
||||
PendingTypeDef::Opaque {
|
||||
name,
|
||||
vars,
|
||||
ann,
|
||||
derived,
|
||||
} => {
|
||||
let referenced_symbols = find_type_def_symbols(scope, &ann.value);
|
||||
|
||||
referenced_type_symbols.insert(name.value, referenced_symbols);
|
||||
// Don't need to insert references for derived types, because these can only contain
|
||||
// builtin abilities, and hence do not affect the type def sorting. We'll insert
|
||||
// references of usages when canonicalizing the derives.
|
||||
|
||||
type_defs.insert(name.value, TypeDef::Opaque(name, vars, ann, derived));
|
||||
}
|
||||
PendingTypeDef::Ability { name, members } => {
|
||||
let mut referenced_symbols = Vec::with_capacity(2);
|
||||
|
@ -313,105 +519,40 @@ pub(crate) fn canonicalize_defs<'a>(
|
|||
|
||||
for type_name in sorted {
|
||||
match type_defs.remove(&type_name).unwrap() {
|
||||
TypeDef::AliasLike(name, vars, ann, kind) => {
|
||||
let symbol = name.value;
|
||||
let can_ann = canonicalize_annotation(
|
||||
TypeDef::Alias(name, vars, ann) => {
|
||||
let alias = canonicalize_alias(
|
||||
env,
|
||||
scope,
|
||||
&ann.value,
|
||||
ann.region,
|
||||
&mut output,
|
||||
var_store,
|
||||
scope,
|
||||
&pending_abilities_in_scope,
|
||||
name,
|
||||
ann,
|
||||
&vars,
|
||||
AliasKind::Structural,
|
||||
);
|
||||
|
||||
// Record all the annotation's references in output.references.lookups
|
||||
for symbol in can_ann.references {
|
||||
output.references.insert_type_lookup(symbol);
|
||||
if let Ok(alias) = alias {
|
||||
aliases.insert(name.value, alias);
|
||||
}
|
||||
}
|
||||
|
||||
let mut can_vars: Vec<Loc<AliasVar>> = Vec::with_capacity(vars.len());
|
||||
let mut is_phantom = false;
|
||||
|
||||
let IntroducedVariables {
|
||||
named,
|
||||
able,
|
||||
wildcards,
|
||||
inferred,
|
||||
..
|
||||
} = can_ann.introduced_variables;
|
||||
|
||||
let mut named: Vec<_> = (named.into_iter().map(OwnedNamedOrAble::Named))
|
||||
.chain(able.into_iter().map(OwnedNamedOrAble::Able))
|
||||
.collect();
|
||||
for loc_lowercase in vars.iter() {
|
||||
let opt_index = named
|
||||
.iter()
|
||||
.position(|nv| nv.ref_name() == &loc_lowercase.value);
|
||||
match opt_index {
|
||||
Some(index) => {
|
||||
// This is a valid lowercase rigid var for the type def.
|
||||
let named_variable = named.swap_remove(index);
|
||||
let var = named_variable.variable();
|
||||
let opt_bound_ability = named_variable.opt_ability();
|
||||
let name = named_variable.name();
|
||||
|
||||
can_vars.push(Loc {
|
||||
value: AliasVar {
|
||||
name,
|
||||
var,
|
||||
opt_bound_ability,
|
||||
},
|
||||
region: loc_lowercase.region,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
is_phantom = true;
|
||||
|
||||
env.problems.push(Problem::PhantomTypeArgument {
|
||||
typ: symbol,
|
||||
variable_region: loc_lowercase.region,
|
||||
variable_name: loc_lowercase.value.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if is_phantom {
|
||||
// Bail out
|
||||
continue;
|
||||
}
|
||||
|
||||
let num_unbound = named.len() + wildcards.len() + inferred.len();
|
||||
if num_unbound > 0 {
|
||||
let one_occurrence = named
|
||||
.iter()
|
||||
.map(|nv| Loc::at(nv.first_seen(), nv.variable()))
|
||||
.chain(wildcards)
|
||||
.chain(inferred)
|
||||
.next()
|
||||
.unwrap()
|
||||
.region;
|
||||
|
||||
env.problems.push(Problem::UnboundTypeVariable {
|
||||
typ: symbol,
|
||||
num_unbound,
|
||||
one_occurrence,
|
||||
kind,
|
||||
});
|
||||
|
||||
// Bail out
|
||||
continue;
|
||||
}
|
||||
|
||||
let alias = create_alias(
|
||||
symbol,
|
||||
name.region,
|
||||
can_vars.clone(),
|
||||
can_ann.typ.clone(),
|
||||
kind,
|
||||
TypeDef::Opaque(name, vars, ann, derived) => {
|
||||
let alias_and_derives = canonicalize_opaque(
|
||||
env,
|
||||
&mut output,
|
||||
var_store,
|
||||
scope,
|
||||
&pending_abilities_in_scope,
|
||||
name,
|
||||
ann,
|
||||
&vars,
|
||||
derived,
|
||||
);
|
||||
|
||||
aliases.insert(symbol, alias);
|
||||
if let Ok((alias, _derives_to_check)) = alias_and_derives {
|
||||
aliases.insert(name.value, alias);
|
||||
}
|
||||
}
|
||||
|
||||
TypeDef::Ability(name, members) => {
|
||||
|
@ -1327,6 +1468,86 @@ fn decl_to_let(decl: Declaration, loc_ret: Loc<Expr>) -> Expr {
|
|||
}
|
||||
}
|
||||
|
||||
fn to_pending_alias_or_opaque<'a>(
|
||||
env: &mut Env<'a>,
|
||||
scope: &mut Scope,
|
||||
name: &'a Loc<&'a str>,
|
||||
vars: &'a [Loc<ast::Pattern<'a>>],
|
||||
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
||||
opt_derived: Option<&'a Loc<ast::Derived<'a>>>,
|
||||
kind: AliasKind,
|
||||
) -> PendingTypeDef<'a> {
|
||||
let shadow_kind = match kind {
|
||||
AliasKind::Structural => ShadowKind::Alias,
|
||||
AliasKind::Opaque => ShadowKind::Opaque,
|
||||
};
|
||||
|
||||
let region = Region::span_across(&name.region, &ann.region);
|
||||
|
||||
match scope.introduce_without_shadow_symbol(&Ident::from(name.value), region) {
|
||||
Ok(symbol) => {
|
||||
let mut can_rigids: Vec<Loc<Lowercase>> = Vec::with_capacity(vars.len());
|
||||
|
||||
for loc_var in vars.iter() {
|
||||
match loc_var.value {
|
||||
ast::Pattern::Identifier(name)
|
||||
if name.chars().next().unwrap().is_lowercase() =>
|
||||
{
|
||||
let lowercase = Lowercase::from(name);
|
||||
can_rigids.push(Loc {
|
||||
value: lowercase,
|
||||
region: loc_var.region,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
// any other pattern in this position is a syntax error.
|
||||
let problem = Problem::InvalidAliasRigid {
|
||||
alias_name: symbol,
|
||||
region: loc_var.region,
|
||||
};
|
||||
env.problems.push(problem);
|
||||
|
||||
return PendingTypeDef::InvalidAlias {
|
||||
kind,
|
||||
symbol,
|
||||
region,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let name = Loc {
|
||||
region: name.region,
|
||||
value: symbol,
|
||||
};
|
||||
|
||||
match kind {
|
||||
AliasKind::Structural => PendingTypeDef::Alias {
|
||||
name,
|
||||
vars: can_rigids,
|
||||
ann,
|
||||
},
|
||||
AliasKind::Opaque => PendingTypeDef::Opaque {
|
||||
name,
|
||||
vars: can_rigids,
|
||||
ann,
|
||||
derived: opt_derived,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Err((original_region, loc_shadowed_symbol)) => {
|
||||
env.problem(Problem::Shadowing {
|
||||
original_region,
|
||||
shadow: loc_shadowed_symbol,
|
||||
kind: shadow_kind,
|
||||
});
|
||||
|
||||
PendingTypeDef::ShadowedAlias
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_pending_type_def<'a>(
|
||||
env: &mut Env<'a>,
|
||||
def: &'a ast::TypeDef<'a>,
|
||||
|
@ -1339,76 +1560,20 @@ fn to_pending_type_def<'a>(
|
|||
Alias {
|
||||
header: TypeHeader { name, vars },
|
||||
ann,
|
||||
}
|
||||
| Opaque {
|
||||
} => to_pending_alias_or_opaque(env, scope, name, vars, ann, None, AliasKind::Structural),
|
||||
Opaque {
|
||||
header: TypeHeader { name, vars },
|
||||
typ: ann,
|
||||
derived: _,
|
||||
} => {
|
||||
let (kind, shadow_kind) = if matches!(def, Alias { .. }) {
|
||||
(AliasKind::Structural, ShadowKind::Alias)
|
||||
} else {
|
||||
(AliasKind::Opaque, ShadowKind::Opaque)
|
||||
};
|
||||
|
||||
let region = Region::span_across(&name.region, &ann.region);
|
||||
|
||||
match scope.introduce_without_shadow_symbol(&Ident::from(name.value), region) {
|
||||
Ok(symbol) => {
|
||||
let mut can_rigids: Vec<Loc<Lowercase>> = Vec::with_capacity(vars.len());
|
||||
|
||||
for loc_var in vars.iter() {
|
||||
match loc_var.value {
|
||||
ast::Pattern::Identifier(name)
|
||||
if name.chars().next().unwrap().is_lowercase() =>
|
||||
{
|
||||
let lowercase = Lowercase::from(name);
|
||||
can_rigids.push(Loc {
|
||||
value: lowercase,
|
||||
region: loc_var.region,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
// any other pattern in this position is a syntax error.
|
||||
let problem = Problem::InvalidAliasRigid {
|
||||
alias_name: symbol,
|
||||
region: loc_var.region,
|
||||
};
|
||||
env.problems.push(problem);
|
||||
|
||||
return PendingTypeDef::InvalidAlias {
|
||||
kind,
|
||||
symbol,
|
||||
region,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let name = Loc {
|
||||
region: name.region,
|
||||
value: symbol,
|
||||
};
|
||||
|
||||
PendingTypeDef::Alias {
|
||||
name,
|
||||
vars: can_rigids,
|
||||
ann,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
Err((original_region, loc_shadowed_symbol)) => {
|
||||
env.problem(Problem::Shadowing {
|
||||
original_region,
|
||||
shadow: loc_shadowed_symbol,
|
||||
kind: shadow_kind,
|
||||
});
|
||||
|
||||
PendingTypeDef::ShadowedAlias
|
||||
}
|
||||
}
|
||||
}
|
||||
derived,
|
||||
} => to_pending_alias_or_opaque(
|
||||
env,
|
||||
scope,
|
||||
name,
|
||||
vars,
|
||||
ann,
|
||||
derived.as_ref(),
|
||||
AliasKind::Opaque,
|
||||
),
|
||||
|
||||
Ability {
|
||||
header, members, ..
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue