I've been working on some glue improvements, specifically having it generate glue for the user-defined effects in hosted
modules - I have a draft PR for this, but it's giving:
thread 'main' panicked at 'unspecialized lambda sets left over during resolution: LambdaSet([] + (<475>FlexAble(fmt, [
Encode.EncoderFormatting
]):Encode.record
:1), ^<473>), UlsOfVar(VecMap { keys: [], values: [] })', crates/compiler/mono/src/layout.rs:1980:17
this is for $ cargo run -- glue crates/glue/src/RustGlue.roc glue-out examples/cli/cli-platform/main.roc
on that branch
I'm assuming this is because of a combination of this change and this line (I added the .chain
)
those variables
have an interaction with lambda sets a bit later on
but I'm guessing maybe they need to be registered in some other way?
Is this coming from when you try to generate the glue for a host-exposed lambda set? If so, this should be fine - it just means the lambda set is polymorphic, like the other polymorphic HELSs. I would probably add a flag to the layout generator to drop those unspecialized lambda sets for glue generation (or always drop them, but if that road is taken, let's add a debug flag that checks against this, since the assert is intended to catch bugs in specialization)
oh hm, I hadn't even thought about where it was coming from
I just tried it on platform-cli
in examples/
- I didn't even look at what it was trying to operate on
but everything in hosted
should need to be monomorphic, right?
(I don't think we have a check for that yet though)
and if so, then shouldn't the lambda sets no longer be polymorphic?
they should be monomorphic in the surface types, but the lambda sets can almost never be monomorphic
gotcha
hence the need for the host-exposed lambda set wrapper functions
one case is if the host-exposed main is something like
mainForHost : {} -> (Int -> Int)
The lambda set of the Int -> Int
is (very likely) only provided per-app, we don't know what lambda set to reference there when generating the glue
Folkert's numbering scheme should generate the right lambda set wrapper
we just need to tell the layout generator that it's okay for them to be unresolved as polymorphic for the purposes of glue
hmmm ok, if I drop it (just disable that debug_assert!
for now), I hit this unreachable!
due to the builtin (!) Alias in question having a name
of Decode.0
and a repr of LayoutRepr::Struct([])
:face_with_raised_eyebrow:
that is Decode.DecodeError
. seems like that pattern match should be expanded?
hm, I don't follow :sweat_smile:
oh as in like:
LayoutRepr::Struct { .. } if *name == Symbol::DECODE_DECODE_ERROR => {
ha, looks like we do have some amount of checking for this :joy:
thread 'main' panicked at 'not yet implemented: TODO give a nice error message for a non-concrete type being passed to the host', crates/glue/src/types.rs:1386:13
guess it's time to improve that error message!
:thinking: I think probably the best way to go about that is to add an extra check after we've just solved all the types for a hosted
module, while we still have all the decls
in scope: traverse each of them, and if there are any unbound type variables in them, change the Decl
's type to Erroneous
and report it
hm, ok I added a text fixture with a couple of effects: https://github.com/roc-lang/roc/blob/acb1a5ac04077c4abdffb203bb79b8c8ccd393f6/crates/glue/tests/fixtures/basic-effects/Effect.roc
now I'm running into this, though:
interestingly, it's only for the getLine : Effect Str
though
if I delete that one, putLine : Str -> Effect {}
works
I would have guessed that it would be for the one with the actual function type, but I guess it's the wrapper?
what is the backtrace? I guess that could happen if you’re asking to generate a RawFunctionLayout for a host exposed lambda sets
update: putLine : Str -> Effect {}
works completes, but generates the wrong type :laughing:
this is interesting...the Content
of the type when I write it down is:
[crates/compiler/load_internal/src/file.rs:2436] symbol =
Effect.putLine
[crates/compiler/load_internal/src/file.rs:2473] f = "Func([<1792>Apply(Str.Str
, []),], <261=1801>LambdaSet([Effect.putLine
, ], ^<1800>), <1793>Opaque(Effect.Effect
, [257, 256], <259>Func([<1>EmptyRecord,], <249=69>LambdaSet([Effect.IdentId(20)
<244>Apply(Str.Str
, []) , ], ^<1796>), <257>EmptyRecord)))"
that looks correct, because the signature in Effect.roc
is:
putLine : Str -> Effect {}
however, the generated glue is:
HostedFn {
name: "putLine",
arg_types: &[],
ret_type: R1,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, )]
#[repr(C)]
pub struct R1 {
pub rest: roc_std::RocList<u8>,
pub result: roc_std::RocResult<f64, ()>,
}
so it thinks it's a thunk whose return value is what we generate for Decode
on a record or tuple
ok, the plot thickens:
Content
in solved_subs
, it's the above Func
as expectedVariable
(and Symbol
) later on in env.subs
in glue
, it's a Record
instead of a Func
:face_with_raised_eyebrow:so one guess at what's happening:
Symbol
that's being recorded is global, so it's correctVariable
that's being recorded is relative to the Effect
module (and that module's Subs
), whereas when I go to look it up later in env.subs
, it's a different Subs
(the one for the root module being used in glue
), so the Variable
is just referring to something totally differentif that is indeed what's happening, I guess a potential solution is to store the Symbol
only and see if I can look up the appropriate Variable
to use in the root module later?
hm, but I don't have an easy way in glue to go from Symbol
to Variable
in the root module :thinking:
like we have exposed_vars_by_symbol: Vec<(Symbol, Variable)>
but these aren't exposed (at least not to the end user)
but it must be possible to look these up somehow, because mono has to do it in order to generate the externs for linking
like those need both the symbol and the appropriate Variable
(or at least the solved type) at some point
anyone know how that works and how we might do the same thing for glue?
why is the solved_subs different from the env.subs in glue?
it's solved_subs
for the hosted
module
actually wait, is that true? :thinking:
or is solved_subs
always for the root module
yeah no it's for the hosted
module
each module gets its own subs (and eventually a solved subs), so it depends where it's being referenced
I'm printing it out for Msg::SolvedTypes
so yeah it's the per-module one
got it. why is glue not using that subs too then? I would assume you want to generate glue from the hosted module?
yeah so the algorithm I'm using right now (which has the aforementioned issue) is:
hosted
module, we add its ModuleId
to state.hosted_ids
for later (this part is fine)Msg::SolvedTypes
for that module, we add all of the symbols in its decls
to state.hosted_vars_by_symbol: MutMap<Symbol, Variable>
- this is where we add the correct Symbol
, but the Variable
we have access to at this point is for the hosted
module's Subs
and not the root module's Subs
state.hosted_vars_by_symbol
makes it into LoadedModule
, which glue
then uses - but although it has the correct Symbol
s, it has the wrong Variable
si see. I would add another subs, let’s call it H for now, alongside this hosted_vars_by_symbol object, and export the module’s variable from the module subs into H, and then work on H in glue
cool, I like it! :thumbs_up:
yeah but when would I call that? :sweat_smile:
can you do it during the time you add the mapping into state.hosted_vars_by_symbol?
I think that would cause a race condition because root_subs: Option<Subs>
might not be set yet
but actually it turns out module_cache
has solved_subs
for each module
yeah
ok I think things will go best if I use export_variable_to
- do I understand correctly that the returned CopiedImport
's variable
field is the new Variable
in the other Subs
's variable space?
yes
oh, it's on StorageSubs
- I haven't worked with that before, what's the difference between it and Subs
?
they are basically the same, StorageSubs is a wrapper of Subs IIRC. it just has some extra methods bc it’s intended for cross-module storage
ok, module_cache.typechecked
entries get removed from the cache too soon for my purposes
one potentially interesting option I just thought of: if root_subs: Option<Subs>
is None
when I want to add the hosted variables to it, just initialize it to empty and copy in the variables
then later, when starting to work on the root module, use that as the starting point if it's set
that way, whichever one gets to it first doesn't matter
at that point might as well drop the Option
I suppose, and always initialize it
ok, got all that working, but now this is blowing up because it has a return type of a FlexVar somehow :thinking:
Adding function type ret_type_id for RocFunction_1540...
[crates/glue/src/types.rs:1323] &ret_layout = InLayout(VOID)
[crates/glue/src/types.rs:1324] types.get_type(lambda_set_type_id) = Unsized
[crates/glue/src/types.rs:1360] subs.get_content_without_compacting(var) = FlexVar(
None,
)
thread 'main' panicked at 'not yet implemented: TODO give a nice error message for a non-concrete type being passed to the host', crates/glue/src/types.rs:1365:13
Last updated: Jul 06 2025 at 12:14 UTC