clean up annotation canonicalization

This commit is contained in:
Folkert 2020-07-12 00:36:11 +02:00
parent 147a482f9d
commit 362ff74b82

View file

@ -1,6 +1,6 @@
use crate::env::Env;
use crate::scope::Scope;
use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_collections::all::{MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_parse::ast::{AssignedField, Tag, TypeAnnotation};
@ -294,34 +294,17 @@ fn can_annotation_help(
},
Record { fields, ext } => {
let mut field_types = SendMap::default();
let mut seen = MutMap::default();
for field in fields.iter() {
let opt_field_name = can_assigned_field(
let field_types = can_assigned_fields(
env,
&field.value,
fields,
region,
scope,
var_store,
introduced_variables,
local_aliases,
&mut field_types,
references,
);
if let Some(added) = opt_field_name {
if let Some(replaced_region) = seen.insert(added.clone(), field.region) {
env.problem(roc_problem::can::Problem::DuplicateRecordFieldType {
field_name: added.clone(),
field_region: field.region,
record_region: region,
replaced_region,
});
}
}
}
let ext_type = match ext {
Some(loc_ann) => can_annotation_help(
env,
@ -339,39 +322,22 @@ fn can_annotation_help(
Type::Record(field_types, Box::new(ext_type))
}
TagUnion { tags, ext } => {
let mut tag_types = Vec::with_capacity(tags.len());
let mut seen = MutMap::default();
for tag in tags.iter() {
let opt_tag_name = can_tag(
let tag_types = can_tags(
env,
&tag.value,
tags,
region,
scope,
var_store,
introduced_variables,
local_aliases,
&mut tag_types,
references,
);
if let Some(added) = opt_tag_name {
if let Some(replaced_region) = seen.insert(added.clone(), tag.region) {
env.problem(roc_problem::can::Problem::DuplicateTag {
tag_name: added.clone(),
tag_region: tag.region,
tag_union_region: region,
replaced_region,
});
}
}
}
let ext_type = match ext {
Some(loc_ann) => can_annotation_help(
env,
&loc_ann.value,
region,
loc_ann.region,
scope,
var_store,
introduced_variables,
@ -405,19 +371,32 @@ fn can_annotation_help(
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
fn can_assigned_field<'a>(
fn can_assigned_fields<'a>(
env: &mut Env,
field: &AssignedField<'a, TypeAnnotation<'a>>,
fields: &&[Located<AssignedField<'a, TypeAnnotation<'a>>>],
region: Region,
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
field_types: &mut SendMap<Lowercase, Type>,
references: &mut MutSet<Symbol>,
) -> Option<Lowercase> {
) -> SendMap<Lowercase, Type> {
use roc_parse::ast::AssignedField::*;
// SendMap doesn't have a `with_capacity`
let mut field_types = SendMap::default();
// field names we've seen so far in this record
let mut seen = std::collections::HashMap::with_capacity(fields.len());
'outer: for loc_field in fields.iter() {
let mut field = &loc_field.value;
// use this inner loop to unwrap the SpaceAfter/SpaceBefore
// when we find the name of this field, break out of the loop
// with that value, so we can check whether the field name is
// a duplicate
let new_name = 'inner: loop {
match field {
LabeledValue(field_name, _, annotation) => {
let field_type = can_annotation_help(
@ -434,7 +413,7 @@ fn can_assigned_field<'a>(
let label = Lowercase::from(field_name.value);
field_types.insert(label.clone(), field_type);
Some(label)
break 'inner label;
}
LabelOnly(loc_field_name) => {
// Interpret { a, b } as { a : a, b : b }
@ -451,36 +430,61 @@ fn can_assigned_field<'a>(
field_types.insert(field_name.clone(), field_type);
Some(field_name)
break 'inner field_name;
}
SpaceBefore(nested, _) | SpaceAfter(nested, _) => can_assigned_field(
env,
nested,
region,
scope,
var_store,
introduced_variables,
local_aliases,
field_types,
references,
),
Malformed(_) => None,
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
// check the nested field instead
field = nested;
continue 'inner;
}
Malformed(_) => {
// TODO report this?
// completely skip this element, advance to the next tag
continue 'outer;
}
}
};
// ensure that the new name is not already in this record:
// note that the right-most tag wins when there are two with the same name
if let Some(replaced_region) = seen.insert(new_name.clone(), loc_field.region) {
env.problem(roc_problem::can::Problem::DuplicateRecordFieldType {
field_name: new_name,
record_region: region,
field_region: loc_field.region,
replaced_region,
});
}
}
field_types
}
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
fn can_tag<'a>(
fn can_tags<'a>(
env: &mut Env,
tag: &Tag<'a>,
tags: &'a [Located<Tag<'a>>],
region: Region,
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
tag_types: &mut Vec<(TagName, Vec<Type>)>,
references: &mut MutSet<Symbol>,
) -> Option<TagName> {
) -> Vec<(TagName, Vec<Type>)> {
let mut tag_types = Vec::with_capacity(tags.len());
// tag names we've seen so far in this tag union
let mut seen = std::collections::HashMap::with_capacity(tags.len());
'outer: for loc_tag in tags.iter() {
let mut tag = &loc_tag.value;
// use this inner loop to unwrap the SpaceAfter/SpaceBefore
// when we find the name of this tag, break out of the loop
// with that value, so we can check whether the tag name is
// a duplicate
let new_name = 'inner: loop {
match tag {
Tag::Global { name, args } => {
let name = name.value.into();
@ -490,7 +494,7 @@ fn can_tag<'a>(
let ann = can_annotation_help(
env,
&arg.value,
region,
arg.region,
scope,
var_store,
introduced_variables,
@ -504,7 +508,7 @@ fn can_tag<'a>(
let tag_name = TagName::Global(name);
tag_types.push((tag_name.clone(), arg_types));
Some(tag_name)
break 'inner tag_name;
}
Tag::Private { name, args } => {
let ident_id = env.ident_ids.get_or_insert(&name.value.into());
@ -515,7 +519,7 @@ fn can_tag<'a>(
let ann = can_annotation_help(
env,
&arg.value,
region,
arg.region,
scope,
var_store,
introduced_variables,
@ -529,19 +533,32 @@ fn can_tag<'a>(
let tag_name = TagName::Private(symbol);
tag_types.push((tag_name.clone(), arg_types));
Some(tag_name)
break 'inner tag_name;
}
Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => can_tag(
env,
nested,
region,
scope,
var_store,
introduced_variables,
local_aliases,
tag_types,
references,
),
Tag::Malformed(_) => None,
Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => {
// check the nested tag instead
tag = nested;
continue 'inner;
}
Tag::Malformed(_) => {
// TODO report this?
// completely skip this element, advance to the next tag
continue 'outer;
}
}
};
// ensure that the new name is not already in this tag union:
// note that the right-most tag wins when there are two with the same name
if let Some(replaced_region) = seen.insert(new_name.clone(), loc_tag.region) {
env.problem(roc_problem::can::Problem::DuplicateTag {
tag_name: new_name,
tag_region: loc_tag.region,
tag_union_region: region,
replaced_region,
});
}
}
tag_types
}