Skip to content

Commit a3f8338

Browse files
authored
Merge pull request #51 from Cakefish/better-generic-support
Enable support for top level generic objects
2 parents 96f2776 + 4e33e70 commit a3f8338

8 files changed

Lines changed: 224 additions & 28 deletions

File tree

bauble/src/context.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -419,12 +419,12 @@ impl CtxNode {
419419
/// * If there are multiple entries at the same path.
420420
fn build_type(&mut self, id: TypeId, type_registry: &TypeRegistry) {
421421
let ty = type_registry.key_type(id);
422-
423422
let node = self.build_nodes(ty.meta.path.borrow());
424-
if let Some(ty) = node.reference.ty
425-
&& ty != id
423+
if let Some(ty_id) = node.reference.ty
424+
&& ty_id != id
426425
{
427-
panic!("Multiple types with the same path");
426+
let path = ty.meta.path.borrow();
427+
panic!("Multiple types with the same path: {path}");
428428
}
429429
node.reference.ty = Some(id);
430430
}
@@ -434,6 +434,7 @@ impl CtxNode {
434434
if node.reference.asset.is_some() {
435435
panic!("Multiple types with the same path");
436436
}
437+
437438
node.reference.asset = Some(ty);
438439
}
439440
}

bauble/src/parse/parser.rs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -216,14 +216,30 @@ pub fn parser<'a>() -> impl Parser<'a, ParserSource<'a>, ParseValues, Extra<'a>>
216216
.to_slice()
217217
.map_with(|ident: &str, e| ident.to_owned().spanned(e.span()));
218218

219-
let path_start = ident.then_ignore(just("::")).repeated().collect::<Vec<_>>();
220-
let path_end = ident
221-
.map(PathEnd::Ident)
222-
.or(just("*::").ignore_then(ident).map(PathEnd::WithIdent));
223-
let path = path_start
224-
.map_with(|v, e| v.spanned(e.span()))
225-
.then(path_end.map_with(|v, e| v.spanned(e.span())))
226-
.map(|(leading, last)| Path { leading, last });
219+
let mut path = chumsky::recursive::Recursive::declare();
220+
let path_start;
221+
let path_end;
222+
path.define({
223+
let ident_with_generics = ident.then(path.clone().delimited_by(just('<'), just('>')));
224+
225+
path_start = ident.then_ignore(just("::")).repeated().collect::<Vec<_>>();
226+
path_end = ident_with_generics
227+
.clone()
228+
.map_with(|(ident, path), e| {
229+
PathEnd::IdentGeneric(ident, Spanned::new(e.span(), Box::new(path)))
230+
})
231+
.or(just("*::")
232+
.ignore_then(ident_with_generics)
233+
.map_with(|(ident, path), e| {
234+
PathEnd::WithIdentGeneric(ident, Spanned::new(e.span(), Box::new(path)))
235+
}))
236+
.or(ident.map(PathEnd::Ident))
237+
.or(just("*::").ignore_then(ident).map(PathEnd::WithIdent));
238+
path_start
239+
.map_with(|v, e| v.spanned(e.span()))
240+
.then(path_end.clone().map_with(|v, e| v.spanned(e.span())))
241+
.map(|(leading, last)| Path { leading, last })
242+
});
227243

228244
let uses = just("use")
229245
.padded_by(comments)
@@ -457,20 +473,22 @@ pub fn parser<'a>() -> impl Parser<'a, ParserSource<'a>, ParseValues, Extra<'a>>
457473
|_| IndexMap::<Ident, ParseVal>::new(),
458474
)));
459475

