Skip to content

Commit 958027c

Browse files
authored
Add map support (#1562)
* feat: add map support across backends Implement map type rendering plus lowering/lifting/deallocation support across the C, C++, C#, Go, MoonBit, and Markdown backends, and add map codegen/runtime tests. This aligns non-Rust generators with core map ABI support and fixes the Go test harness module replacement path needed for map codegen verification. Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com> * revert: remove map support from non-Rust backends Defer map codegen for MoonBit, Go, C#, C++, and C to future PRs that include runtime tests and review from language-specific maintainers. Only todo!() stubs for the new MapLower/MapLift/ IterMapKey/IterMapValue/GuestDeallocateMap instruction variants are kept so exhaustive matches compile. * revert: drop unrelated go.mod replace path fix This change was a fix for a latent bug from the vanity imports migration but is unrelated to map support. Removing to keep the PR focused on Rust. * refactor: clean up map type support across backends - Make `InterfaceGenerator::type_map` a required trait method instead of providing a default `todo!()` impl, so missing implementations are caught at compile time rather than runtime. - Remove `RuntimeItem::MapType` indirection in the Rust backend; reference `{rt}::Map` directly instead of emitting a `pub use` re-export. - Fix borrowed map rendering to use `&Map<K, V>` instead of the incorrect `&[(K, V)]` slice syntax that was copy-pasted from lists. - Add explanatory comments on map ABI methods that reuse list read/write helpers (maps share the list<tuple<K, V>> memory layout). - Add explicit `type_map` todo stubs to C, C++, C#, Go, and MoonBit backends. * Address PR review: make anonymous_type_map required and fix runner test - Make `anonymous_type_map` a required method on `AnonymousTypeGenerator` trait (no default impl), consistent with all other methods in the trait. - Add explicit `anonymous_type_map` todo!() implementation to C backend. - Fix map runner test to construct proper Map types instead of passing slice literals, matching the generated `&Map<K, V>` parameter signatures. * Mark map.wit codegen test as expected failure for unsupported backends The map.wit codegen test hits todo!() panics in backends that don't yet implement map support (C, C++, C#, Go, MoonBit). Add map.wit to each backend's should_fail_verify so CI treats these as expected failures. * Fix rustfmt and use starts_with for C map.wit codegen variants - Run cargo fmt to fix formatting in c/lib.rs, test/c.rs, test/moonbit.rs. - Use starts_with("map.wit") for the C backend's should_fail_verify to catch all codegen test variants (no-sig-flattening, autodrop, async). * Add comment back Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com> * Expand map test coverage with additional runtime and codegen tests Add runtime tests for empty maps, option values, maps inside records, inline anonymous map types, and a 100-entry stress test. Expand codegen tests with option values, nested maps, record values, bool keys, and char keys. * Add map tests for nested maps, multi-param, variant, and result types Exercise additional ABI code paths: nested map<K, map<K2, V>> with recursive lower/lift blocks, multiple map parameters with independent ptr/len pairs, map as a variant arm payload, map inside result<ok, err>, and tuple-with-map in codegen. * Add tuple-with-map and single-entry map runtime tests Exercise map inside an anonymous tuple (filter_mode_preserve_top path) and the single-entry boundary case. * Add WitMap trait and configurable map_type for Rust bindings Instead of hardcoding HashMap/BTreeMap, introduce a WitMap trait that generated code delegates to for map construction, insertion, and length. This lets users swap in their own map type via the new `map_type` bindgen option. The default is BTreeMap (always, regardless of std). * Format code Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com> * Add tests for WitMap trait and map_type codegen option - Unit tests for WitMap trait impls (BTreeMap, HashMap, reference blanket) - Proc-macro integration tests exercising map_type with HashMap and default - Codegen test variant running all .wit files with --map-type=HashMap * Fix &&BTreeMap WitMap trait resolution for borrowed map variants Remove `type Iter` and `wit_map_into_iter` from the `WitMap` trait and drop the `IntoIterator` bound from the blanket `&T` impl. This allows the blanket impl to apply at any reference depth (e.g. `&&BTreeMap`) which is needed when codegen emits `WitMap::wit_map_len(&map)` on an already-borrowed map. Generated lowering code now uses `IntoIterator::into_iter(map)` for iteration, which the standard library already provides for both owned and borrowed map types. * Fix map codegen for borrowed ownership mode Use method syntax (.wit_map_len() and .into_iter()) in generated MapLower code instead of UFCS. This lets Rust's auto-deref handle &&BTreeMap operands that arise when the borrow-mode param wrapper adds an extra & prefix to already-borrowed map arguments. A scoped `use WitMap;` import is emitted so method resolution finds the trait's wit_map_len method. * Use .len() and .into_iter() in MapLower codegen Revert MapLower to use method syntax for length and iteration, matching the original pre-WitMap code. Method syntax auto-derefs through &&Map operands that arise in borrowed ownership modes. Simplify WitMap trait to just new/push (used only in MapLift). The map type must also provide .len() and implement IntoIterator, which all standard map types already do. * Route MapLower .len() through WitMap trait via module-level import Instead of calling .len() directly (which bypasses the WitMap trait and would break custom map types), generated code now calls .wit_map_len() using method syntax. The WitMap trait is brought into scope via a module-level `use _rt::WitMap;` import, avoiding the scoping issues that arose from emitting `use` inside nested blocks. * Format code --------- Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent 81ce0de commit 958027c

File tree

26 files changed

+942
-19
lines changed

26 files changed

+942
-19
lines changed

crates/c/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,6 +1746,10 @@ void __wasm_export_{ns}_{snake}_dtor({ns}_{snake}_t* arg) {{
17461746
todo!("named fixed-length list types are not yet supported in the C backend")
17471747
}
17481748

1749+
fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) {
1750+
todo!("map types are not yet supported in the C backend")
1751+
}
1752+
17491753
fn type_future(&mut self, id: TypeId, _name: &str, _ty: &Option<Type>, docs: &Docs) {
17501754
self.src.h_defs("\n");
17511755
self.docs(docs, SourceType::HDefs);
@@ -1867,6 +1871,10 @@ impl<'a> wit_bindgen_core::AnonymousTypeGenerator<'a> for InterfaceGenerator<'a>
18671871
) {
18681872
todo!("print_anonymous_type for fixed length list");
18691873
}
1874+
1875+
fn anonymous_type_map(&mut self, _id: TypeId, _key: &Type, _value: &Type, _docs: &Docs) {
1876+
todo!("anonymous map types are not yet supported in the C backend");
1877+
}
18701878
}
18711879

