Skip to content

Commit a53da2b

Browse files
committed
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
1 parent 7d2b8a5 commit a53da2b

File tree

5 files changed

+286
-91
lines changed

5 files changed

+286
-91
lines changed

crates/compiler/load_internal/src/file.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4662,7 +4662,9 @@ pub fn add_imports(
46624662

46634663
let mut cached_symbol_vars = VecMap::default();
46644664

4665-
for &symbol in &exposed_for_module.imported_values {
4665+
let imported_values = exposed_for_module.imported_values.iter().copied();
4666+
4667+
for symbol in imported_values {
46664668
import_variable_for_symbol(
46674669
subs,
46684670
constraints,

crates/compiler/solve/src/specialize.rs

Lines changed: 130 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -723,115 +723,155 @@ fn get_specialization_lambda_set_ambient_function<P: Phase>(
723723
phase: &P,
724724
ability_member: Symbol,
725725
lset_region: u8,
726-
specialization_key: SpecializationTypeKey,
726+
mut specialization_key: SpecializationTypeKey,
727727
target_rank: Rank,
728728
) -> Result<Variable, ()> {
729-
match specialization_key {
730-
SpecializationTypeKey::Opaque(opaque) => {
731-
let opaque_home = opaque.module_id();
732-
let external_specialized_lset =
733-
phase.with_module_abilities_store(opaque_home, |abilities_store| {
734-
let impl_key = roc_can::abilities::ImplKey {
729+
loop {
730+
match specialization_key {
731+
SpecializationTypeKey::Opaque(opaque) => {
732+
let opaque_home = opaque.module_id();
733+
let found = phase.with_module_abilities_store(opaque_home, |abilities_store| {
734+
find_opaque_specialization_ambient_function(
735+
abilities_store,
735736
opaque,
736737
ability_member,
737-
};
738-
739-
let opt_specialization =
740-
abilities_store.get_implementation(impl_key);
741-
match opt_specialization {
742-
None => {
743-
if P::IS_LATE {
744-
internal_error!(
745-
"expected to know a specialization for {:?}#{:?}, but it wasn't found",
746-
opaque,
747-
ability_member
748-
);
749-
} else {
750-
// doesn't specialize, we'll have reported an error for this
751-
Err(())
752-
}
738+
lset_region,
739+
)
740+
});
741+
742+
let external_specialized_lset = match found {
743+
FoundOpaqueSpecialization::UpdatedSpecializationKey(key) => {
744+
specialization_key = key;
745+
continue;
746+
}
747+
FoundOpaqueSpecialization::AmbientFunction(lset) => lset,
748+
FoundOpaqueSpecialization::NotFound => {
749+
if P::IS_LATE {
750+
internal_error!(
751+
"expected to know a specialization for {:?}#{:?}, but it wasn't found",
752+
opaque,
753+
ability_member
754+
);
755+
} else {
756+
// We'll have reported an error for this.
757+
return Err(());
753758
}
754-
Some(member_impl) => match member_impl {
755-
MemberImpl::Impl(spec_symbol) => {
756-
let specialization =
757-
abilities_store.specialization_info(*spec_symbol).expect("expected custom implementations to always have complete specialization info by this point");
758-
759-
let specialized_lambda_set = *specialization
760-
.specialization_lambda_sets
761-
.get(&lset_region)
762-
.unwrap_or_else(|| panic!("lambda set region not resolved: {:?}", (spec_symbol, specialization)));
763-
Ok(specialized_lambda_set)
764-
}
765-
MemberImpl::Error => todo_abilities!(),
766-
},
767759
}
768-
})?;
760+
};
769761

770-
let specialized_ambient = phase.copy_lambda_set_ambient_function_to_home_subs(
771-
external_specialized_lset,
772-
opaque_home,
773-
subs,
774-
);
762+
let specialized_ambient = phase.copy_lambda_set_ambient_function_to_home_subs(
763+
external_specialized_lset,
764+
opaque_home,
765+
subs,
766+
);
775767

776-
Ok(specialized_ambient)
777-
}
768+
return Ok(specialized_ambient);
769+
}
778770

779-
SpecializationTypeKey::Derived(derive_key) => {
780-
let mut derived_module = derived_env.derived_module.lock().unwrap();
771+
SpecializationTypeKey::Derived(derive_key) => {
772+
let mut derived_module = derived_env.derived_module.lock().unwrap();
781773

782-
let (_, _, specialization_lambda_sets) =
783-
derived_module.get_or_insert(derived_env.exposed_types, derive_key);
774+
let (_, _, specialization_lambda_sets) =
775+
derived_module.get_or_insert(derived_env.exposed_types, derive_key);
784776

785-
let specialized_lambda_set = *specialization_lambda_sets
786-
.get(&lset_region)
787-
.expect("lambda set region not resolved");
777+
let specialized_lambda_set = *specialization_lambda_sets
778+
.get(&lset_region)
779+
.expect("lambda set region not resolved");
788780

789-
let specialized_ambient = derived_module.copy_lambda_set_ambient_function_to_subs(
790-
specialized_lambda_set,
791-
subs,
792-
target_rank,
793-
);
781+
let specialized_ambient = derived_module.copy_lambda_set_ambient_function_to_subs(
782+
specialized_lambda_set,
783+
subs,
784+
target_rank,
785+
);
794786

795-
Ok(specialized_ambient)
796-
}
787+
return Ok(specialized_ambient);
788+
}
797789

798-
SpecializationTypeKey::Immediate(imm) => {
799-
// Immediates are like opaques in that we can simply look up their type definition in
800-
// the ability store, there is nothing new to synthesize.
801-
//
802-
// THEORY: if something can become an immediate, it will always be available in the
803-
// local ability store, because the transformation is local (?)
804-
//
805-
// TODO: I actually think we can get what we need here by examining `derived_env.exposed_types`,
806-
// since immediates can only refer to builtins - and in userspace, all builtin types
807-
// are available in `exposed_types`.
808-
let immediate_lambda_set_at_region =
809-
phase.get_and_copy_ability_member_ambient_function(imm, lset_region, subs);
810-
811-
Ok(immediate_lambda_set_at_region)
812-
}
790+
SpecializationTypeKey::Immediate(imm) => {
791+
// Immediates are like opaques in that we can simply look up their type definition in
792+
// the ability store, there is nothing new to synthesize.
793+
//
794+
// THEORY: if something can become an immediate, it will always be available in the
795+
// local ability store, because the transformation is local (?)
796+
//
797+
// TODO: I actually think we can get what we need here by examining `derived_env.exposed_types`,
798+
// since immediates can only refer to builtins - and in userspace, all builtin types
799+
// are available in `exposed_types`.
800+
let immediate_lambda_set_at_region =
801+
phase.get_and_copy_ability_member_ambient_function(imm, lset_region, subs);
802+
803+
return Ok(immediate_lambda_set_at_region);
804+
}
813805

814-
SpecializationTypeKey::SingleLambdaSetImmediate(imm) => {
815-
let module_id = imm.module_id();
816-
debug_assert!(module_id.is_builtin());
806+
SpecializationTypeKey::SingleLambdaSetImmediate(imm) => {
807+
let module_id = imm.module_id();
808+
debug_assert!(module_id.is_builtin());
817809

818-
let module_types = &derived_env
819-
.exposed_types
820-
.get(&module_id)
821-
.unwrap()
822-
.exposed_types_storage_subs;
810+
let module_types = &derived_env
811+
.exposed_types
812+
.get(&module_id)
813+
.unwrap()
814+
.exposed_types_storage_subs;
823815

824-
// Since this immediate has only one lambda set, the region must be pointing to 1, and
825-
// moreover the imported function type is the ambient function of the single lset.
826-
debug_assert_eq!(lset_region, 1);
827-
let storage_var = module_types.stored_vars_by_symbol.get(&imm).unwrap();
828-
let imported = module_types
829-
.storage_subs
830-
.export_variable_to(subs, *storage_var);
816+
// Since this immediate has only one lambda set, the region must be pointing to 1, and
817+
// moreover the imported function type is the ambient function of the single lset.
818+
debug_assert_eq!(lset_region, 1);
819+
let storage_var = module_types.stored_vars_by_symbol.get(&imm).unwrap();
820+
let imported = module_types
821+
.storage_subs
822+
.export_variable_to(subs, *storage_var);
831823

832-
roc_types::subs::instantiate_rigids(subs, imported.variable);
824+
roc_types::subs::instantiate_rigids(subs, imported.variable);
833825

834-
Ok(imported.variable)
826+
return Ok(imported.variable);
827+
}
835828
}
836829
}
837830
}
831+
832+
enum FoundOpaqueSpecialization {
833+
UpdatedSpecializationKey(SpecializationTypeKey),
834+
AmbientFunction(Variable),
835+
NotFound,
836+
}
837+
838+
fn find_opaque_specialization_ambient_function(
839+
abilities_store: &AbilitiesStore,
840+
opaque: Symbol,
841+
ability_member: Symbol,
842+
lset_region: u8,
843+
) -> FoundOpaqueSpecialization {
844+
let impl_key = roc_can::abilities::ImplKey {
845+
opaque,
846+
ability_member,
847+
};
848+
849+
let opt_specialization = abilities_store.get_implementation(impl_key);
850+
match opt_specialization {
851+
None => match ability_member {
852+
Symbol::INSPECT_TO_INSPECTOR => FoundOpaqueSpecialization::UpdatedSpecializationKey(
853+
SpecializationTypeKey::Immediate(Symbol::INSPECT_OPAQUE),
854+
),
855+
_ => FoundOpaqueSpecialization::NotFound,
856+
},
857+
Some(member_impl) => match member_impl {
858+
MemberImpl::Impl(spec_symbol) => {
859+
let specialization =
860+
abilities_store.specialization_info(*spec_symbol).expect("expected custom implementations to always have complete specialization info by this point");
861+
862+
let specialized_lambda_set = *specialization
863+
.specialization_lambda_sets
864+
.get(&lset_region)
865+
.unwrap_or_else(|| {
866+
panic!(
867+
"lambda set region not resolved: {:?}",
868+
(spec_symbol, specialization)
869+
)
870+
});
871+
872+
FoundOpaqueSpecialization::AmbientFunction(specialized_lambda_set)
873+
}
874+
MemberImpl::Error => todo_abilities!(),
875+
},
876+
}
877+
}

crates/compiler/test_gen/src/gen_abilities.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2288,4 +2288,42 @@ mod inspect {
22882288
RocStr
22892289
);
22902290
}
2291+
2292+
#[test]
2293+
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
2294+
fn opaque_automatic() {
2295+
assert_evals_to!(
2296+
indoc!(
2297+
r#"
2298+
app "test" provides [main] to "./platform"
2299+
2300+
Op := {}
2301+
2302+
main = Inspect.toDbgStr (Inspect.inspect (@Op {}))
2303+
"#
2304+
),
2305+
RocStr::from(r#"<opaque>"#),
2306+
RocStr
2307+
);
2308+
}
2309+
2310+
#[test]
2311+
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
2312+
fn opaque_automatic_with_polymorphic_call() {
2313+
assert_evals_to!(
2314+
indoc!(
2315+
r#"
2316+
app "test" provides [main] to "./platform"
2317+
2318+
Op := {}
2319+
2320+
late = \a -> Inspect.toDbgStr (Inspect.inspect a)
2321+
2322+
main = late (@Op {})
2323+
"#
2324+
),
2325+
RocStr::from(r#"<opaque>"#),
2326+
RocStr
2327+
);
2328+
}
22912329
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# +emit:mono
2+
app "test" provides [main] to "./platform"
3+
4+
Op := {}
5+
6+
main =
7+
dbg (@Op {})
8+
1
9+
10+
# -emit:mono
11+
procedure Inspect.251 (Inspect.252):
12+
let Inspect.317 : Str = "<opaque>";
13+
let Inspect.316 : Str = CallByName Inspect.61 Inspect.252 Inspect.317;
14+
ret Inspect.316;
15+
16+
procedure Inspect.30 (Inspect.147):
17+
ret Inspect.147;
18+
19+
procedure Inspect.35 (Inspect.300):
20+
ret Inspect.300;
21+
22+
procedure Inspect.36 (Inspect.304):
23+
let Inspect.311 : Str = "";
24+
ret Inspect.311;
25+
26+
procedure Inspect.45 (Inspect.302):
27+
let Inspect.314 : {} = Struct {};
28+
let Inspect.313 : {} = CallByName Inspect.30 Inspect.314;
29+
ret Inspect.313;
30+
31+
procedure Inspect.5 (Inspect.150):
32+
let Inspect.312 : {} = CallByName Inspect.45 Inspect.150;
33+
let Inspect.309 : {} = Struct {};
34+
let Inspect.308 : Str = CallByName Inspect.36 Inspect.309;
35+
let Inspect.307 : Str = CallByName Inspect.251 Inspect.308;
36+
ret Inspect.307;
37+
38+
procedure Inspect.61 (Inspect.303, Inspect.298):
39+
let Inspect.319 : Str = CallByName Str.3 Inspect.303 Inspect.298;
40+
dec Inspect.298;
41+
ret Inspect.319;
42+
43+
procedure Str.3 (#Attr.2, #Attr.3):
44+
let Str.292 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
45+
ret Str.292;
46+
47+
procedure Test.0 ():
48+
let Test.5 : {} = Struct {};
49+
let Test.4 : Str = CallByName Inspect.5 Test.5;
50+
let Test.2 : Str = CallByName Inspect.35 Test.4;
51+
dbg Test.2;
52+
dec Test.2;
53+
let Test.3 : I64 = 1i64;
54+
ret Test.3;

0 commit comments

Comments
 (0)