Canonicalize deriving

This commit is contained in:
Ayaz Hafiz 2022-05-19 18:15:05 -04:00
parent 09acc5458b
commit 9712cfe342
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
6 changed files with 397 additions and 171 deletions

View file

@ -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, ..