-
Notifications
You must be signed in to change notification settings - Fork 90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Construct external Rust types with named fields #589
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice, thanks for starting the work towards external struct construction!
Everything looks pretty good to me, apart that we need to deal with re-ordering. Since we want to maintain the evaluation order we unfortunately can't just re-order field value construction (which would've been nice). So we'll have to cope by swapping or copying values around for now.
I have some thoughts about changing the underlying instructions so that each has a target address they're being assembled into. It would simplify construction but is further down the line.
3698d71
to
4fde80e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change looks nice, there's possibly a stack offset issue during assembly I'd like you to test for and possibly fix.
cx.asm.push( | ||
Inst::Swap { | ||
a: stack_position, | ||
b: desired_position, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these will have to be offset from the base of stack construction since the stack might contain other data before this point? That is, based of cx.scopes.total(&span)
before the assignments above are constructed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was going to ask you about this. Currently this is using stack_bottom.checked_add
to ensure the position is inside the call frame. But a lot of your stack operations are relative to the top, so I wasn't sure if you preferred Inst::Swap
to also be relative to the top for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relative to the stack frame seems fine and easy to reason about. Unless you can come up with some other reason.
examples/examples/external_struct.rs
Outdated
|
||
let mut sources = rune::sources! { | ||
entry => { | ||
pub fn main() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add some padding in this test, like a few variable declarations before you construct the struct and it should produce the error which requires adding a stack offset (see above).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was planning to add a true test (or tests) to exercise the new functionality, and leave the example focused on demonstrating the basics. Will push something up momentarily.
|
||
let output = vm.call(["main"], ())?; | ||
let output: External = rune::from_value(output)?; | ||
println!("{:?}", output); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add an assert_eq!
here which checks that the constructed object has the expected values.
crates/rune/src/runtime/stack.rs
Outdated
let a = self.stack.len().checked_sub(a).ok_or(StackError)?; | ||
let b = self.stack.len().checked_sub(b).ok_or(StackError)?; | ||
self.stack.swap(a, b); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is using offsets from the top of the stack now. Should it check that the computed offsets don't exceed stack_bottom
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's necessary to ensure stack isolation between function calls, which could otherwise pose a security risk in case there are any miscompilations. Note that you get that for free if you checked_add
from stack_bottom
instead (relative to the stack frame), all though you still need to check that neither exceeds the size of the stack to avoid the panic when out of bounds during slice::swap
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok, I misunderstood your previous comment. Unfortunately I'm out of pocket the next few days. If you have time, feel free to push a fix and merge everything. Thanks for all the feedback!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks for your work and giving me the heads up!
@@ -1976,6 +1976,10 @@ fn expr_object<'hir>( | |||
hir::ExprObjectKind::StructVariant { hash } => { | |||
cx.asm.push(Inst::StructVariant { hash, slot }, span); | |||
} | |||
hir::ExprObjectKind::Constructor { hash, args } => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a great name, IMO. Any suggestions? Maybe hir::ExprObjectKind::ExternalType
, or something along those lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Including "External" seems good, so 👍. Not important to be perfect since it's an internal variant and can easily be renamed.
This pull request allows Rune scripts to construct Rust types with named fields, like so:
I've tested this with structs and it works as expected. It should also work with enum variants that have named fields, though I haven't tested that yet.
It does this by creating a closure during the
Any
macro expansion, and installing that as a constructor function associated with the type:By invoking the constructor function, the VM is placing the
External
type onto the stack as aValue::Any
. This means you can successfully extract the typed object using theFromValue
trait:Right now, this relies on the caller to assign the struct fields in the correct order. Otherwise the call to the closure fails due to mismatched types. There are probably a few ways to fix that, including making the closure take a single
Object
argument and extracting the fields from it.This probably also doesn't work with nested structs or generics, at least for the moment.
Fixes #400.