460-
let reference = just('$').ignore_then(path).map(|path| {
476+
let reference = just('$').ignore_then(path.clone()).map(|path| {
461477
// We have at least 1 element in the path.
462478
Value::Ref(path)
463479
});
464480

465-
let path_p = path.padded_by(comments).padded();
481+
let path_p = path.clone().padded_by(comments).padded();
466482

467483
// Parser for tuple structs
468484
let unnamed_struct = path_p
485+
.clone()
469486
.then(tuple.clone())
470487
.map(|(name, fields)| (Some(name), Value::Struct(FieldsKind::Unnamed(fields))));
471488

472489
// Parser for structs
473490
let named_struct = path_p
491+
.clone()
474492
.then(structure.clone())
475493
.map(|(name, fields)| (Some(name), Value::Struct(FieldsKind::Named(fields))));
476494

@@ -503,16 +521,19 @@ pub fn parser<'a>() -> impl Parser<'a, ParserSource<'a>, ParseValues, Extra<'a>>
503521
// - `| Path::B`
504522
// - `|`
505523
let path_or = path_p
524+
.clone()
506525
.separated_by(just('|').padded_by(comments))
507526
.allow_leading()
508527
.at_least(2)
509528
.collect()
510529
.or(just('|')
511-
.ignore_then(path_p.or_not())
530+
.ignore_then(path_p.clone().or_not())
512531
.map(|p| p.into_iter().collect()))
513532
.map(Value::Or);
514533

515-
let path_value = path.map(|path: Path| (Some(path), Value::Struct(FieldsKind::Unit)));
534+
let path_value = path
535+
.clone()
536+
.map(|path: Path| (Some(path), Value::Struct(FieldsKind::Unit)));
516537

517538
// The start of a raw string: count the number of open braces
518539
let start_raw = just('{').repeated().at_least(1).count();
@@ -562,6 +583,7 @@ pub fn parser<'a>() -> impl Parser<'a, ParserSource<'a>, ParseValues, Extra<'a>>
562583
.boxed();
563584

564585
let ty_specification = path
586+
.clone()
565587
.delimited_by(
566588
just('<').padded_by(comments).padded(),
567589
just('>').padded_by(comments).padded(),
@@ -625,9 +647,10 @@ pub fn parser<'a>() -> impl Parser<'a, ParserSource<'a>, ParseValues, Extra<'a>>
625647
uses.then(
626648
just("copy")
627649
.padded()
628-
.ignore_then(binding(ident, object.clone(), path, comments))
650+
.ignore_then(binding(ident, object.clone(), path.clone(), comments))
629651
.map(|binding| (binding, ItemType::Copy))
630-
.or(binding(ident, object, path, comments).map(|binding| (binding, ItemType::Value)))
652+
.or(binding(ident, object, path.clone(), comments)
653+
.map(|binding| (binding, ItemType::Value)))
631654
.repeated()
632655
.collect::<Vec<_>>(),
633656
)

bauble/src/parse/value.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,19 @@ pub enum PathEnd {
1414
WithIdent(Ident),
1515
/// path::ident
1616
Ident(Ident),
17+
/// path::*::ident<...>
18+
WithIdentGeneric(Ident, Spanned<Box<Path>>),
19+
/// path::ident<...>
20+
IdentGeneric(Ident, Spanned<Box<Path>>),
1721
}
1822

1923
impl fmt::Display for PathEnd {
2024
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2125
match self {
2226
PathEnd::WithIdent(ident) => write!(f, "*::{ident}"),
2327
PathEnd::Ident(ident) => write!(f, "{ident}"),
28+
PathEnd::WithIdentGeneric(ident, path) => write!(f, "*::{ident}<{path}>",),
29+
PathEnd::IdentGeneric(ident, path) => write!(f, "{ident}<{path}>",),
2430
}
2531
}
2632
}
@@ -47,14 +53,37 @@ impl Path {
4753
}
4854

4955
pub fn last_ident(&self) -> Spanned<&str> {
50-
let (PathEnd::WithIdent(ident) | PathEnd::Ident(ident)) = &self.last.value;
56+
let (PathEnd::WithIdent(ident)
57+
| PathEnd::Ident(ident)
58+
| PathEnd::IdentGeneric(ident, _)
59+
| PathEnd::WithIdentGeneric(ident, _)) = &self.last.value;
5160

5261
ident.as_ref().map(|s| s.as_str())
5362
}
5463

5564
pub fn span(&self) -> crate::Span {
5665
crate::Span::new(self.last.span, self.leading.span.start..self.last.span.end)
5766
}
67+
68+
pub fn split_generic(&self) -> Option<(Path, &Path)> {
69+
match &*self.last {
70+
PathEnd::WithIdent(_) | PathEnd::Ident(_) => None,
71+
PathEnd::WithIdentGeneric(ident, path) => Some((
72+
Path {
73+
leading: self.leading.clone(),
74+
last: Spanned::new(self.last.span, PathEnd::WithIdent(ident.clone())),
75+
},
76+
path.as_ref().to_inner(),
77+
)),
78+
PathEnd::IdentGeneric(ident, path) => Some((
79+
Path {
80+
leading: self.leading.clone(),
81+
last: Spanned::new(self.last.span, PathEnd::Ident(ident.clone())),
82+
},
83+
path.as_ref().to_inner(),
84+
)),
85+
}
86+
}
5887
}
5988

6089
impl fmt::Display for Path {

bauble/src/types/path.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,11 +425,53 @@ impl<S: AsRef<str>> TypePath<S> {
425425
/// This means that:
426426
/// - The path is non-empty.
427427
/// - All the path segments are valid rust identifiers.
428+
/// - May include generic arguments.
428429
pub fn is_representable_type(&self) -> bool {
430+
let mut generic_ending = false;
429431
!self.is_empty()
430432
&& self.iter().all(|part| {
433+
if generic_ending {
434+
// If a generic path, must end with generic argument.
435+
return false;
436+
}
437+
438+
let s = part.as_str();
439+
440+
let has_generic = s.ends_with('>');
431441
let mut s = part.as_str().chars();
432442

443+
if has_generic {
444+
generic_ending = true;
445+
let mut delim_c = 1;
446+
let s = s.by_ref().rev().skip(1);
447+
let mut inner = String::new();
448+
for c in s {
449+
if c == '>' {
450+
delim_c += 1;
451+
}
452+
if c == '<' {
453+
delim_c -= 1;
454+
}
455+
456+
if delim_c == 0 {
457+
break;
458+
}
459+
460+
inner.push(c);
461+
}
462+
463+
if delim_c != 0 {
464+
// no corresponding delimiter was found.
465+
return false;
466+
}
467+
468+
// Assume inner argument to type are valid.
469+
let inner = TypePath::new_unchecked(inner);
470+
if !inner.is_representable_type() {
471+
return false;
472+
}
473+
}
474+
433475
s.next()
434476
.expect("Invariant, path parts aren't empty.")
435477
.is_ident_start()

bauble/src/value/display.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -715,13 +715,7 @@ impl<CTX> IndentedDisplay<CTX> for PathTreeNode {
715715
w.write("}");
716716
}
717717
PathTreeEnd::Everything => w.write("*"),
718-
PathTreeEnd::PathEnd(path_end) => match path_end {
719-
crate::parse::PathEnd::WithIdent(s) => {
720-
w.write("*::");
721-
w.write(s);
722-
}
723-
crate::parse::PathEnd::Ident(s) => w.write(s),
724-
},
718+
PathTreeEnd::PathEnd(path_end) => w.write(&path_end.to_string()),
725719
}
726720
}
727721
}

bauble/src/value/symbols.rs

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{borrow::Cow, collections::HashMap};
22

33
use crate::{
4-
BaubleContext,
4+
BaubleContext, CustomError,
55
context::PathReference,
66
parse::{Path, PathEnd, PathTreeEnd, PathTreeNode},
77
path::{TypePath, TypePathElem},
@@ -160,6 +160,13 @@ impl<'a> Symbols<'a> {
160160
.spanned(end.span));
161161
}
162162
}
163+
PathTreeEnd::PathEnd(PathEnd::IdentGeneric(ident, ..))
164+
| PathTreeEnd::PathEnd(PathEnd::WithIdentGeneric(ident, ..)) => {
165+
return Err(ConversionError::Custom(CustomError::new(
166+
"Use cannot use generics",
167+
))
168+
.spanned(ident.span));
169+
}
163170
}
164171
Ok(())
165172
}
@@ -247,12 +254,84 @@ impl<'a> Symbols<'a> {
247254
.map_err(|p| p.spanned(raw_path.span()))?;
248255
PathKind::Direct(leading)
249256
}
257+
PathEnd::WithIdentGeneric(ident, generic) => {
258+
let generic = self.resolve_path(&generic.value)?;
259+
PathKind::Indirect(
260+
leading,
261+
TypePathElem::new(format!("{ident}<{generic}>"))
262+
.map_err(|p| p.spanned(raw_path.span()))?,
263+
)
264+
}
265+
PathEnd::IdentGeneric(ident, generic) => {
266+
let generic = self.resolve_path(&generic.value)?;
267+
leading
268+
.push_str(&format!("{ident}<{generic}>"))
269+
.map_err(|p| p.spanned(raw_path.span()))?;
270+
PathKind::Direct(leading)
271+
}
250272
};
251273
Ok(path.spanned(raw_path.span()))
252274
}
253275

