Skip to content

Commit 5167d9e

Browse files
authored
Merge pull request #397 from bytecodealliance/components-more-types
Support component `enum`, `variant`, `flags` types
2 parents 105c942 + 656f741 commit 5167d9e

File tree

4 files changed

+156
-11
lines changed

4 files changed

+156
-11
lines changed

ext/src/ruby_api/component/convert.rs

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,22 @@ use crate::{define_rb_intern, err, error, not_implemented};
55
use magnus::exception::type_error;
66
use magnus::rb_sys::AsRawValue;
77
use magnus::value::{IntoId, Lazy, ReprValue};
8-
use magnus::{prelude::*, value, Error, IntoValue, RArray, RClass, RHash, RString, Ruby, Value};
8+
use magnus::{
9+
prelude::*, try_convert, value, Error, IntoValue, RArray, RClass, RHash, RString, Ruby, Value,
10+
};
911
use wasmtime::component::{Type, Val};
1012

1113
define_rb_intern!(
14+
// For Component::Result
1215
OK => "ok",
1316
ERROR => "error",
1417
IS_ERROR => "error?",
1518
IS_OK => "ok?",
19+
20+
// For Component::Variant
21+
NEW => "new",
22+
NAME => "name",
23+
VALUE => "value",
1624
);
1725

1826
pub(crate) fn component_val_to_rb(val: Val, _store: &StoreContextValue) -> Result<Value, Error> {
@@ -54,7 +62,18 @@ pub(crate) fn component_val_to_rb(val: Val, _store: &StoreContextValue) -> Resul
5462
}
5563
Ok(array.into_value())
5664
}
57-
Val::Variant(_kind, _val) => not_implemented!("Variant not implemented"),
65+
Val::Variant(kind, val) => {
66+
let ruby = Ruby::get().unwrap();
67+
let payload = match val {
68+
Some(val) => component_val_to_rb(*val, _store)?,
69+
None => ruby.qnil().into_value(),
70+
};
71+
72+
variant_class(&ruby).funcall(
73+
NEW.into_id_with(&ruby),
74+
(kind.into_value_with(&ruby), payload),
75+
)
76+
}
5877
Val::Enum(kind) => Ok(kind.as_str().into_value()),
5978
Val::Option(val) => match val {
6079
Some(val) => Ok(component_val_to_rb(*val, _store)?),
@@ -72,7 +91,7 @@ pub(crate) fn component_val_to_rb(val: Val, _store: &StoreContextValue) -> Resul
7291
};
7392
result_class(&ruby).funcall(ruby_method, (ruby_argument,))
7493
}
75-
Val::Flags(_vec) => not_implemented!("Flags not implemented"),
94+
Val::Flags(vec) => Ok(vec.into_value()),
7695
Val::Resource(_resource_any) => not_implemented!("Resource not implemented"),
7796
}
7897
}
@@ -168,8 +187,46 @@ pub(crate) fn rb_to_component_val(
168187

169188
Ok(Val::Tuple(vals))
170189
}
171-
Type::Variant(_variant) => not_implemented!("Variant not implemented"),
172-
Type::Enum(_enum) => not_implemented!("Enum not implementend"),
190+
Type::Variant(variant) => {
191+
let ruby = Ruby::get().unwrap();
192+
193+
let name: RString = value.funcall(NAME.into_id_with(&ruby), ())?;
194+
let name = name.to_string()?;
195+
196+
let case = variant
197+
.cases()
198+
.find(|case| case.name == name.as_str())
199+
.ok_or_else(|| {
200+
error!(
201+
"invalid variant case \"{}\", valid cases: {:?}",
202+
name,
203+
RArray::from_iter(variant.cases().map(|c| c.name))
204+
)
205+
})?;
206+
207+
let payload_rb: Value = value.funcall(VALUE.into_id_with(&ruby), ())?;
208+
let payload_val = match (&case.ty, payload_rb.is_nil()) {
209+
(Some(ty), _) => rb_to_component_val(payload_rb, _store, ty)
210+
.map(|val| Some(Box::new(val)))
211+
.map_err(|e| e.append(format!(" (variant value for \"{}\")", &name))),
212+
213+
// case doesn't have payload and Variant#value *is nil*
214+
(None, true) => Ok(None),
215+
216+
// case doesn't have payload and Variant#value *is not nil*
217+
(None, false) => err!(
218+
"expected no value for variant case \"{}\", got {}",
219+
&name,
220+
payload_rb.inspect()
221+
),
222+
}?;
223+
224+
Ok(Val::Variant(name, payload_val))
225+
}
226+
Type::Enum(_) => {
227+
let rstring = RString::try_convert(value)?;
228+
rstring.to_string().map(Val::Enum)
229+
}
173230
Type::Option(option_type) => {
174231
if value.is_nil() {
175232
Ok(Val::Option(None))
@@ -220,7 +277,7 @@ pub(crate) fn rb_to_component_val(
220277
}
221278
}
222279
}
223-
Type::Flags(_flags) => not_implemented!("Flags not implemented"),
280+
Type::Flags(_) => Vec::<String>::try_convert(value).map(Val::Flags),
224281
Type::Own(_resource_type) => not_implemented!("Resource not implemented"),
225282
Type::Borrow(_resource_type) => not_implemented!("Resource not implemented"),
226283
}
@@ -232,6 +289,12 @@ fn result_class(ruby: &Ruby) -> RClass {
232289
ruby.get_inner(&RESULT_CLASS)
233290
}
234291