18721880
pub enum CTypeNameInfo<'a> {

crates/core/src/abi.rs

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,28 @@ def_instruction! {
310310
ty: TypeId,
311311
} : [2] => [1],
312312

313+
/// Lowers a map into a canonical pointer/length pair.
314+
///
315+
/// This operation pops a map value from the stack and pushes pointer
316+
/// and length. A block is popped from the block stack to lower one
317+
/// key/value entry into linear memory.
318+
MapLower {
319+
key: &'a Type,
320+
value: &'a Type,
321+
realloc: Option<&'a str>,
322+
} : [1] => [2],
323+
324+
/// Lifts a canonical pointer/length pair into a map.
325+
///
326+
/// This operation consumes pointer and length from the stack. A block
327+
/// is popped from the block stack and must produce key/value for one
328+
/// map entry.
329+
MapLift {
330+
key: &'a Type,
331+
value: &'a Type,
332+
ty: TypeId,
333+
} : [2] => [1],
334+
313335
/// Pops all fields for a fixed list off the stack and then composes them
314336
/// into an array.
315337
FixedLengthListLift {
@@ -349,6 +371,14 @@ def_instruction! {
349371
/// This is only used inside of blocks related to lowering lists.
350372
IterElem { element: &'a Type } : [0] => [1],
351373

374+
/// Pushes an operand onto the stack representing the current map key
375+
/// for each map iteration.
376+
IterMapKey { key: &'a Type } : [0] => [1],
377+
378+
/// Pushes an operand onto the stack representing the current map value
379+
/// for each map iteration.
380+
IterMapValue { value: &'a Type } : [0] => [1],
381+
352382
/// Pushes an operand onto the stack representing the base pointer of
353383
/// the next element in a list.
354384
///
@@ -581,6 +611,17 @@ def_instruction! {
581611
element: &'a Type,
582612
} : [2] => [0],
583613

614+
/// Used exclusively for guest-code generation this indicates that a
615+
/// map is being deallocated. The ptr/length are on the stack and are
616+
/// popped off and used to deallocate the map entry buffer.
617+
///
618+
/// This variant also pops a block off the block stack to be used as
619+
/// the body of the deallocation loop over map entries.
620+
GuestDeallocateMap {
621+
key: &'a Type,
622+
value: &'a Type,
623+
} : [2] => [0],
624+
584625
/// Used exclusively for guest-code generation this indicates that
585626
/// a variant is being deallocated. The integer discriminant is popped
586627
/// off the stack as well as `blocks` number of blocks popped from the
@@ -875,7 +916,7 @@ fn needs_deallocate(resolve: &Resolve, ty: &Type, what: Deallocate) -> bool {
875916
TypeDefKind::Future(_) | TypeDefKind::Stream(_) => what.handles(),
876917
TypeDefKind::Unknown => unreachable!(),
877918
TypeDefKind::FixedLengthList(t, _) => needs_deallocate(resolve, t, what),
878-
TypeDefKind::Map(..) => todo!(),
919+
TypeDefKind::Map(_, _) => true,
879920
},
880921

881922
Type::Bool
@@ -1618,7 +1659,25 @@ impl<'a, B: Bindgen> Generator<'a, B> {
16181659
self.lower(ty);
16191660
}
16201661
}
1621-
TypeDefKind::Map(..) => todo!(),
1662+
TypeDefKind::Map(key, value) => {
1663+
let realloc = self.list_realloc();
1664+
let value_offset = self.bindgen.sizes().field_offsets([key, value])[1].0;
1665+
self.push_block();
1666+
self.emit(&IterMapKey { key });
1667+
self.emit(&IterBasePointer);
1668+
let key_addr = self.stack.pop().unwrap();
1669+
self.write_to_memory(key, key_addr, Default::default());
1670+
self.emit(&IterMapValue { value });
1671+
self.emit(&IterBasePointer);
1672+
let value_addr = self.stack.pop().unwrap();
1673+
self.write_to_memory(value, value_addr, value_offset);
1674+
self.finish_block(0);
1675+
self.emit(&MapLower {
1676+
key,
1677+
value,
1678+
realloc,
1679+
});
1680+
}
16221681
},
16231682
}
16241683
}
@@ -1819,7 +1878,16 @@ impl<'a, B: Bindgen> Generator<'a, B> {
18191878
id,
18201879
});
18211880
}
1822-
TypeDefKind::Map(..) => todo!(),
1881+
TypeDefKind::Map(key, value) => {
1882+
let value_offset = self.bindgen.sizes().field_offsets([key, value])[1].0;
1883+
self.push_block();
1884+
self.emit(&IterBasePointer);
1885+
let entry_addr = self.stack.pop().unwrap();
1886+
self.read_from_memory(key, entry_addr.clone(), Default::default());
1887+
self.read_from_memory(value, entry_addr, value_offset);
1888+
self.finish_block(2);
1889+
self.emit(&MapLift { key, value, ty: id });
1890+
}
18231891
},
18241892
}
18251893
}
@@ -1907,6 +1975,8 @@ impl<'a, B: Bindgen> Generator<'a, B> {
19071975
Type::Id(id) => match &self.resolve.types[id].kind {
19081976
TypeDefKind::Type(t) => self.write_to_memory(t, addr, offset),
19091977
TypeDefKind::List(_) => self.write_list_to_memory(ty, addr, offset),
1978+
// Maps have the same linear memory layout as list<tuple<K, V>>.
1979+
TypeDefKind::Map(_, _) => self.write_list_to_memory(ty, addr, offset),
19101980

19111981
TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::Handle(_) => {
19121982
self.lower_and_emit(ty, addr, &I32Store { offset })
@@ -2016,7 +2086,6 @@ impl<'a, B: Bindgen> Generator<'a, B> {
20162086
id,
20172087
});
20182088
}
2019-
TypeDefKind::Map(..) => todo!(),
20202089
},
20212090
}
20222091
}
@@ -2115,6 +2184,8 @@ impl<'a, B: Bindgen> Generator<'a, B> {
21152184
TypeDefKind::Type(t) => self.read_from_memory(t, addr, offset),
21162185

21172186
TypeDefKind::List(_) => self.read_list_from_memory(ty, addr, offset),
2187+
// Maps have the same linear memory layout as list<tuple<K, V>>.
2188+
TypeDefKind::Map(_, _) => self.read_list_from_memory(ty, addr, offset),
21182189

21192190
TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::Handle(_) => {
21202191
self.emit_and_lift(ty, addr, &I32Load { offset })
@@ -2216,7 +2287,6 @@ impl<'a, B: Bindgen> Generator<'a, B> {
22162287
id,
22172288
});
22182289
}
2219-
TypeDefKind::Map(..) => todo!(),
22202290
},
22212291
}
22222292
}
@@ -2339,6 +2409,18 @@ impl<'a, B: Bindgen> Generator<'a, B> {
23392409
self.emit(&Instruction::GuestDeallocateList { element });
23402410
}
23412411

