Introduce annotation in first step of recursive solving independently

The algorithm for solving recursive definitions proceeds in several
steps. There are three main phases: introduction of what's known,
solving what's not known, and then checking our work of what was
inferred against what the programmer claimed. Concretely:

1. All explicitly-annotated signatures in the mutually recursive set are
   introduced and let-generalized.
2. Then, inference type variables (`_`) and unannotated def signatures are
   introduced to the cycle, without generalization. The bodies of these
   defs, that are either unannotated or have inference variables, are
   solved.
3. The defs from step (2) are now let-generalized, since we now know
   that their types are consistent. At this point, all the defs in the
   cycle have their types introduced and let-generalized, but we still
   haven't checked the bodies of the defs froom step (1).
4. Check the bodies of explicitly-annotated defs in recursive set. This
   might materially affect the actual types in the signature, for
   example do to fixpoint-fixing or alias expansion.
5. As a result of (4) possibly changing the structure of the annotated
   type, and because the previous annotated types in (1) were introduced
   at a lower rank, we now re-introduce and re-generalize the solved def
   types, in the same we did in step (3).
5. The rest of the program is solved.

Now, a very important thing here is that the annotation signature
introduced for (1) consists of different type variables than the
annotation signature introduced in (5). The reason is that they live at
different ranks. Prior to this patch we were not explicilty doing so;
this commit ensures that we do.
This commit is contained in:
Ayaz Hafiz 2023-04-10 15:54:45 -05:00
parent 352345d1d9
commit b9ab93fd98
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
3 changed files with 113 additions and 54 deletions

View file