292+
fn variant_class(ruby: &Ruby) -> RClass {
293+
static VARIANT_CLASS: Lazy<RClass> =
294+
Lazy::new(|ruby| component_namespace(ruby).const_get("Variant").unwrap());
295+
ruby.get_inner(&VARIANT_CLASS)
296+
}
297+
235298
pub fn init(ruby: &Ruby) -> Result<(), Error> {
236299
// Warm up
237300
let _ = result_class(ruby);
@@ -240,5 +303,10 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
240303
let _ = IS_ERROR;
241304
let _ = IS_OK;
242305

306+
let _ = result_class(ruby);
307+
let _ = NEW;
308+
let _ = NAME;
309+
let _ = VALUE;
310+
243311
Ok(())
244312
}

lib/wasmtime/component.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,51 @@ class UncheckedResult < Wasmtime::Error; end
8282
# `.error` is used over `.new`.
8383
private :initialize
8484
end
85+
86+
# Represents a value for component model's variant case.
87+
# A variant case has a name that uniquely identify the case within the
88+
# variant and optionally a value.
89+
#
90+
# @example Constructing variants
91+
# # Given the following variant:
92+
# # variant filter {
93+
# # all,
94+
# # none,
95+
# # lt(u32),
96+
# # }
97+
#
98+
# Variant.new("all")
99+
# Variant.new("none")
100+
# Variant.new("lt", 100)
101+
class Variant
102+
# The name of the variant case
103+
# @return [String]
104+
attr_reader :name
105+
106+
# The optional payload of the variant case
107+
# @return [Object]
108+
attr_reader :value
109+
110+
# @param name [String] the name of variant case
111+
# @param value [Object] the optional payload of the variant case
112+
def initialize(name, value = nil)
113+
@name = name
114+
@value = value
115+
end
116+
117+
def ==(other)
118+
eql?(other)
119+
end
120+
121+
def eql?(other)
122+
self.class == other.class &&
123+
name == other.name &&
124+
value == other.value
125+
end
126+
127+
def hash
128+
[self.class, @name, @value].hash
129+
end
130+
end
85131
end
86132
end

spec/unit/component/convert_spec.rb

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ module Component
2828
["list", [1, 2, 2**32 - 1]], # list<u32>
2929
["record", {"x" => 1, "y" => 2}],
3030
["tuple", [1, "foo"]], # tuple<u32, string>
31-
# TODO variant
32-
# TODO enum
31+
["variant", Variant.new("all"), Variant.new("lt", 12)],
32+
["enum", "l"],
3333
["option", 0, nil], # option<u32>
3434
["result", Result.ok(1), Result.error(2)], # result<u32, u32>
35-
["result-unit", Result.ok(nil), Result.error(nil)]
36-
# TODO flags
35+
["result-unit", Result.ok(nil), Result.error(nil)],
36+
["flags", [], ["read"], ["read", "write", "exec"]]
3737
].each do |type, *values|
3838
values.each do |v|
3939
it "#{type} #{v.inspect}" do
@@ -84,9 +84,15 @@ module Component
8484
["record", {"x" => 1}, /struct field missing: y/],
8585
["record", nil, /no implicit conversion of NilClass into Hash/],
8686
["tuple", nil, /no implicit conversion of NilClass into Array/],
87+
["variant", Variant.new("no"), /invalid variant case "no", valid cases: \["all", "none", "lt"\]/],
88+
["variant", Variant.new("lt", "nah"), /(variant value for "lt")/],
89+
["enum", "no", /enum variant name `no` is not valid/],
8790
["result", nil, /undefined method `ok\?/],
8891
["result-unit", Result.ok(""), /expected nil for result<_, E> ok branch/],
89-
["result-unit", Result.error(""), /expected nil for result<O, _> error branch/]
92+
["result-unit", Result.error(""), /expected nil for result<O, _> error branch/],
93+
["flags", ["no"], /unknown flag: `no`/],
94+
["flags", [1], /no implicit conversion of Integer into String/],
95+
["flags", 1, /no implicit conversion of Integer into Array/]
9096
].each do |type, value, klass, msg|
9197
it "fails on #{type} #{value.inspect}" do
9298
expect { instance.invoke("id-#{type}", value) }.to raise_error(klass, msg)

spec/unit/component/variant_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require "spec_helper"
2+
3+
module Wasmtime
4+
module Component
5+
RSpec.describe Variant do
6+
it "has name" do
7+
expect(Variant.new("a", 1).name).to eq("a")
8+
end
9+
10+
it "has value" do
11+
expect(Variant.new("a", 1).value).to eq(1)
12+
end
13+
14+
it "behaves like a value object" do
15+
expect(Variant.new("a", 1)).to eq(Variant.new("a", 1))
16+
expect(Variant.new("a", 1).hash).to eq(Variant.new("a", 1).hash)
17+
18+
expect(Variant.new("a")).not_to eq(Variant.new("b"))
19+
expect(Variant.new("a", 1)).not_to eq(Variant.new("a", 2))
20+
expect(Variant.new("a").hash).not_to eq(Variant.new("b").hash)
21+
expect(Variant.new("a", 1).hash).not_to eq(Variant.new("a", 2).hash)
22+
end
23+
end
24+
end
25+
end

0 commit comments

Comments
 (0)