2412+
TypeDefKind::Map(key, value) => {
2413+
let value_offset = self.bindgen.sizes().field_offsets([key, value])[1].0;
2414+
self.push_block();
2415+
self.emit(&IterBasePointer);
2416+
let entry_addr = self.stack.pop().unwrap();
2417+
self.deallocate_indirect(key, entry_addr.clone(), Default::default(), what);
2418+
self.deallocate_indirect(value, entry_addr, value_offset, what);
2419+
self.finish_block(0);
2420+
2421+
self.emit(&Instruction::GuestDeallocateMap { key, value });
2422+
}
2423+
23422424
TypeDefKind::Handle(Handle::Own(_))
23432425
| TypeDefKind::Future(_)
23442426
| TypeDefKind::Stream(_)
@@ -2405,7 +2487,6 @@ impl<'a, B: Bindgen> Generator<'a, B> {
24052487
TypeDefKind::Unknown => unreachable!(),
24062488

24072489
TypeDefKind::FixedLengthList(..) => todo!(),
2408-
TypeDefKind::Map(..) => todo!(),
24092490
},
24102491
}
24112492
}
@@ -2464,6 +2545,17 @@ impl<'a, B: Bindgen> Generator<'a, B> {
24642545
self.deallocate(ty, what);
24652546
}
24662547

