Report specializations that target the unexpected type

This commit is contained in:
Ayaz Hafiz 2022-07-25 12:44:57 -04:00
parent 968bd468a8
commit 3703940da9
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
3 changed files with 111 additions and 28 deletions

View file

@ -110,6 +110,12 @@ pub enum TypeError {
derive_region: Region,
impl_region: Region,
},
WrongSpecialization {
region: Region,
ability_member: Symbol,
expected_opaque: Symbol,
found_opaque: Symbol,
},
}
use roc_types::types::Alias;
@ -1739,39 +1745,60 @@ fn check_ability_specialization(
match specialization_type {
Some(Obligated::Opaque(opaque)) => {
// This is a specialization for an opaque - that's allowed.
// This is a specialization for an opaque - but is it the opaque the
// specialization was claimed to be for?
if opaque == impl_key.opaque {
// It was! All is good.
subs.commit_snapshot(snapshot);
introduce(subs, rank, pools, &vars);
subs.commit_snapshot(snapshot);
introduce(subs, rank, pools, &vars);
let specialization_lambda_sets = specialization_lambda_sets
.into_iter()
.map(|((symbol, region), var)| {
debug_assert_eq!(symbol, ability_member);
(region, var)
})
.collect();
let specialization_lambda_sets = specialization_lambda_sets
.into_iter()
.map(|((symbol, region), var)| {
debug_assert_eq!(symbol, ability_member);
(region, var)
})
.collect();
deferred_uls_to_resolve.union(other_lambda_sets_to_specialize);
deferred_uls_to_resolve.union(other_lambda_sets_to_specialize);
let specialization_region = symbol_loc_var.region;
let specialization =
MemberSpecializationInfo::new(symbol, specialization_lambda_sets);
let specialization_region = symbol_loc_var.region;
let specialization =
MemberSpecializationInfo::new(symbol, specialization_lambda_sets);
// Make sure we check that the opaque has specialized all members of the
// ability, after we finish solving the module.
deferred_obligations
.add(must_implement_ability, AbilityImplError::IncompleteAbility);
// This specialization dominates any derives that might be present.
deferred_obligations.dominate(
RequestedDeriveKey {
opaque,
ability: parent_ability,
},
specialization_region,
);
// Make sure we check that the opaque has specialized all members of the
// ability, after we finish solving the module.
deferred_obligations
.add(must_implement_ability, AbilityImplError::IncompleteAbility);
// This specialization dominates any derives that might be present.
deferred_obligations.dominate(
RequestedDeriveKey {
opaque,
ability: parent_ability,
},
specialization_region,
);
Ok(specialization)
Ok(specialization)
} else {
// This def is not specialized for the claimed opaque type, that's an
// error.
// Commit so that `var` persists in subs.
subs.commit_snapshot(snapshot);
let problem = TypeError::WrongSpecialization {
region: symbol_loc_var.region,
ability_member: impl_key.ability_member,
expected_opaque: impl_key.opaque,
found_opaque: opaque,
};
problems.push(problem);
Err(())
}
}
Some(Obligated::Adhoc(var)) => {
// This is a specialization of a structural type - never allowed.
@ -1795,7 +1822,7 @@ fn check_ability_specialization(
None => {
// This can happen when every ability constriant on a type variable went
// through only another type variable. That means this def is not specialized
// for one concrete type - we won't admit this.
// for one concrete type, and especially not our opaque - we won't admit this currently.
// Rollback the snapshot so we unlink the root signature with the specialization,
// so we can have two separate error types.

View file

@ -254,6 +254,35 @@ pub fn type_problem<'b>(
severity: Severity::Warning,
})
}
WrongSpecialization {
region,
ability_member,
expected_opaque,
found_opaque,
} => {
let stack = [
alloc.concat([
alloc.reflow("This specialization of "),
alloc.symbol_unqualified(ability_member),
alloc.reflow(" is not for the expected type:"),
]),
alloc.region(lines.convert_region(region)),
alloc.concat([
alloc.reflow("It was previously claimed to be a specialization for "),
alloc.symbol_unqualified(expected_opaque),
alloc.reflow(", but was determined to actually specialize "),
alloc.symbol_unqualified(found_opaque),
alloc.reflow("!"),
]),
];
Some(Report {
title: "WRONG SPECIALIZATION TYPE".to_string(),
filename,
doc: alloc.stack(stack),
severity: Severity::RuntimeError,
})
}
}
}

View file

@ -10089,4 +10089,31 @@ All branches in an `if` must have the same type!
your code don't wonder why it is there.
"###
);
test_report!(
specialization_for_wrong_type,
indoc!(
r#"
app "test" provides [hash, Id, Id2] to "./platform"
Hash has hash : a -> U64 | a has Hash
Id := {} has [Hash {hash}]
Id2 := {}
hash = \@Id2 _ -> 0
"#
),
@r###"
WRONG SPECIALIZATION TYPE /code/proj/Main.roc
This specialization of `hash` is not for the expected type:
8 hash = \@Id2 _ -> 0
^^^^
It was previously claimed to be a specialization for `Id`, but was
determined to actually specialize `Id2`!
"###
);
}