Lower Return Type Notation (Type::method(..): Send)

We do it the way rustc does it, by only marking segments with it, and not the whole path. This will allow extending where it is allowed in the future.
This commit is contained in:
Chayim Refael Friedman 2025-03-08 20:38:47 +02:00
parent 3fc655b239
commit eaa0a39831
5 changed files with 125 additions and 80 deletions

View file

@ -872,7 +872,7 @@ fn copy_generic_args(
args, args,
has_self_type: generic_args.has_self_type, has_self_type: generic_args.has_self_type,
bindings, bindings,
desugared_from_fn: generic_args.desugared_from_fn, parenthesized: generic_args.parenthesized,
} }
}) })
} }

View file

@ -79,6 +79,19 @@ thin_vec_with_header_struct! {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GenericArgsParentheses {
No,
/// Bounds of the form `Type::method(..): Send` or `impl Trait<method(..): Send>`,
/// aka. Return Type Notation or RTN.
ReturnTypeNotation,
/// `Fn`-family parenthesized traits, e.g. `impl Fn(u32) -> String`.
///
/// This is desugared into one generic argument containing a tuple of all arguments,
/// and an associated type binding for `Output` for the return type.
ParenSugar,
}
/// Generic arguments to a path segment (e.g. the `i32` in `Option<i32>`). This /// Generic arguments to a path segment (e.g. the `i32` in `Option<i32>`). This
/// also includes bindings of associated types, like in `Iterator<Item = Foo>`. /// also includes bindings of associated types, like in `Iterator<Item = Foo>`.
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -92,9 +105,8 @@ pub struct GenericArgs {
pub has_self_type: bool, pub has_self_type: bool,
/// Associated type bindings like in `Iterator<Item = T>`. /// Associated type bindings like in `Iterator<Item = T>`.
pub bindings: Box<[AssociatedTypeBinding]>, pub bindings: Box<[AssociatedTypeBinding]>,
/// Whether these generic args were desugared from `Trait(Arg) -> Output` /// Whether these generic args were written with parentheses and how.
/// parenthesis notation typically used for the `Fn` traits. pub parenthesized: GenericArgsParentheses,
pub desugared_from_fn: bool,
} }
/// An associated type binding like in `Iterator<Item = T>`. /// An associated type binding like in `Iterator<Item = T>`.
@ -326,7 +338,16 @@ impl GenericArgs {
args: Box::default(), args: Box::default(),
has_self_type: false, has_self_type: false,
bindings: Box::default(), bindings: Box::default(),
desugared_from_fn: false, parenthesized: GenericArgsParentheses::No,
}
}
pub(crate) fn return_type_notation() -> GenericArgs {
GenericArgs {
args: Box::default(),
has_self_type: false,
bindings: Box::default(),
parenthesized: GenericArgsParentheses::ReturnTypeNotation,
} }
} }
} }

View file