254276
pub fn resolve_item(&self, raw_path: &Path, ref_kind: RefKind) -> Result<Cow<PathReference>> {
255-
let path = self.resolve_path(raw_path)?;
277+
fn resolve_path(
278+
symbols: &Symbols,
279+
raw_path: &Path,
280+
ref_kind: RefKind,
281+
) -> Result<Spanned<PathKind>> {
282+
let raw_path_split = raw_path.split_generic();
283+
let is_generic = raw_path_split.is_some();
284+
let (path, &generic) = raw_path_split
285+
.as_ref()
286+
.map(|(l, r)| (l, r))
287+
.unwrap_or((raw_path, &raw_path));
288+
289+
let path = symbols.resolve_path(path)?;
290+
291+
Ok(if matches!(ref_kind, RefKind::Type) {
292+
match path.value {
293+
PathKind::Direct(type_path) => {
294+
if let Some(RefCopy::Ref(r)) = symbols.uses.get(type_path.as_str())
295+
&& let Some(ty) = r.ty
296+
{
297+
let path = &symbols.ctx.type_registry().key_type(ty).meta.path;
298+
if is_generic {
299+
let generic = resolve_path(symbols, generic, ref_kind)?;
300+
PathKind::Direct(
301+
TypePath::new(format!("{path}<{generic}>")).unwrap(),
302+
)
303+
} else {
304+
PathKind::Direct(TypePath::new(path.to_string()).unwrap())
305+
}
306+
} else if is_generic {
307+
let generic = resolve_path(symbols, generic, ref_kind)?;
308+
PathKind::Direct(
309+
TypePath::new(format!("{type_path}<{generic}>")).unwrap(),
310+
)
311+
} else {
312+
PathKind::Direct(TypePath::new(format!("{type_path}")).unwrap())
313+
}
314+
}
315+
PathKind::Indirect(type_path, type_path_elem) => {
316+
if is_generic {
317+
let generic = resolve_path(symbols, generic, ref_kind)?;
318+
PathKind::Indirect(
319+
type_path,
320+
TypePathElem::new(format!("{type_path_elem}<{generic}>")).unwrap(),
321+
)
322+
} else {
323+
PathKind::Indirect(type_path, type_path_elem)
324+
}
325+
}
326+
}
327+
.spanned(path.span)
328+
} else {
329+
path
330+
})
331+
}
332+
333+
let path = resolve_path(self, raw_path, ref_kind)?;
334+
256335
match &path.value {
257336
PathKind::Direct(path) => {
258337
if let Some(RefCopy::Ref(r)) = self.uses.get(path.as_str()) {

bauble_macro_util/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,7 +1185,9 @@ pub fn derive_bauble_derive_input(
11851185
quote! {
11861186
::bauble::path::TypePath::new({
11871187
let mut s = __generic_path.to_string();
1188+
s.push_str("<");
11881189
#types
1190+
s.push_str(">");
11891191
s
11901192
}).unwrap()
11911193
}

0 commit comments

Comments
 (0)