//! HIR back into source code, and just displaying them for debugging/testing
//! purposes.
-use std::{
- array,
- fmt::{self, Debug},
-};
+use std::fmt::{self, Debug};
+use base_db::CrateId;
use chalk_ir::BoundVar;
use hir_def::{
body,
intern::{Internable, Interned},
item_scope::ItemInNs,
path::{Path, PathKind},
- type_ref::{TypeBound, TypeRef},
+ type_ref::{TraitBoundModifier, TypeBound, TypeRef},
visibility::Visibility,
- AssocContainerId, Lookup, ModuleId, TraitId,
+ HasModule, ItemContainerId, Lookup, ModuleId, TraitId,
};
use hir_expand::{hygiene::Hygiene, name::Name};
+use itertools::Itertools;
+use syntax::SmolStr;
use crate::{
- const_from_placeholder_idx, db::HirDatabase, from_assoc_type_id, from_foreign_def_id,
- from_placeholder_idx, lt_from_placeholder_idx, mapping::from_chalk, primitive, subst_prefix,
- to_assoc_type_id, utils::generics, AdtId, AliasEq, AliasTy, CallableDefId, CallableSig, Const,
- ConstValue, DomainGoal, GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData,
- LifetimeOutlives, Mutability, OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause,
- Scalar, TraitRef, TraitRefExt, Ty, TyExt, TyKind, WhereClause,
+ const_from_placeholder_idx,
+ db::HirDatabase,
+ from_assoc_type_id, from_foreign_def_id, from_placeholder_idx, lt_from_placeholder_idx,
+ mapping::from_chalk,
+ primitive, subst_prefix, to_assoc_type_id,
+ utils::{self, generics},
+ AdtId, AliasEq, AliasTy, CallableDefId, CallableSig, Const, ConstValue, DomainGoal, GenericArg,
+ ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives, Mutability, OpaqueTy,
+ ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar, TraitRef, TraitRefExt, Ty, TyExt,
+ TyKind, WhereClause,
};
pub struct HirFormatter<'a> {
write!(self, "{}", sep)?;
}
first = false;
+
+ // Abbreviate multiple omitted types with a single ellipsis.
+ if self.should_truncate() {
+ return write!(self, "{}", TYPE_HINT_TRUNCATION);
+ }
+
e.hir_fmt(self)?;
}
Ok(())
}
pub fn should_truncate(&self) -> bool {
- if let Some(max_size) = self.max_size {
- self.curr_size >= max_size
- } else {
- false
+ match self.max_size {
+ Some(max_size) => self.curr_size >= max_size,
+ None => false,
}
}
let trait_ = f.db.trait_data(self.trait_(f.db));
write!(f, "<")?;
- self.self_type_parameter(&Interner).hir_fmt(f)?;
+ self.self_type_parameter(Interner).hir_fmt(f)?;
write!(f, " as {}", trait_.name)?;
- if self.substitution.len(&Interner) > 1 {
+ if self.substitution.len(Interner) > 1 {
write!(f, "<")?;
- f.write_joined(&self.substitution.as_slice(&Interner)[1..], ", ")?;
+ f.write_joined(&self.substitution.as_slice(Interner)[1..], ", ")?;
write!(f, ">")?;
}
write!(f, ">::{}", f.db.type_alias_data(from_assoc_type_id(self.associated_ty_id)).name)?;
return write!(f, "{}", TYPE_HINT_TRUNCATION);
}
- self.substitution.at(&Interner, 0).hir_fmt(f)
+ self.substitution.at(Interner, 0).hir_fmt(f)
}
}
return write!(f, "{}", TYPE_HINT_TRUNCATION);
}
- match self.kind(&Interner) {
+ match self.kind(Interner) {
TyKind::Never => write!(f, "!")?,
TyKind::Str => write!(f, "str")?,
TyKind::Scalar(Scalar::Bool) => write!(f, "bool")?,
write!(f, "]")?;
}
TyKind::Raw(m, t) | TyKind::Ref(m, _, t) => {
- if matches!(self.kind(&Interner), TyKind::Raw(..)) {
+ if matches!(self.kind(Interner), TyKind::Raw(..)) {
write!(
f,
"*{}",
}
// FIXME: all this just to decide whether to use parentheses...
- let datas;
- let predicates: Vec<_> = match t.kind(&Interner) {
+ let contains_impl_fn = |bounds: &[QuantifiedWhereClause]| {
+ bounds.iter().any(|bound| {
+ if let WhereClause::Implemented(trait_ref) = bound.skip_binders() {
+ let trait_ = trait_ref.hir_trait_id();
+ fn_traits(f.db.upcast(), trait_).any(|it| it == trait_)
+ } else {
+ false
+ }
+ })
+ };
+ let (preds_to_print, has_impl_fn_pred) = match t.kind(Interner) {
TyKind::Dyn(dyn_ty) if dyn_ty.bounds.skip_binders().interned().len() > 1 => {
- dyn_ty.bounds.skip_binders().interned().iter().cloned().collect()
+ let bounds = dyn_ty.bounds.skip_binders().interned();
+ (bounds.len(), contains_impl_fn(bounds))
}
- &TyKind::Alias(AliasTy::Opaque(OpaqueTy {
+ TyKind::Alias(AliasTy::Opaque(OpaqueTy {
opaque_ty_id,
- substitution: ref parameters,
- })) => {
- let impl_trait_id = f.db.lookup_intern_impl_trait_id(opaque_ty_id.into());
+ substitution: parameters,
+ }))
+ | TyKind::OpaqueType(opaque_ty_id, parameters) => {
+ let impl_trait_id =
+ f.db.lookup_intern_impl_trait_id((*opaque_ty_id).into());
if let ImplTraitId::ReturnTypeImplTrait(func, idx) = impl_trait_id {
- datas =
+ let datas =
f.db.return_type_impl_traits(func)
.expect("impl trait id without data");
let data = (*datas)
.as_ref()
.map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
- let bounds = data.substitute(&Interner, parameters);
- bounds.into_value_and_skipped_binders().0
+ let bounds = data.substitute(Interner, parameters);
+ let mut len = bounds.skip_binders().len();
+
+ // Don't count Sized but count when it absent
+ // (i.e. when explicit ?Sized bound is set).
+ let default_sized = SizedByDefault::Sized {
+ anchor: func.lookup(f.db.upcast()).module(f.db.upcast()).krate(),
+ };
+ let sized_bounds = bounds
+ .skip_binders()
+ .iter()
+ .filter(|b| {
+ matches!(
+ b.skip_binders(),
+ WhereClause::Implemented(trait_ref)
+ if default_sized.is_sized_trait(
+ trait_ref.hir_trait_id(),
+ f.db.upcast(),
+ ),
+ )
+ })
+ .count();
+ match sized_bounds {
+ 0 => len += 1,
+ _ => {
+ len = len.saturating_sub(sized_bounds);
+ }
+ }
+
+ (len, contains_impl_fn(bounds.skip_binders()))
} else {
- Vec::new()
+ (0, false)
}
}
- _ => Vec::new(),
+ _ => (0, false),
};
- if let Some(WhereClause::Implemented(trait_ref)) =
- predicates.get(0).map(|b| b.skip_binders())
- {
- let trait_ = trait_ref.hir_trait_id();
- if fn_traits(f.db.upcast(), trait_).any(|it| it == trait_)
- && predicates.len() <= 2
- {
- return t.hir_fmt(f);
- }
+ if has_impl_fn_pred && preds_to_print <= 2 {
+ return t.hir_fmt(f);
}
- if predicates.len() > 1 {
+ if preds_to_print > 1 {
write!(f, "(")?;
t.hir_fmt(f)?;
write!(f, ")")?;
}
}
TyKind::Tuple(_, substs) => {
- if substs.len(&Interner) == 1 {
+ if substs.len(Interner) == 1 {
write!(f, "(")?;
- substs.at(&Interner, 0).hir_fmt(f)?;
+ substs.at(Interner, 0).hir_fmt(f)?;
write!(f, ",)")?;
} else {
write!(f, "(")?;
- f.write_joined(&*substs.as_slice(&Interner), ", ")?;
+ f.write_joined(&*substs.as_slice(Interner), ", ")?;
write!(f, ")")?;
}
}
}
TyKind::FnDef(def, parameters) => {
let def = from_chalk(f.db, *def);
- let sig = f.db.callable_item_signature(def).substitute(&Interner, parameters);
+ let sig = f.db.callable_item_signature(def).substitute(Interner, parameters);
match def {
CallableDefId::FunctionId(ff) => {
write!(f, "fn {}", f.db.function_data(ff).name)?
write!(f, "{}", f.db.enum_data(e.parent).variants[e.local_id].name)?
}
};
- if parameters.len(&Interner) > 0 {
+ if parameters.len(Interner) > 0 {
let generics = generics(f.db.upcast(), def.into());
let (parent_params, self_param, type_params, _impl_trait_params) =
generics.provenance_split();
// We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self?
if total_len > 0 {
write!(f, "<")?;
- f.write_joined(¶meters.as_slice(&Interner)[..total_len], ", ")?;
+ f.write_joined(¶meters.as_slice(Interner)[..total_len], ", ")?;
write!(f, ">")?;
}
}
}
}
- if parameters.len(&Interner) > 0 {
+ if parameters.len(Interner) > 0 {
let parameters_to_write = if f.display_target.is_source_code()
|| f.omit_verbose_types()
{
.map(|generic_def_id| f.db.generic_defaults(generic_def_id))
.filter(|defaults| !defaults.is_empty())
{
- None => parameters.as_slice(&Interner),
+ None => parameters.as_slice(Interner),
Some(default_parameters) => {
let mut default_from = 0;
- for (i, parameter) in parameters.iter(&Interner).enumerate() {
+ for (i, parameter) in parameters.iter(Interner).enumerate() {
match (
- parameter.assert_ty_ref(&Interner).kind(&Interner),
+ parameter.assert_ty_ref(Interner).kind(Interner),
default_parameters.get(i),
) {
(&TyKind::Error, _) | (_, None) => {
default_from = i + 1;
}
(_, Some(default_parameter)) => {
- let actual_default =
- default_parameter.clone().substitute(
- &Interner,
- &subst_prefix(parameters, i),
- );
- if parameter.assert_ty_ref(&Interner) != &actual_default
+ let actual_default = default_parameter
+ .clone()
+ .substitute(Interner, &subst_prefix(parameters, i));
+ if parameter.assert_ty_ref(Interner) != &actual_default
{
default_from = i + 1;
}
}
}
}
- ¶meters.as_slice(&Interner)[0..default_from]
+ ¶meters.as_slice(Interner)[0..default_from]
}
}
} else {
- parameters.as_slice(&Interner)
+ parameters.as_slice(Interner)
};
if !parameters_to_write.is_empty() {
write!(f, "<")?;
- f.write_joined(parameters_to_write, ", ")?;
+
+ if f.display_target.is_source_code() {
+ let mut first = true;
+ for generic_arg in parameters_to_write {
+ if !first {
+ write!(f, ", ")?;
+ }
+ first = false;
+
+ if generic_arg.ty(Interner).map(|ty| ty.kind(Interner))
+ == Some(&TyKind::Error)
+ {
+ write!(f, "_")?;
+ } else {
+ generic_arg.hir_fmt(f)?;
+ }
+ }
+ } else {
+ f.write_joined(parameters_to_write, ", ")?;
+ }
+
write!(f, ">")?;
}
}
TyKind::AssociatedType(assoc_type_id, parameters) => {
let type_alias = from_assoc_type_id(*assoc_type_id);
let trait_ = match type_alias.lookup(f.db.upcast()).container {
- AssocContainerId::TraitId(it) => it,
+ ItemContainerId::TraitId(it) => it,
_ => panic!("not an associated type"),
};
let trait_ = f.db.trait_data(trait_);
// Use placeholder associated types when the target is test (https://rust-lang.github.io/chalk/book/clauses/type_equality.html#placeholder-associated-types)
if f.display_target.is_test() {
write!(f, "{}::{}", trait_.name, type_alias_data.name)?;
- if parameters.len(&Interner) > 0 {
+ if parameters.len(Interner) > 0 {
write!(f, "<")?;
- f.write_joined(&*parameters.as_slice(&Interner), ", ")?;
+ f.write_joined(&*parameters.as_slice(Interner), ", ")?;
write!(f, ">")?;
}
} else {
let data = (*datas)
.as_ref()
.map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
- let bounds = data.substitute(&Interner, ¶meters);
- write_bounds_like_dyn_trait_with_prefix("impl", bounds.skip_binders(), f)?;
+ let bounds = data.substitute(Interner, ¶meters);
+ let krate = func.lookup(f.db.upcast()).module(f.db.upcast()).krate();
+ write_bounds_like_dyn_trait_with_prefix(
+ "impl",
+ bounds.skip_binders(),
+ SizedByDefault::Sized { anchor: krate },
+ f,
+ )?;
// FIXME: it would maybe be good to distinguish this from the alias type (when debug printing), and to show the substitution
}
ImplTraitId::AsyncBlockTypeImplTrait(..) => {
write!(f, "impl Future<Output = ")?;
- parameters.at(&Interner, 0).hir_fmt(f)?;
+ parameters.at(Interner, 0).hir_fmt(f)?;
write!(f, ">")?;
}
}
DisplaySourceCodeError::Closure,
));
}
- let sig = substs.at(&Interner, 0).assert_ty_ref(&Interner).callable_sig(f.db);
+ let sig = substs.at(Interner, 0).assert_ty_ref(Interner).callable_sig(f.db);
if let Some(sig) = sig {
if sig.params().is_empty() {
write!(f, "||")?;
- } else if f.omit_verbose_types() {
+ } else if f.should_truncate() {
write!(f, "|{}|", TYPE_HINT_TRUNCATION)?;
} else {
write!(f, "|")?;
let substs = generics.type_params_subst(f.db);
let bounds =
f.db.generic_predicates(id.parent)
- .into_iter()
- .map(|pred| pred.clone().substitute(&Interner, &substs))
+ .iter()
+ .map(|pred| pred.clone().substitute(Interner, &substs))
.filter(|wc| match &wc.skip_binders() {
WhereClause::Implemented(tr) => {
- &tr.self_type_parameter(&Interner) == self
+ &tr.self_type_parameter(Interner) == self
}
WhereClause::AliasEq(AliasEq {
alias: AliasTy::Projection(proj),
ty: _,
- }) => &proj.self_type_parameter(&Interner) == self,
+ }) => &proj.self_type_parameter(Interner) == self,
_ => false,
})
.collect::<Vec<_>>();
- write_bounds_like_dyn_trait_with_prefix("impl", &bounds, f)?;
+ let krate = id.parent.module(f.db.upcast()).krate();
+ write_bounds_like_dyn_trait_with_prefix(
+ "impl",
+ &bounds,
+ SizedByDefault::Sized { anchor: krate },
+ f,
+ )?;
}
}
}
write_bounds_like_dyn_trait_with_prefix(
"dyn",
dyn_ty.bounds.skip_binders().interned(),
+ SizedByDefault::NotSized,
f,
)?;
}
let data = (*datas)
.as_ref()
.map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
- let bounds = data.substitute(&Interner, &opaque_ty.substitution);
- write_bounds_like_dyn_trait_with_prefix("impl", bounds.skip_binders(), f)?;
+ let bounds = data.substitute(Interner, &opaque_ty.substitution);
+ let krate = func.lookup(f.db.upcast()).module(f.db.upcast()).krate();
+ write_bounds_like_dyn_trait_with_prefix(
+ "impl",
+ bounds.skip_binders(),
+ SizedByDefault::Sized { anchor: krate },
+ f,
+ )?;
}
ImplTraitId::AsyncBlockTypeImplTrait(..) => {
write!(f, "{{async block}}")?;
fn fn_traits(db: &dyn DefDatabase, trait_: TraitId) -> impl Iterator<Item = TraitId> {
let krate = trait_.lookup(db).container.krate();
- let fn_traits = [
- db.lang_item(krate, "fn".into()),
- db.lang_item(krate, "fn_mut".into()),
- db.lang_item(krate, "fn_once".into()),
- ];
- array::IntoIter::new(fn_traits).into_iter().flatten().flat_map(|it| it.as_trait())
+ utils::fn_traits(db, krate)
+}
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum SizedByDefault {
+ NotSized,
+ Sized { anchor: CrateId },
+}
+
+impl SizedByDefault {
+ fn is_sized_trait(self, trait_: TraitId, db: &dyn DefDatabase) -> bool {
+ match self {
+ Self::NotSized => false,
+ Self::Sized { anchor } => {
+ let sized_trait = db
+ .lang_item(anchor, SmolStr::new_inline("sized"))
+ .and_then(|lang_item| lang_item.as_trait());
+ Some(trait_) == sized_trait
+ }
+ }
+ }
}
pub fn write_bounds_like_dyn_trait_with_prefix(
prefix: &str,
predicates: &[QuantifiedWhereClause],
+ default_sized: SizedByDefault,
f: &mut HirFormatter,
) -> Result<(), HirDisplayError> {
write!(f, "{}", prefix)?;
- if !predicates.is_empty() {
+ if !predicates.is_empty()
+ || predicates.is_empty() && matches!(default_sized, SizedByDefault::Sized { .. })
+ {
write!(f, " ")?;
- write_bounds_like_dyn_trait(predicates, f)
+ write_bounds_like_dyn_trait(predicates, default_sized, f)
} else {
Ok(())
}
fn write_bounds_like_dyn_trait(
predicates: &[QuantifiedWhereClause],
+ default_sized: SizedByDefault,
f: &mut HirFormatter,
) -> Result<(), HirDisplayError> {
// Note: This code is written to produce nice results (i.e.
let mut first = true;
let mut angle_open = false;
let mut is_fn_trait = false;
+ let mut is_sized = false;
for p in predicates.iter() {
match p.skip_binders() {
WhereClause::Implemented(trait_ref) => {
let trait_ = trait_ref.hir_trait_id();
+ if default_sized.is_sized_trait(trait_, f.db.upcast()) {
+ is_sized = true;
+ if matches!(default_sized, SizedByDefault::Sized { .. }) {
+ // Don't print +Sized, but rather +?Sized if absent.
+ continue;
+ }
+ }
if !is_fn_trait {
is_fn_trait = fn_traits(f.db.upcast(), trait_).any(|it| it == trait_);
}
// existential) here, which is the only thing that's
// possible in actual Rust, and hence don't print it
write!(f, "{}", f.db.trait_data(trait_).name)?;
- if let [_, params @ ..] = &*trait_ref.substitution.as_slice(&Interner) {
+ if let [_, params @ ..] = &*trait_ref.substitution.as_slice(Interner) {
if is_fn_trait {
if let Some(args) =
- params.first().and_then(|it| it.assert_ty_ref(&Interner).as_tuple())
+ params.first().and_then(|it| it.assert_ty_ref(Interner).as_tuple())
{
write!(f, "(")?;
- f.write_joined(args.as_slice(&Interner), ", ")?;
+ f.write_joined(args.as_slice(Interner), ", ")?;
write!(f, ")")?;
}
} else if !params.is_empty() {
if angle_open {
write!(f, ">")?;
}
+ if matches!(default_sized, SizedByDefault::Sized { .. }) {
+ if !is_sized {
+ write!(f, "{}?Sized", if first { "" } else { " + " })?;
+ } else if first {
+ write!(f, "Sized")?;
+ }
+ }
Ok(())
}
return write!(f, "{}", TYPE_HINT_TRUNCATION);
}
- tr.self_type_parameter(&Interner).hir_fmt(f)?;
+ tr.self_type_parameter(Interner).hir_fmt(f)?;
if use_as {
write!(f, " as ")?;
} else {
write!(f, ": ")?;
}
write!(f, "{}", f.db.trait_data(tr.hir_trait_id()).name)?;
- if tr.substitution.len(&Interner) > 1 {
+ if tr.substitution.len(Interner) > 1 {
write!(f, "<")?;
- f.write_joined(&tr.substitution.as_slice(&Interner)[1..], ", ")?;
+ f.write_joined(&tr.substitution.as_slice(Interner)[1..], ", ")?;
write!(f, ">")?;
}
Ok(())
inner.hir_fmt(f)?;
write!(f, "]")?;
}
- TypeRef::Fn(tys, is_varargs) => {
+ TypeRef::Fn(parameters, is_varargs) => {
// FIXME: Function pointer qualifiers.
write!(f, "fn(")?;
- f.write_joined(&tys[..tys.len() - 1], ", ")?;
+ for index in 0..parameters.len() - 1 {
+ let (param_name, param_type) = ¶meters[index];
+ if let Some(name) = param_name {
+ write!(f, "{}: ", name)?;
+ }
+
+ param_type.hir_fmt(f)?;
+
+ // Last index contains the return type so we stop writing commas on the second-to-last index
+ if index != parameters.len() - 2 {
+ write!(f, ", ")?;
+ }
+ }
if *is_varargs {
- write!(f, "{}...", if tys.len() == 1 { "" } else { ", " })?;
+ write!(f, "{}...", if parameters.len() == 1 { "" } else { ", " })?;
}
write!(f, ")")?;
- let ret_ty = tys.last().unwrap();
- match ret_ty {
- TypeRef::Tuple(tup) if tup.is_empty() => {}
- _ => {
- write!(f, " -> ")?;
- ret_ty.hir_fmt(f)?;
+ if let Some((_, ret_ty)) = ¶meters.last() {
+ match ret_ty {
+ TypeRef::Tuple(tup) if tup.is_empty() => {}
+ _ => {
+ write!(f, " -> ")?;
+ ret_ty.hir_fmt(f)?;
+ }
}
}
}
impl HirDisplay for TypeBound {
fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
match self {
- TypeBound::Path(path) => path.hir_fmt(f),
+ TypeBound::Path(path, modifier) => {
+ match modifier {
+ TraitBoundModifier::None => (),
+ TraitBoundModifier::Maybe => write!(f, "?")?,
+ }
+ path.hir_fmt(f)
+ }
TypeBound::Lifetime(lifetime) => write!(f, "{}", lifetime.name),
+ TypeBound::ForLifetime(lifetimes, path) => {
+ write!(f, "for<{}> ", lifetimes.iter().format(", "))?;
+ path.hir_fmt(f)
+ }
TypeBound::Error => write!(f, "{{error}}"),
}
}
write!(f, ">")?;
}
(_, PathKind::Plain) => {}
- (_, PathKind::Abs) => write!(f, "::")?,
+ (_, PathKind::Abs) => {}
(_, PathKind::Crate) => write!(f, "crate")?,
(_, PathKind::Super(0)) => write!(f, "self")?,
(_, PathKind::Super(n)) => {
- write!(f, "super")?;
- for _ in 0..*n {
- write!(f, "::super")?;
+ for i in 0..*n {
+ if i > 0 {
+ write!(f, "::")?;
+ }
+ write!(f, "super")?;
}
}
(_, PathKind::DollarCrate(_)) => write!(f, "{{extern_crate}}")?,
}
for (seg_idx, segment) in self.segments().iter().enumerate() {
- if seg_idx != 0 {
+ if !matches!(self.kind(), PathKind::Plain) || seg_idx > 0 {
write!(f, "::")?;
}
write!(f, "{}", segment.name)?;
if let Some(generic_args) = segment.args_and_bindings {
// We should be in type context, so format as `Foo<Bar>` instead of `Foo::<Bar>`.
// Do we actually format expressions?
+ if generic_args.desugared_from_fn {
+ // 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 `,`.
+ if let hir_def::path::GenericArg::Type(TypeRef::Tuple(v)) =
+ &generic_args.args[0]
+ {
+ if v.len() == 1 {
+ write!(f, "(")?;
+ v[0].hir_fmt(f)?;
+ write!(f, ")")?;
+ } else {
+ generic_args.args[0].hir_fmt(f)?;
+ }
+ }
+ if let Some(ret) = &generic_args.bindings[0].type_ref {
+ if !matches!(ret, TypeRef::Tuple(v) if v.is_empty()) {
+ write!(f, " -> ")?;
+ ret.hir_fmt(f)?;
+ }
+ }
+ return Ok(());
+ }
+
write!(f, "<")?;
let mut first = true;
for arg in &generic_args.args {