@ -13,7 +13,10 @@ use stdx::thin_vec::EmptyOptimizedThinVec;
use syntax::ast::{self, AstNode, HasGenericArgs, HasTypeBounds}; use syntax::ast::{self, AstNode, HasGenericArgs, HasTypeBounds};
use crate::{ use crate::{
path::{AssociatedTypeBinding, GenericArg, GenericArgs, ModPath, Path, PathKind}, path::{
AssociatedTypeBinding, GenericArg, GenericArgs, GenericArgsParentheses, ModPath, Path,
PathKind,
},
type_ref::{LifetimeRef, TypeBound, TypeRef}, type_ref::{LifetimeRef, TypeBound, TypeRef},
}; };
@ -73,6 +76,9 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
segment.parenthesized_arg_list(), segment.parenthesized_arg_list(),
segment.ret_type(), segment.ret_type(),
) )
})
.or_else(|| {
segment.return_type_syntax().map(|_| GenericArgs::return_type_notation())
}); });
if args.is_some() { if args.is_some() {
generic_args.resize(segments.len(), None); generic_args.resize(segments.len(), None);
@ -126,7 +132,7 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
has_self_type: true, has_self_type: true,
bindings: it.bindings.clone(), bindings: it.bindings.clone(),
desugared_from_fn: it.desugared_from_fn, parenthesized: it.parenthesized,
}, },
None => GenericArgs { None => GenericArgs {
args: Box::new([self_type]), args: Box::new([self_type]),
@ -281,7 +287,12 @@ pub(super) fn lower_generic_args(
let name = name_ref.as_name(); let name = name_ref.as_name();
let args = assoc_type_arg let args = assoc_type_arg
.generic_arg_list() .generic_arg_list()
.and_then(|args| lower_generic_args(lower_ctx, args)); .and_then(|args| lower_generic_args(lower_ctx, args))
.or_else(|| {
assoc_type_arg
.return_type_syntax()
.map(|_| GenericArgs::return_type_notation())
});
let type_ref = let type_ref =
assoc_type_arg.ty().map(|it| TypeRef::from_ast(lower_ctx, it)); assoc_type_arg.ty().map(|it| TypeRef::from_ast(lower_ctx, it));
let type_ref = type_ref let type_ref = type_ref
@ -315,7 +326,7 @@ pub(super) fn lower_generic_args(
args: args.into_boxed_slice(), args: args.into_boxed_slice(),
has_self_type: false, has_self_type: false,
bindings: bindings.into_boxed_slice(), bindings: bindings.into_boxed_slice(),
desugared_from_fn: false, parenthesized: GenericArgsParentheses::No,
}) })
} }
@ -353,5 +364,10 @@ fn lower_generic_args_from_fn_path(
bounds: Box::default(), bounds: Box::default(),
}]) }])
}; };
Some(GenericArgs { args, has_self_type: false, bindings, desugared_from_fn: true }) Some(GenericArgs {
args,
has_self_type: false,
bindings,
parenthesized: GenericArgsParentheses::ParenSugar,
})
} }

View file

@ -2303,78 +2303,83 @@ impl HirDisplayWithTypesMap for Path {
if let Some(generic_args) = segment.args_and_bindings { if let Some(generic_args) = segment.args_and_bindings {
// We should be in type context, so format as `Foo<Bar>` instead of `Foo::<Bar>`. // We should be in type context, so format as `Foo<Bar>` instead of `Foo::<Bar>`.
// Do we actually format expressions? // Do we actually format expressions?
if generic_args.desugared_from_fn { match generic_args.parenthesized {
// First argument will be a tuple, which already includes the parentheses. hir_def::path::GenericArgsParentheses::ReturnTypeNotation => {
// If the tuple only contains 1 item, write it manually to avoid the trailing `,`. write!(f, "(..)")?;
let tuple = match generic_args.args[0] { }
hir_def::path::GenericArg::Type(ty) => match &types_map[ty] { hir_def::path::GenericArgsParentheses::ParenSugar => {
TypeRef::Tuple(it) => Some(it), // First argument will be a tuple, which already includes the parentheses.
// If the tuple only contains 1 item, write it manually to avoid the trailing `,`.
let tuple = match generic_args.args[0] {
hir_def::path::GenericArg::Type(ty) => match &types_map[ty] {
TypeRef::Tuple(it) => Some(it),
_ => None,
},
_ => None, _ => None,
}, };
_ => None, if let Some(v) = tuple {
}; if v.len() == 1 {
if let Some(v) = tuple { write!(f, "(")?;
if v.len() == 1 { v[0].hir_fmt(f, types_map)?;
write!(f, "(")?; write!(f, ")")?;
v[0].hir_fmt(f, types_map)?; } else {
write!(f, ")")?; generic_args.args[0].hir_fmt(f, types_map)?;
} else { }
generic_args.args[0].hir_fmt(f, types_map)?; }
if let Some(ret) = generic_args.bindings[0].type_ref {
if !matches!(&types_map[ret], TypeRef::Tuple(v) if v.is_empty()) {
write!(f, " -> ")?;
ret.hir_fmt(f, types_map)?;
}
} }
} }
if let Some(ret) = generic_args.bindings[0].type_ref { hir_def::path::GenericArgsParentheses::No => {
if !matches!(&types_map[ret], TypeRef::Tuple(v) if v.is_empty()) { let mut first = true;
write!(f, " -> ")?; // Skip the `Self` bound if exists. It's handled outside the loop.
ret.hir_fmt(f, types_map)?; for arg in &generic_args.args[generic_args.has_self_type as usize..] {
if first {
first = false;
write!(f, "<")?;
} else {
write!(f, ", ")?;
}
arg.hir_fmt(f, types_map)?;
}
for binding in generic_args.bindings.iter() {
if first {
first = false;
write!(f, "<")?;
} else {
write!(f, ", ")?;
}
write!(f, "{}", binding.name.display(f.db.upcast(), f.edition()))?;
match &binding.type_ref {
Some(ty) => {
write!(f, " = ")?;
ty.hir_fmt(f, types_map)?
}
None => {
write!(f, ": ")?;
f.write_joined(
binding.bounds.iter().map(TypesMapAdapter::wrap(types_map)),
" + ",
)?;
}
}
} }
}
return Ok(());
}
let mut first = true; // There may be no generic arguments to print, in case of a trait having only a
// Skip the `Self` bound if exists. It's handled outside the loop. // single `Self` bound which is converted to `<Ty as Trait>::Assoc`.
for arg in &generic_args.args[generic_args.has_self_type as usize..] { if !first {
if first { write!(f, ">")?;
first = false;
write!(f, "<")?;
} else {
write!(f, ", ")?;
}
arg.hir_fmt(f, types_map)?;
}
for binding in generic_args.bindings.iter() {
if first {
first = false;
write!(f, "<")?;
} else {
write!(f, ", ")?;
}
write!(f, "{}", binding.name.display(f.db.upcast(), f.edition()))?;
match &binding.type_ref {
Some(ty) => {
write!(f, " = ")?;
ty.hir_fmt(f, types_map)?
} }
None => {
write!(f, ": ")?; // Current position: `<Ty as Trait<Args>|`
f.write_joined( if generic_args.has_self_type {
binding.bounds.iter().map(TypesMapAdapter::wrap(types_map)), write!(f, ">")?;
" + ",
)?;
} }
} }
} }
// There may be no generic arguments to print, in case of a trait having only a
// single `Self` bound which is converted to `<Ty as Trait>::Assoc`.
if !first {
write!(f, ">")?;
}
// Current position: `<Ty as Trait<Args>|`
if generic_args.has_self_type {
write!(f, ">")?;
}
} }
} }