@ -2097,6 +2097,7 @@ fn constrain_destructure_def(
loc_pattern,
&mut ftv,
&mut def_pattern_state.headers,
IsRecursiveDef::No,
);
let env = &mut Env {
@ -2674,6 +2675,7 @@ fn constrain_typed_def(
&def.loc_pattern,
&mut ftv,
&mut def_pattern_state.headers,
IsRecursiveDef::No,
);
let env = &mut Env {
@ -3323,6 +3325,12 @@ pub struct InstantiateRigids {
pub new_infer_variables: Vec<Variable>,
}
#[derive(PartialEq, Eq)]
enum IsRecursiveDef {
Yes,
No,
}
fn instantiate_rigids(
types: &mut Types,
constraints: &mut Constraints,
@ -3331,65 +3339,83 @@ fn instantiate_rigids(
loc_pattern: &Loc<Pattern>,
ftv: &mut MutMap<Lowercase, Variable>, // rigids defined before the current annotation
headers: &mut VecMap<Symbol, Loc<TypeOrVar>>,
is_recursive_def: IsRecursiveDef,
) -> InstantiateRigids {
let mut annotation = annotation.clone();
let mut new_rigid_variables: Vec<Variable> = Vec::new();
let mut new_rigid_variables = vec![];
let mut new_infer_variables = vec![];
let mut rigid_substitution: MutMap<Variable, Variable> = MutMap::default();
for named in introduced_vars.iter_named() {
use std::collections::hash_map::Entry::*;
let mut generate_fresh_ann = |types: &mut Types| {
let mut annotation = annotation.clone();
match ftv.entry(named.name().clone()) {
Occupied(occupied) => {
let existing_rigid = occupied.get();
rigid_substitution.insert(named.variable(), *existing_rigid);
}
Vacant(vacant) => {
// It's possible to use this rigid in nested defs
vacant.insert(named.variable());
new_rigid_variables.push(named.variable());
let mut rigid_substitution: MutMap<Variable, Variable> = MutMap::default();
for named in introduced_vars.iter_named() {
use std::collections::hash_map::Entry::*;
match ftv.entry(named.name().clone()) {
Occupied(occupied) => {
let existing_rigid = occupied.get();
rigid_substitution.insert(named.variable(), *existing_rigid);
}
Vacant(vacant) => {
// It's possible to use this rigid in nested defs
vacant.insert(named.variable());
new_rigid_variables.push(named.variable());
}
}
}
// wildcards are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.wildcards.iter().map(|v| v.value));
// lambda set vars are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied());
// ext-infer vars are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.infer_ext_in_output.iter().copied());
new_infer_variables.extend(introduced_vars.inferred.iter().map(|v| v.value));
// Instantiate rigid variables
if !rigid_substitution.is_empty() {
annotation.substitute_variables(&rigid_substitution);
}
let annotation_index = types.from_old_type(&annotation);
annotation_index
};
let signature = generate_fresh_ann(types);
{
// If this is a recursive def, we must also generate a fresh annotation to be used as the
// type annotation that will be used in the first def headers introduced during the solving
// of the recursive definition.
//
// That is, this annotation serves as step (1) of XREF(rec-def-strategy). We don't want to
// link to the final annotation, since it may be incomplete (or incorrect, see step (1)).
// So, we generate a fresh annotation here, and return a separate fresh annotation below;
// the latter annotation is the one used to construct the finalized type.
let annotation_index = if is_recursive_def == IsRecursiveDef::Yes {
generate_fresh_ann(types)
} else {
signature
};
let loc_annotation_ref = Loc::at(loc_pattern.region, annotation_index);
if let Pattern::Identifier(symbol) = loc_pattern.value {
let annotation_index = constraints.push_type(types, annotation_index);
headers.insert(symbol, Loc::at(loc_pattern.region, annotation_index));
} else if let Some(new_headers) = crate::pattern::headers_from_annotation(
types,
constraints,
&loc_pattern.value,
&loc_annotation_ref,
) {
headers.extend(new_headers)
}
}
// wildcards are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.wildcards.iter().map(|v| v.value));
// lambda set vars are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied());
// ext-infer vars are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.infer_ext_in_output.iter().copied());
let new_infer_variables: Vec<Variable> =
introduced_vars.inferred.iter().map(|v| v.value).collect();
// Instantiate rigid variables
if !rigid_substitution.is_empty() {
annotation.substitute_variables(&rigid_substitution);
}
let annotation_index = types.from_old_type(&annotation);
// TODO investigate when we can skip this. It seems to only be required for correctness
// for recursive functions. For non-recursive functions the final type is correct, but
// alias information is sometimes lost
//
// Skipping all of this cloning here would be neat!
let loc_annotation_ref = Loc::at(loc_pattern.region, &annotation);
if let Pattern::Identifier(symbol) = loc_pattern.value {
let annotation_index = constraints.push_type(types, annotation_index);
headers.insert(symbol, Loc::at(loc_pattern.region, annotation_index));
} else if let Some(new_headers) = crate::pattern::headers_from_annotation(
types,
constraints,
&loc_pattern.value,
&loc_annotation_ref,
) {
headers.extend(new_headers)
}
InstantiateRigids {
signature: annotation_index,
signature,
new_rigid_variables,
new_infer_variables,
}
@ -3867,7 +3893,7 @@ pub fn rec_defs_help_simple(
}
}
// Strategy for recursive defs:
// NB(rec-def-strategy) Strategy for recursive defs:
//
// 1. Let-generalize all rigid annotations. These are the source of truth we'll solve
// everything else with. If there are circular type errors here, they will be caught
@ -4068,6 +4094,7 @@ fn rec_defs_help(
&def.loc_pattern,
&mut ftv,
&mut def_pattern_state.headers,
IsRecursiveDef::Yes,
);
let is_hybrid = !new_infer_variables.is_empty();
@ -4289,7 +4316,7 @@ fn rec_defs_help(
}
}
// Strategy for recursive defs:
// NB(rec-def-strategy) Strategy for recursive defs:
//
// 1. Let-generalize all rigid annotations. These are the source of truth we'll solve
// everything else with. If there are circular type errors here, they will be caught