2548+
TypeDefKind::Map(_, _) => {
2549+
self.stack.push(addr.clone());
2550+
self.emit(&Instruction::PointerLoad { offset });
2551+
self.stack.push(addr);
2552+
self.emit(&Instruction::LengthLoad {
2553+
offset: offset + self.bindgen.sizes().align(ty).into(),
2554+
});
2555+
2556+
self.deallocate(ty, what);
2557+
}
2558+
24672559
TypeDefKind::Handle(Handle::Own(_))
24682560
| TypeDefKind::Future(_)
24692561
| TypeDefKind::Stream(_)
@@ -2527,7 +2619,6 @@ impl<'a, B: Bindgen> Generator<'a, B> {
25272619
TypeDefKind::Stream(_) => unreachable!(),
25282620
TypeDefKind::Unknown => unreachable!(),
25292621
TypeDefKind::FixedLengthList(_, _) => {}
2530-
TypeDefKind::Map(..) => todo!(),
25312622
},
25322623
}
25332624
}

crates/core/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ pub trait InterfaceGenerator<'a> {
166166
fn type_alias(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs);
167167
fn type_list(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs);
168168
fn type_fixed_length_list(&mut self, id: TypeId, name: &str, ty: &Type, size: u32, docs: &Docs);
169+
fn type_map(&mut self, id: TypeId, name: &str, key: &Type, value: &Type, docs: &Docs);
169170
fn type_builtin(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs);
170171
fn type_future(&mut self, id: TypeId, name: &str, ty: &Option<Type>, docs: &Docs);
171172
fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option<Type>, docs: &Docs);
@@ -203,7 +204,7 @@ where
203204
TypeDefKind::FixedLengthList(t, size) => {
204205
generator.type_fixed_length_list(id, name, t, *size, &ty.docs)
205206
}
206-
TypeDefKind::Map(..) => todo!(),
207+
TypeDefKind::Map(key, value) => generator.type_map(id, name, key, value, &ty.docs),
207208
TypeDefKind::Unknown => unreachable!(),
208209
}
209210
}
@@ -217,6 +218,7 @@ pub trait AnonymousTypeGenerator<'a> {
217218
fn anonymous_type_result(&mut self, id: TypeId, ty: &Result_, docs: &Docs);
218219
fn anonymous_type_list(&mut self, id: TypeId, ty: &Type, docs: &Docs);
219220
fn anonymous_type_fixed_length_list(&mut self, id: TypeId, ty: &Type, size: u32, docs: &Docs);
221+
fn anonymous_type_map(&mut self, id: TypeId, key: &Type, value: &Type, docs: &Docs);
220222
fn anonymous_type_future(&mut self, id: TypeId, ty: &Option<Type>, docs: &Docs);
221223
fn anonymous_type_stream(&mut self, id: TypeId, ty: &Option<Type>, docs: &Docs);
222224
fn anonymous_type_type(&mut self, id: TypeId, ty: &Type, docs: &Docs);
@@ -242,7 +244,7 @@ pub trait AnonymousTypeGenerator<'a> {
242244
TypeDefKind::FixedLengthList(t, size) => {
243245
self.anonymous_type_fixed_length_list(id, t, *size, &ty.docs)
244246
}
245-
TypeDefKind::Map(..) => todo!(),
247+
TypeDefKind::Map(key, value) => self.anonymous_type_map(id, key, value, &ty.docs),
246248
TypeDefKind::Unknown => unreachable!(),
247249
}
248250
}