View file

@ -8,7 +8,7 @@ use hir_def::{
data::TraitFlags, data::TraitFlags,
expr_store::HygieneId, expr_store::HygieneId,
generics::{TypeParamProvenance, WherePredicate, WherePredicateTypeTarget}, generics::{TypeParamProvenance, WherePredicate, WherePredicateTypeTarget},
path::{GenericArg, GenericArgs, Path, PathSegment, PathSegments}, path::{GenericArg, GenericArgs, GenericArgsParentheses, Path, PathSegment, PathSegments},
resolver::{ResolveValueResult, TypeNs, ValueNs}, resolver::{ResolveValueResult, TypeNs, ValueNs},
type_ref::{TypeBound, TypeRef, TypesMap}, type_ref::{TypeBound, TypeRef, TypesMap},
GenericDefId, GenericParamId, ItemContainerId, Lookup, TraitId, GenericDefId, GenericParamId, ItemContainerId, Lookup, TraitId,
@ -138,12 +138,15 @@ impl<'a, 'b> PathLoweringContext<'a, 'b> {
fn prohibit_parenthesized_generic_args(&mut self) -> bool { fn prohibit_parenthesized_generic_args(&mut self) -> bool {
if let Some(generic_args) = self.current_or_prev_segment.args_and_bindings { if let Some(generic_args) = self.current_or_prev_segment.args_and_bindings {
if generic_args.desugared_from_fn { match generic_args.parenthesized {
let segment = self.current_segment_u32(); GenericArgsParentheses::No => {}
self.on_diagnostic( GenericArgsParentheses::ReturnTypeNotation | GenericArgsParentheses::ParenSugar => {
PathLoweringDiagnostic::ParenthesizedGenericArgsWithoutFnTrait { segment }, let segment = self.current_segment_u32();
); self.on_diagnostic(
return true; PathLoweringDiagnostic::ParenthesizedGenericArgsWithoutFnTrait { segment },
);
return true;
}
} }
} }
false false