Carry a type alias's default parameters
`pub type Result<O, E = ContextError> = core::result::Result<O, E>` is how a crate spells its own Result, and it is written `Result<O>` at most use sites. That is an arity mismatch against the alias's two parameters, so it did not expand at all -- and the unexpanded name then failed everything downstream that knows what a `Result` is: `Ok(..)` was "not a known enum variant", and `?` on it "not a supported combination". Defaults now travel with the parameters and fill in the missing arguments. tests/cases/064. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2cccbf7 parent: 1687aba modified
src/lower.rs +29 -6 | @@ -312,7 +312,7 @@ pub struct Lowerer { | ||
| 312 | 312 | /// Writes through it produce the proc's string result. |
| 313 | 313 | fmt_param: Option<String>, |
| 314 | 314 | /// `type X<T> = ...`, expanded before any type is mapped. |
| 315 | - aliases: HashMap<String, (Vec<String>, syn::Type)>, | |
| 315 | + aliases: HashMap<String, (Vec<(String, Option<syn::Type>)>, syn::Type)>, | |
| 316 | 316 | /// Whether anything in the input is a future, which decides if the async |
| 317 | 317 | /// runtime is emitted. |
| 318 | 318 | /// Whether the program spawns threads. Sharing a `ref` across threads |
| @@ -733,12 +733,18 @@ impl Lowerer { | ||
| 733 | 733 | match item { |
| 734 | 734 | Item::Use(u) => self.collect_use(&u.tree, &[]), |
| 735 | 735 | Item::Type(t) => { |
| 736 | - let params: Vec<String> = t | |
| 736 | + // Defaults travel with the parameters: `type Result<O, E = | |
| 737 | + // ContextError>` is written `Result<O>` at most use sites, and | |
| 738 | + // without the default that is an arity mismatch and no | |
| 739 | + // expansion at all. | |
| 740 | + let params: Vec<(String, Option<syn::Type>)> = t | |
| 737 | 741 | .generics |
| 738 | 742 | .params |
| 739 | 743 | .iter() |
| 740 | 744 | .filter_map(|g| match g { |
| 741 | - syn::GenericParam::Type(t) => Some(t.ident.to_string()), | |
| 745 | + syn::GenericParam::Type(t) => { | |
| 746 | + Some((t.ident.to_string(), t.default.clone().map(|(_, d)| d))) | |
| 747 | + } | |
| 742 | 748 | _ => None, |
| 743 | 749 | }) |
| 744 | 750 | .collect(); |
| @@ -986,12 +992,18 @@ impl Lowerer { | ||
| 986 | 992 | } |
| 987 | 993 | } |
| 988 | 994 | Item::Type(t) => { |
| 989 | - let params: Vec<String> = t | |
| 995 | + // Defaults travel with the parameters: `type Result<O, E = | |
| 996 | + // ContextError>` is written `Result<O>` at most use sites, and | |
| 997 | + // without the default that is an arity mismatch and no | |
| 998 | + // expansion at all. | |
| 999 | + let params: Vec<(String, Option<syn::Type>)> = t | |
| 990 | 1000 | .generics |
| 991 | 1001 | .params |
| 992 | 1002 | .iter() |
| 993 | 1003 | .filter_map(|g| match g { |
| 994 | - syn::GenericParam::Type(t) => Some(t.ident.to_string()), | |
| 1004 | + syn::GenericParam::Type(t) => { | |
| 1005 | + Some((t.ident.to_string(), t.default.clone().map(|(_, d)| d))) | |
| 1006 | + } | |
| 995 | 1007 | _ => None, |
| 996 | 1008 | }) |
| 997 | 1009 | .collect(); |
| @@ -1557,6 +1569,16 @@ impl Lowerer { | ||
| 1557 | 1569 | .collect(), |
| 1558 | 1570 | _ => vec![], |
| 1559 | 1571 | }; |
| 1572 | + // A missing argument is supplied by the parameter's default, which is | |
| 1573 | + // how `type Result<O, E = ContextError>` is written `Result<O>` almost | |
| 1574 | + // everywhere it is used. | |
| 1575 | + let mut args = args; | |
| 1576 | + while args.len() < params.len() { | |
| 1577 | + match ¶ms[args.len()].1 { | |
| 1578 | + Some(d) => args.push(d.clone()), | |
| 1579 | + None => break, | |
| 1580 | + } | |
| 1581 | + } | |
| 1560 | 1582 | if args.len() != params.len() { |
| 1561 | 1583 | // Flattening several files into one module can bring a crate's own |
| 1562 | 1584 | // alias (`type Result<T> = Result<T, Error>`) into scope at a site |
| @@ -1565,7 +1587,8 @@ impl Lowerer { | ||
| 1565 | 1587 | // neither is left for `ty::map` to report. |
| 1566 | 1588 | return Ok(t.clone()); |
| 1567 | 1589 | } |
| 1568 | - self.expand(&substitute(target, params, &args), depth + 1) | |
| 1590 | + let names: Vec<String> = params.iter().map(|(n, _)| n.clone()).collect(); | |
| 1591 | + self.expand(&substitute(target, &names, &args), depth + 1) | |
| 1569 | 1592 | } |
| 1570 | 1593 | |
| 1571 | 1594 | /// The type parameters a generic item declares. |
| @@ -312,7 +312,7 @@ pub struct Lowerer { | |||
| 312 | /// Writes through it produce the proc's string result. | 312 | /// Writes through it produce the proc's string result. |
| 313 | fmt_param: Option<String>, | 313 | fmt_param: Option<String>, |
| 314 | /// `type X<T> = ...`, expanded before any type is mapped. | 314 | /// `type X<T> = ...`, expanded before any type is mapped. |
| 315 | - aliases: HashMap<String, (Vec<String>, syn::Type)>, | 315 | + aliases: HashMap<String, (Vec<(String, Option<syn::Type>)>, syn::Type)>, |
| 316 | /// Whether anything in the input is a future, which decides if the async | 316 | /// Whether anything in the input is a future, which decides if the async |
| 317 | /// runtime is emitted. | 317 | /// runtime is emitted. |
| 318 | /// Whether the program spawns threads. Sharing a `ref` across threads | 318 | /// Whether the program spawns threads. Sharing a `ref` across threads |
| @@ -733,12 +733,18 @@ impl Lowerer { | |||
| 733 | match item { | 733 | match item { |
| 734 | Item::Use(u) => self.collect_use(&u.tree, &[]), | 734 | Item::Use(u) => self.collect_use(&u.tree, &[]), |
| 735 | Item::Type(t) => { | 735 | Item::Type(t) => { |
| 736 | - let params: Vec<String> = t | 736 | + // Defaults travel with the parameters: `type Result<O, E = |
| 737 | + // ContextError>` is written `Result<O>` at most use sites, and | ||
| 738 | + // without the default that is an arity mismatch and no | ||
| 739 | + // expansion at all. | ||
| 740 | + let params: Vec<(String, Option<syn::Type>)> = t | ||
| 737 | .generics | 741 | .generics |
| 738 | .params | 742 | .params |
| 739 | .iter() | 743 | .iter() |
| 740 | .filter_map(|g| match g { | 744 | .filter_map(|g| match g { |
| 741 | - syn::GenericParam::Type(t) => Some(t.ident.to_string()), | 745 | + syn::GenericParam::Type(t) => { |
| 746 | + Some((t.ident.to_string(), t.default.clone().map(|(_, d)| d))) | ||
| 747 | + } | ||
| 742 | _ => None, | 748 | _ => None, |
| 743 | }) | 749 | }) |
| 744 | .collect(); | 750 | .collect(); |
| @@ -986,12 +992,18 @@ impl Lowerer { | |||
| 986 | } | 992 | } |
| 987 | } | 993 | } |
| 988 | Item::Type(t) => { | 994 | Item::Type(t) => { |
| 989 | - let params: Vec<String> = t | 995 | + // Defaults travel with the parameters: `type Result<O, E = |
| 996 | + // ContextError>` is written `Result<O>` at most use sites, and | ||
| 997 | + // without the default that is an arity mismatch and no | ||
| 998 | + // expansion at all. | ||
| 999 | + let params: Vec<(String, Option<syn::Type>)> = t | ||
| 990 | .generics | 1000 | .generics |
| 991 | .params | 1001 | .params |
| 992 | .iter() | 1002 | .iter() |
| 993 | .filter_map(|g| match g { | 1003 | .filter_map(|g| match g { |
| 994 | - syn::GenericParam::Type(t) => Some(t.ident.to_string()), | 1004 | + syn::GenericParam::Type(t) => { |
| 1005 | + Some((t.ident.to_string(), t.default.clone().map(|(_, d)| d))) | ||
| 1006 | + } | ||
| 995 | _ => None, | 1007 | _ => None, |
| 996 | }) | 1008 | }) |
| 997 | .collect(); | 1009 | .collect(); |
| @@ -1557,6 +1569,16 @@ impl Lowerer { | |||
| 1557 | .collect(), | 1569 | .collect(), |
| 1558 | _ => vec![], | 1570 | _ => vec![], |
| 1559 | }; | 1571 | }; |
| 1572 | + // A missing argument is supplied by the parameter's default, which is | ||
| 1573 | + // how `type Result<O, E = ContextError>` is written `Result<O>` almost | ||
| 1574 | + // everywhere it is used. | ||
| 1575 | + let mut args = args; | ||
| 1576 | + while args.len() < params.len() { | ||
| 1577 | + match ¶ms[args.len()].1 { | ||
| 1578 | + Some(d) => args.push(d.clone()), | ||
| 1579 | + None => break, | ||
| 1580 | + } | ||
| 1581 | + } | ||
| 1560 | if args.len() != params.len() { | 1582 | if args.len() != params.len() { |
| 1561 | // Flattening several files into one module can bring a crate's own | 1583 | // Flattening several files into one module can bring a crate's own |
| 1562 | // alias (`type Result<T> = Result<T, Error>`) into scope at a site | 1584 | // alias (`type Result<T> = Result<T, Error>`) into scope at a site |
| @@ -1565,7 +1587,8 @@ impl Lowerer { | |||
| 1565 | // neither is left for `ty::map` to report. | 1587 | // neither is left for `ty::map` to report. |
| 1566 | return Ok(t.clone()); | 1588 | return Ok(t.clone()); |
| 1567 | } | 1589 | } |
| 1568 | - self.expand(&substitute(target, params, &args), depth + 1) | 1590 | + let names: Vec<String> = params.iter().map(|(n, _)| n.clone()).collect(); |
| 1591 | + self.expand(&substitute(target, &names, &args), depth + 1) | ||
| 1569 | } | 1592 | } |
| 1570 | 1593 | ||
| 1571 | /// The type parameters a generic item declares. | 1594 | /// The type parameters a generic item declares. |
added
tests/cases/064-alias-default-param.rs +33 -0 | new file mode 100644 | ||
| @@ -0,0 +1,33 @@ | ||
| 1 | +// A type alias with a default parameter. `type Result<T, E = MyError>` is how | |
| 2 | +// most crates spell their own Result, and it is written `Result<T>` almost | |
| 3 | +// everywhere it is used — which is an arity mismatch unless the default is | |
| 4 | +// carried along with the parameter. | |
| 5 | +#[derive(Debug)] | |
| 6 | +struct MyError { | |
| 7 | + code: u32, | |
| 8 | +} | |
| 9 | + | |
| 10 | +type Res<T, E = MyError> = core::result::Result<T, E>; | |
| 11 | + | |
| 12 | +fn halve(n: u32) -> Res<u32> { | |
| 13 | + if n % 2 == 0 { | |
| 14 | + Ok(n / 2) | |
| 15 | + } else { | |
| 16 | + Err(MyError { code: n }) | |
| 17 | + } | |
| 18 | +} | |
| 19 | + | |
| 20 | +fn both(n: u32) -> Res<u32, MyError> { | |
| 21 | + halve(n) | |
| 22 | +} | |
| 23 | + | |
| 24 | +fn main() { | |
| 25 | + match halve(10) { | |
| 26 | + Ok(v) => println!("ok {}", v), | |
| 27 | + Err(e) => println!("err {}", e.code), | |
| 28 | + } | |
| 29 | + match both(7) { | |
| 30 | + Ok(v) => println!("ok {}", v), | |
| 31 | + Err(e) => println!("err {}", e.code), | |
| 32 | + } | |
| 33 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | +// A type alias with a default parameter. `type Result<T, E = MyError>` is how | ||
| 2 | +// most crates spell their own Result, and it is written `Result<T>` almost | ||
| 3 | +// everywhere it is used — which is an arity mismatch unless the default is | ||
| 4 | +// carried along with the parameter. | ||
| 5 | +#[derive(Debug)] | ||
| 6 | +struct MyError { | ||
| 7 | + code: u32, | ||
| 8 | +} | ||
| 9 | + | ||
| 10 | +type Res<T, E = MyError> = core::result::Result<T, E>; | ||
| 11 | + | ||
| 12 | +fn halve(n: u32) -> Res<u32> { | ||
| 13 | + if n % 2 == 0 { | ||
| 14 | + Ok(n / 2) | ||
| 15 | + } else { | ||
| 16 | + Err(MyError { code: n }) | ||
| 17 | + } | ||
| 18 | +} | ||
| 19 | + | ||
| 20 | +fn both(n: u32) -> Res<u32, MyError> { | ||
| 21 | + halve(n) | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | +fn main() { | ||
| 25 | + match halve(10) { | ||
| 26 | + Ok(v) => println!("ok {}", v), | ||
| 27 | + Err(e) => println!("err {}", e.code), | ||
| 28 | + } | ||
| 29 | + match both(7) { | ||
| 30 | + Ok(v) => println!("ok {}", v), | ||
| 31 | + Err(e) => println!("err {}", e.code), | ||
| 32 | + } | ||
| 33 | +} | ||