crates/core/src/types.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,11 @@ impl Types {
261261
TypeDefKind::FixedLengthList(ty, _) => {
262262
info = self.type_info(resolve, ty);
263263
}
264-
TypeDefKind::Map(..) => todo!(),
264+
TypeDefKind::Map(key, value) => {
265+
info = self.type_info(resolve, key);
266+
info |= self.type_info(resolve, value);
267+
info.has_list = true;
268+
}
265269
TypeDefKind::Unknown => unreachable!(),
266270
}
267271
let prev = self.type_info.insert(ty, info);

crates/cpp/src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2232,6 +2232,17 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a>
22322232
todo!("named fixed-length list types are not yet supported in the C++ backend")
22332233
}
22342234

2235+
fn type_map(
2236+
&mut self,
2237+
_id: TypeId,
2238+
_name: &str,
2239+
_key: &wit_bindgen_core::wit_parser::Type,
2240+
_value: &wit_bindgen_core::wit_parser::Type,
2241+
_docs: &wit_bindgen_core::wit_parser::Docs,
2242+
) {
2243+
todo!("map types are not yet supported in the C++ backend")
2244+
}
2245+
22352246
fn type_builtin(
22362247
&mut self,
22372248
_id: TypeId,
@@ -3503,6 +3514,13 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> {
35033514
}
35043515
abi::Instruction::AsyncTaskReturn { .. } => todo!(),
35053516
abi::Instruction::DropHandle { .. } => todo!(),
3517+
abi::Instruction::MapLower { .. }
3518+
| abi::Instruction::MapLift { .. }
3519+
| abi::Instruction::IterMapKey { .. }
3520+
| abi::Instruction::IterMapValue { .. }
3521+
| abi::Instruction::GuestDeallocateMap { .. } => {
3522+
todo!("map types are not yet supported in this backend")
3523+
}
35063524
}
35073525
}
35083526

