mirror of
https://github.com/roc-lang/roc.git
synced 2025-11-02 05:48:17 +00:00
Make sure late specializations of opaques inherit Inspect as needed
A "late specialization" of a type is an ability specialization that
is not visible or needed until after type-specialization; i.e. during
monomorphization.
The `Inspect.toInspector` ability is special-cased for opaques that do
not claim or explicitly implement `Inspect`. In such cases, they are
treated as structural types, and given the immediate specialization of
`Inpect.inspectOpaque`.
However, prior to this commit, that special-casing would only be applied
during early specialiation (i.e. specializations visible during
generalized type inference). This commit applies the special case to
late specialization as well - the specialization decision for an opaque
type is always the specialization of the opaque type in the late case,
but now, when we go to look up the ambient lambda set of the
specialization, if it does not exist and corresponds to
`Inspect.toInspector`, we fall back to the immediate.
One concern I have here is that in a case like
```
Op := {}
x =
dbg (@Op {})
```
the specialization of `Inspect.toInspector` for `Op` should be known
early. Indeed, the program
```
Op := {}
x =
Inspect.toInspector (@Op {}) |> Inspect.apply (Inspect.init {}) |> Inspect.toDbgStr
```
Compiles fine without this change. This makes me suspect there is an
issue with the implementation of `dbg`'s desugaring. If possible, this
should be addressed sooner rather than later.
Closes #6127
This commit is contained in:
parent
7d2b8a509d
commit
a53da2bc24
5 changed files with 285 additions and 90 deletions
|
|
@ -4662,7 +4662,9 @@ pub fn add_imports(
|
|||
|
||||
let mut cached_symbol_vars = VecMap::default();
|
||||
|
||||
for &symbol in &exposed_for_module.imported_values {
|
||||
let imported_values = exposed_for_module.imported_values.iter().copied();
|
||||
|
||||
for symbol in imported_values {
|
||||
import_variable_for_symbol(
|
||||
subs,
|
||||
constraints,
|
||||
|
|
|
|||
|
|
@ -723,115 +723,155 @@ fn get_specialization_lambda_set_ambient_function<P: Phase>(
|
|||
phase: &P,
|
||||
ability_member: Symbol,
|
||||
lset_region: u8,
|
||||
specialization_key: SpecializationTypeKey,
|
||||
mut specialization_key: SpecializationTypeKey,
|
||||
target_rank: Rank,
|
||||
) -> Result<Variable, ()> {
|
||||
match specialization_key {
|
||||
SpecializationTypeKey::Opaque(opaque) => {
|
||||
let opaque_home = opaque.module_id();
|
||||
let external_specialized_lset =
|
||||
phase.with_module_abilities_store(opaque_home, |abilities_store| {
|
||||
let impl_key = roc_can::abilities::ImplKey {
|
||||
loop {
|
||||
match specialization_key {
|
||||
SpecializationTypeKey::Opaque(opaque) => {
|
||||
let opaque_home = opaque.module_id();
|
||||
let found = phase.with_module_abilities_store(opaque_home, |abilities_store| {
|
||||
find_opaque_specialization_ambient_function(
|
||||
abilities_store,
|
||||
opaque,
|
||||
ability_member,
|
||||
};
|
||||
lset_region,
|
||||
)
|
||||
});
|
||||
|
||||
let opt_specialization =
|
||||
abilities_store.get_implementation(impl_key);
|
||||
match opt_specialization {
|
||||
None => {
|
||||
if P::IS_LATE {
|
||||
internal_error!(
|
||||
"expected to know a specialization for {:?}#{:?}, but it wasn't found",
|
||||
opaque,
|
||||
ability_member
|
||||
);
|
||||
} else {
|
||||
// doesn't specialize, we'll have reported an error for this
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
Some(member_impl) => match member_impl {
|
||||
MemberImpl::Impl(spec_symbol) => {
|
||||
let specialization =
|
||||
abilities_store.specialization_info(*spec_symbol).expect("expected custom implementations to always have complete specialization info by this point");
|
||||
|
||||
let specialized_lambda_set = *specialization
|
||||
.specialization_lambda_sets
|
||||
.get(&lset_region)
|
||||
.unwrap_or_else(|| panic!("lambda set region not resolved: {:?}", (spec_symbol, specialization)));
|
||||
Ok(specialized_lambda_set)
|
||||
}
|
||||
MemberImpl::Error => todo_abilities!(),
|
||||
},
|
||||
let external_specialized_lset = match found {
|
||||
FoundOpaqueSpecialization::UpdatedSpecializationKey(key) => {
|
||||
specialization_key = key;
|
||||
continue;
|
||||
}
|
||||
})?;
|
||||
FoundOpaqueSpecialization::AmbientFunction(lset) => lset,
|
||||
FoundOpaqueSpecialization::NotFound => {
|
||||
if P::IS_LATE {
|
||||
internal_error!(
|
||||
"expected to know a specialization for {:?}#{:?}, but it wasn't found",
|
||||
opaque,
|
||||
ability_member
|
||||
);
|
||||
} else {
|
||||
// We'll have reported an error for this.
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let specialized_ambient = phase.copy_lambda_set_ambient_function_to_home_subs(
|
||||
external_specialized_lset,
|
||||
opaque_home,
|
||||
subs,
|
||||
);
|
||||
let specialized_ambient = phase.copy_lambda_set_ambient_function_to_home_subs(
|
||||
external_specialized_lset,
|
||||
opaque_home,
|
||||
subs,
|
||||
);
|
||||
|
||||
Ok(specialized_ambient)
|
||||
}
|
||||
return Ok(specialized_ambient);
|
||||
}
|
||||
|
||||
SpecializationTypeKey::Derived(derive_key) => {
|
||||
let mut derived_module = derived_env.derived_module.lock().unwrap();
|
||||
SpecializationTypeKey::Derived(derive_key) => {
|
||||
let mut derived_module = derived_env.derived_module.lock().unwrap();
|
||||
|
||||
let (_, _, specialization_lambda_sets) =
|
||||
derived_module.get_or_insert(derived_env.exposed_types, derive_key);
|
||||
let (_, _, specialization_lambda_sets) =
|
||||
derived_module.get_or_insert(derived_env.exposed_types, derive_key);
|
||||
|
||||
let specialized_lambda_set = *specialization_lambda_sets
|
||||
.get(&lset_region)
|
||||
.expect("lambda set region not resolved");
|
||||
let specialized_lambda_set = *specialization_lambda_sets
|
||||
.get(&lset_region)
|
||||
.expect("lambda set region not resolved");
|
||||
|
||||
let specialized_ambient = derived_module.copy_lambda_set_ambient_function_to_subs(
|
||||
specialized_lambda_set,
|
||||
subs,
|
||||
target_rank,
|
||||
);
|
||||
let specialized_ambient = derived_module.copy_lambda_set_ambient_function_to_subs(
|
||||
specialized_lambda_set,
|
||||
subs,
|
||||
target_rank,
|
||||
);
|
||||
|
||||
Ok(specialized_ambient)
|
||||
}
|
||||
return Ok(specialized_ambient);
|
||||
}
|
||||
|
||||
SpecializationTypeKey::Immediate(imm) => {
|
||||
// Immediates are like opaques in that we can simply look up their type definition in
|
||||
// the ability store, there is nothing new to synthesize.
|
||||
//
|
||||
// THEORY: if something can become an immediate, it will always be available in the
|
||||
// local ability store, because the transformation is local (?)
|
||||
//
|
||||
// TODO: I actually think we can get what we need here by examining `derived_env.exposed_types`,
|
||||
// since immediates can only refer to builtins - and in userspace, all builtin types
|
||||
// are available in `exposed_types`.
|
||||
let immediate_lambda_set_at_region =
|
||||
phase.get_and_copy_ability_member_ambient_function(imm, lset_region, subs);
|
||||
SpecializationTypeKey::Immediate(imm) => {
|
||||
// Immediates are like opaques in that we can simply look up their type definition in
|
||||
// the ability store, there is nothing new to synthesize.
|
||||
//
|
||||
// THEORY: if something can become an immediate, it will always be available in the
|
||||
// local ability store, because the transformation is local (?)
|
||||
//
|
||||
// TODO: I actually think we can get what we need here by examining `derived_env.exposed_types`,
|
||||
// since immediates can only refer to builtins - and in userspace, all builtin types
|
||||
// are available in `exposed_types`.
|
||||
let immediate_lambda_set_at_region =
|
||||
phase.get_and_copy_ability_member_ambient_function(imm, lset_region, subs);
|
||||
|
||||
Ok(immediate_lambda_set_at_region)
|
||||
}
|
||||
return Ok(immediate_lambda_set_at_region);
|
||||
}
|
||||
|
||||
SpecializationTypeKey::SingleLambdaSetImmediate(imm) => {
|
||||
let module_id = imm.module_id();
|
||||
debug_assert!(module_id.is_builtin());
|
||||
SpecializationTypeKey::SingleLambdaSetImmediate(imm) => {
|
||||
let module_id = imm.module_id();
|
||||
debug_assert!(module_id.is_builtin());
|
||||
|
||||
let module_types = &derived_env
|
||||
.exposed_types
|
||||
.get(&module_id)
|
||||
.unwrap()
|
||||
.exposed_types_storage_subs;
|
||||
let module_types = &derived_env
|
||||
.exposed_types
|
||||
.get(&module_id)
|
||||
.unwrap()
|
||||
.exposed_types_storage_subs;
|
||||
|
||||
// Since this immediate has only one lambda set, the region must be pointing to 1, and
|
||||
// moreover the imported function type is the ambient function of the single lset.
|
||||
debug_assert_eq!(lset_region, 1);
|
||||
let storage_var = module_types.stored_vars_by_symbol.get(&imm).unwrap();
|
||||
let imported = module_types
|
||||
.storage_subs
|
||||
.export_variable_to(subs, *storage_var);
|
||||
// Since this immediate has only one lambda set, the region must be pointing to 1, and
|
||||
// moreover the imported function type is the ambient function of the single lset.
|
||||
debug_assert_eq!(lset_region, 1);
|
||||
let storage_var = module_types.stored_vars_by_symbol.get(&imm).unwrap();
|
||||
let imported = module_types
|
||||
.storage_subs
|
||||
.export_variable_to(subs, *storage_var);
|
||||
|
||||
roc_types::subs::instantiate_rigids(subs, imported.variable);
|
||||
roc_types::subs::instantiate_rigids(subs, imported.variable);
|
||||
|
||||
Ok(imported.variable)
|
||||
return Ok(imported.variable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum FoundOpaqueSpecialization {
|
||||
UpdatedSpecializationKey(SpecializationTypeKey),
|
||||
AmbientFunction(Variable),
|
||||
NotFound,
|
||||
}
|
||||
|
||||
fn find_opaque_specialization_ambient_function(
|
||||
abilities_store: &AbilitiesStore,
|
||||
opaque: Symbol,
|
||||
ability_member: Symbol,
|
||||
lset_region: u8,
|
||||
) -> FoundOpaqueSpecialization {
|
||||
let impl_key = roc_can::abilities::ImplKey {
|
||||
opaque,
|
||||
ability_member,
|
||||
};
|
||||
|
||||
let opt_specialization = abilities_store.get_implementation(impl_key);
|
||||
match opt_specialization {
|
||||
None => match ability_member {
|
||||
Symbol::INSPECT_TO_INSPECTOR => FoundOpaqueSpecialization::UpdatedSpecializationKey(
|
||||
SpecializationTypeKey::Immediate(Symbol::INSPECT_OPAQUE),
|
||||
),
|
||||
_ => FoundOpaqueSpecialization::NotFound,
|
||||
},
|
||||
Some(member_impl) => match member_impl {
|
||||
MemberImpl::Impl(spec_symbol) => {
|
||||
let specialization =
|
||||
abilities_store.specialization_info(*spec_symbol).expect("expected custom implementations to always have complete specialization info by this point");
|
||||
|
||||
let specialized_lambda_set = *specialization
|
||||
.specialization_lambda_sets
|
||||
.get(&lset_region)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"lambda set region not resolved: {:?}",
|
||||
(spec_symbol, specialization)
|
||||
)
|
||||
});
|
||||
|
||||
FoundOpaqueSpecialization::AmbientFunction(specialized_lambda_set)
|
||||
}
|
||||
MemberImpl::Error => todo_abilities!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2288,4 +2288,42 @@ mod inspect {
|
|||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn opaque_automatic() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Op := {}
|
||||
|
||||
main = Inspect.toDbgStr (Inspect.inspect (@Op {}))
|
||||
"#
|
||||
),
|
||||
RocStr::from(r#"<opaque>"#),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn opaque_automatic_with_polymorphic_call() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Op := {}
|
||||
|
||||
late = \a -> Inspect.toDbgStr (Inspect.inspect a)
|
||||
|
||||
main = late (@Op {})
|
||||
"#
|
||||
),
|
||||
RocStr::from(r#"<opaque>"#),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
# +emit:mono
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Op := {}
|
||||
|
||||
main =
|
||||
dbg (@Op {})
|
||||
1
|
||||
|
||||
# -emit:mono
|
||||
procedure Inspect.251 (Inspect.252):
|
||||
let Inspect.317 : Str = "<opaque>";
|
||||
let Inspect.316 : Str = CallByName Inspect.61 Inspect.252 Inspect.317;
|
||||
ret Inspect.316;
|
||||
|
||||
procedure Inspect.30 (Inspect.147):
|
||||
ret Inspect.147;
|
||||
|
||||
procedure Inspect.35 (Inspect.300):
|
||||
ret Inspect.300;
|
||||
|
||||
procedure Inspect.36 (Inspect.304):
|
||||
let Inspect.311 : Str = "";
|
||||
ret Inspect.311;
|
||||
|
||||
procedure Inspect.45 (Inspect.302):
|
||||
let Inspect.314 : {} = Struct {};
|
||||
let Inspect.313 : {} = CallByName Inspect.30 Inspect.314;
|
||||
ret Inspect.313;
|
||||
|
||||
procedure Inspect.5 (Inspect.150):
|
||||
let Inspect.312 : {} = CallByName Inspect.45 Inspect.150;
|
||||
let Inspect.309 : {} = Struct {};
|
||||
let Inspect.308 : Str = CallByName Inspect.36 Inspect.309;
|
||||
let Inspect.307 : Str = CallByName Inspect.251 Inspect.308;
|
||||
ret Inspect.307;
|
||||
|
||||
procedure Inspect.61 (Inspect.303, Inspect.298):
|
||||
let Inspect.319 : Str = CallByName Str.3 Inspect.303 Inspect.298;
|
||||
dec Inspect.298;
|
||||
ret Inspect.319;
|
||||
|
||||
procedure Str.3 (#Attr.2, #Attr.3):
|
||||
let Str.292 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
||||
ret Str.292;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.5 : {} = Struct {};
|
||||
let Test.4 : Str = CallByName Inspect.5 Test.5;
|
||||
let Test.2 : Str = CallByName Inspect.35 Test.4;
|
||||
dbg Test.2;
|
||||
dec Test.2;
|
||||
let Test.3 : I64 = 1i64;
|
||||
ret Test.3;
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# +emit:mono
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Op := {}
|
||||
|
||||
late = \a ->
|
||||
dbg a
|
||||
1
|
||||
|
||||
main =
|
||||
late (@Op {})
|
||||
|
||||
# -emit:mono
|
||||
procedure Inspect.251 (Inspect.252):
|
||||
let Inspect.317 : Str = "<opaque>";
|
||||
let Inspect.316 : Str = CallByName Inspect.61 Inspect.252 Inspect.317;
|
||||
ret Inspect.316;
|
||||
|
||||
procedure Inspect.30 (Inspect.147):
|
||||
ret Inspect.147;
|
||||
|
||||
procedure Inspect.35 (Inspect.300):
|
||||
ret Inspect.300;
|
||||
|
||||
procedure Inspect.36 (Inspect.304):
|
||||
let Inspect.311 : Str = "";
|
||||
ret Inspect.311;
|
||||
|
||||
procedure Inspect.45 (Inspect.302):
|
||||
let Inspect.314 : {} = Struct {};
|
||||
let Inspect.313 : {} = CallByName Inspect.30 Inspect.314;
|
||||
ret Inspect.313;
|
||||
|
||||
procedure Inspect.5 (Inspect.150):
|
||||
let Inspect.312 : {} = CallByName Inspect.45 Inspect.150;
|
||||
let Inspect.309 : {} = Struct {};
|
||||
let Inspect.308 : Str = CallByName Inspect.36 Inspect.309;
|
||||
let Inspect.307 : Str = CallByName Inspect.251 Inspect.308;
|
||||
ret Inspect.307;
|
||||
|
||||
procedure Inspect.61 (Inspect.303, Inspect.298):
|
||||
let Inspect.319 : Str = CallByName Str.3 Inspect.303 Inspect.298;
|
||||
dec Inspect.298;
|
||||
ret Inspect.319;
|
||||
|
||||
procedure Str.3 (#Attr.2, #Attr.3):
|
||||
let Str.292 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
||||
ret Str.292;
|
||||
|
||||
procedure Test.2 (Test.3):
|
||||
let Test.8 : Str = CallByName Inspect.5 Test.3;
|
||||
let Test.4 : Str = CallByName Inspect.35 Test.8;
|
||||
dbg Test.4;
|
||||
dec Test.4;
|
||||
let Test.7 : I64 = 1i64;
|
||||
ret Test.7;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.6 : {} = Struct {};
|
||||
let Test.5 : I64 = CallByName Test.2 Test.6;
|
||||
ret Test.5;
|
||||
Loading…
Add table
Add a link
Reference in a new issue