crates/csharp/src/function.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,6 +1468,11 @@ impl Bindgen for FunctionBindgen<'_, '_> {
14681468
| Instruction::FixedLengthListLower { .. }
14691469
| Instruction::FixedLengthListLowerToMemory { .. }
14701470
| Instruction::FixedLengthListLiftFromMemory { .. }
1471+
| Instruction::MapLower { .. }
1472+
| Instruction::MapLift { .. }
1473+
| Instruction::IterMapKey { .. }
1474+
| Instruction::IterMapValue { .. }
1475+
| Instruction::GuestDeallocateMap { .. }
14711476
=> {
14721477
dbg!(inst);
14731478
todo!()

crates/csharp/src/interface.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1749,6 +1749,10 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> {
17491749
todo!("named fixed-length list types are not yet supported in the C# backend")
17501750
}
17511751

1752+
fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) {
1753+
todo!("map types are not yet supported in the C# backend")
1754+
}
1755+
17521756
fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) {
17531757
unimplemented!();
17541758
}

crates/go/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2980,6 +2980,10 @@ const (
29802980
uwriteln!(self.src, "{docs}type {name} = [{size}]{ty}");
29812981
}
29822982

2983+
fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) {
2984+
todo!("map types are not yet supported in the Go backend")
2985+
}
2986+
29832987
fn type_builtin(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) {
29842988
_ = (id, name, ty, docs);
29852989
todo!()

crates/guest-rust/macro/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ impl Parse for Config {
111111
Opt::Ownership(ownership) => opts.ownership = ownership,
112112
Opt::Skip(list) => opts.skip.extend(list.iter().map(|i| i.value())),
113113
Opt::RuntimePath(path) => opts.runtime_path = Some(path.value()),
114+
Opt::MapType(path) => opts.map_type = Some(path.value()),
114115
Opt::BitflagsPath(path) => opts.bitflags_path = Some(path.value()),
115116
Opt::Stubs => {
116117
opts.stubs = true;
@@ -304,6 +305,7 @@ mod kw {
304305
syn::custom_keyword!(inline);
305306
syn::custom_keyword!(ownership);
306307
syn::custom_keyword!(runtime_path);
308+
syn::custom_keyword!(map_type);
307309
syn::custom_keyword!(bitflags_path);
308310
syn::custom_keyword!(exports);
309311
syn::custom_keyword!(stubs);
@@ -383,6 +385,7 @@ enum Opt {
383385
Skip(Vec<syn::LitStr>),
384386
Ownership(Ownership),
385387
RuntimePath(syn::LitStr),
388+
MapType(syn::LitStr),
386389
BitflagsPath(syn::LitStr),
387390
Stubs,
388391
ExportPrefix(syn::LitStr),
@@ -486,6 +489,10 @@ impl Parse for Opt {
486489
input.parse::<kw::runtime_path>()?;
487490
input.parse::<Token![:]>()?;
488491
Ok(Opt::RuntimePath(input.parse()?))
492+
} else if l.peek(kw::map_type) {
493+
input.parse::<kw::map_type>()?;
494+
input.parse::<Token![:]>()?;
495+
Ok(Opt::MapType(input.parse()?))
489496
} else if l.peek(kw::bitflags_path) {
490497
input.parse::<kw::bitflags_path>()?;
491498
input.parse::<Token![:]>()?;

0 commit comments

Comments
 (0)