1
Fork 0

Parse condition options into a struct

This commit is contained in:
mejrs 2025-03-27 23:09:44 +01:00
parent 2007c8994d
commit ba9f51b055
3 changed files with 168 additions and 165 deletions

View file

@ -2,14 +2,13 @@ use std::iter;
use std::path::PathBuf;
use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::codes::*;
use rustc_errors::{ErrorGuaranteed, struct_span_code_err};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{AttrArgs, Attribute};
use rustc_middle::bug;
use rustc_middle::ty::print::PrintTraitRefExt as _;
use rustc_middle::ty::{self, GenericArgsRef, GenericParamDefKind, TyCtxt};
use rustc_middle::ty::print::PrintTraitRefExt;
use rustc_middle::ty::{self, GenericArgsRef, GenericParamDef, GenericParamDefKind, TyCtxt};
use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
use rustc_span::{Span, Symbol, sym};
use tracing::{debug, info};
@ -17,9 +16,9 @@ use {rustc_attr_parsing as attr, rustc_hir as hir};
use super::{ObligationCauseCode, PredicateObligation};
use crate::error_reporting::TypeErrCtxt;
use crate::error_reporting::traits::on_unimplemented_condition::Condition;
use crate::error_reporting::traits::on_unimplemented_condition::{Condition, ConditionOptions};
use crate::error_reporting::traits::on_unimplemented_format::errors::*;
use crate::error_reporting::traits::on_unimplemented_format::{Ctx, FormatString};
use crate::error_reporting::traits::on_unimplemented_format::{Ctx, FormatArgs, FormatString};
use crate::errors::{
EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented,
};
@ -107,86 +106,81 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
.unwrap_or_else(|| (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args));
let trait_pred = trait_pred.skip_binder();
let mut flags = vec![];
let mut self_types = vec![];
let mut generic_args: Vec<(Symbol, String)> = vec![];
let mut crate_local = false;
// FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty): HIR is not present for RPITITs,
// but I guess we could synthesize one here. We don't see any errors that rely on
// that yet, though.
let enclosure = self.describe_enclosure(obligation.cause.body_id).map(|t| t.to_owned());
flags.push((sym::ItemContext, enclosure));
let item_context = self
.describe_enclosure(obligation.cause.body_id)
.map(|t| t.to_owned())
.unwrap_or(String::new());
match obligation.cause.code() {
let direct = match obligation.cause.code() {
ObligationCauseCode::BuiltinDerived(..)
| ObligationCauseCode::ImplDerived(..)
| ObligationCauseCode::WellFormedDerived(..) => {}
| ObligationCauseCode::WellFormedDerived(..) => false,
_ => {
// this is a "direct", user-specified, rather than derived,
// obligation.
flags.push((sym::direct, None));
true
}
}
};
if let Some(k) = obligation.cause.span.desugaring_kind() {
flags.push((sym::from_desugaring, None));
flags.push((sym::from_desugaring, Some(format!("{k:?}"))));
}
let from_desugaring = obligation.cause.span.desugaring_kind().map(|k| format!("{k:?}"));
if let ObligationCauseCode::MainFunctionType = obligation.cause.code() {
flags.push((sym::cause, Some("MainFunctionType".to_string())));
}
flags.push((sym::Trait, Some(trait_pred.trait_ref.print_trait_sugared().to_string())));
let cause = if let ObligationCauseCode::MainFunctionType = obligation.cause.code() {
Some("MainFunctionType".to_string())
} else {
None
};
// Add all types without trimmed paths or visible paths, ensuring they end up with
// their "canonical" def path.
ty::print::with_no_trimmed_paths!(ty::print::with_no_visible_paths!({
let generics = self.tcx.generics_of(def_id);
let self_ty = trait_pred.self_ty();
// This is also included through the generics list as `Self`,
// but the parser won't allow you to use it
flags.push((sym::_Self, Some(self_ty.to_string())));
self_types.push(self_ty.to_string());
if let Some(def) = self_ty.ty_adt_def() {
// We also want to be able to select self's original
// signature with no type arguments resolved
flags.push((
sym::_Self,
Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()),
));
self_types.push(self.tcx.type_of(def.did()).instantiate_identity().to_string());
}
for param in generics.own_params.iter() {
let value = match param.kind {
for GenericParamDef { name, kind, index, .. } in generics.own_params.iter() {
let value = match kind {
GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
args[param.index as usize].to_string()
args[*index as usize].to_string()
}
GenericParamDefKind::Lifetime => continue,
};
let name = param.name;
flags.push((name, Some(value)));
generic_args.push((*name, value));
if let GenericParamDefKind::Type { .. } = param.kind {
let param_ty = args[param.index as usize].expect_ty();
if let GenericParamDefKind::Type { .. } = kind {
let param_ty = args[*index as usize].expect_ty();
if let Some(def) = param_ty.ty_adt_def() {
// We also want to be able to select the parameter's
// original signature with no type arguments resolved
flags.push((
name,
Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()),
generic_args.push((
*name,
self.tcx.type_of(def.did()).instantiate_identity().to_string(),
));
}
}
}
if let Some(true) = self_ty.ty_adt_def().map(|def| def.did().is_local()) {
flags.push((sym::crate_local, None));
crate_local = true;
}
// Allow targeting all integers using `{integral}`, even if the exact type was resolved
if self_ty.is_integral() {
flags.push((sym::_Self, Some("{integral}".to_owned())));
self_types.push("{integral}".to_owned());
}
if self_ty.is_array_slice() {
flags.push((sym::_Self, Some("&[]".to_owned())));
self_types.push("&[]".to_owned());
}
if self_ty.is_fn() {
@ -201,53 +195,51 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
hir::Safety::Unsafe => "unsafe fn",
}
};
flags.push((sym::_Self, Some(shortname.to_owned())));
self_types.push(shortname.to_owned());
}
// Slices give us `[]`, `[{ty}]`
if let ty::Slice(aty) = self_ty.kind() {
flags.push((sym::_Self, Some("[]".to_string())));
self_types.push("[]".to_owned());
if let Some(def) = aty.ty_adt_def() {
// We also want to be able to select the slice's type's original
// signature with no type arguments resolved
flags.push((
sym::_Self,
Some(format!("[{}]", self.tcx.type_of(def.did()).instantiate_identity())),
));
self_types
.push(format!("[{}]", self.tcx.type_of(def.did()).instantiate_identity()));
}
if aty.is_integral() {
flags.push((sym::_Self, Some("[{integral}]".to_string())));
self_types.push("[{integral}]".to_string());
}
}
// Arrays give us `[]`, `[{ty}; _]` and `[{ty}; N]`
if let ty::Array(aty, len) = self_ty.kind() {
flags.push((sym::_Self, Some("[]".to_string())));
self_types.push("[]".to_string());
let len = len.try_to_target_usize(self.tcx);
flags.push((sym::_Self, Some(format!("[{aty}; _]"))));
self_types.push(format!("[{aty}; _]"));
if let Some(n) = len {
flags.push((sym::_Self, Some(format!("[{aty}; {n}]"))));
self_types.push(format!("[{aty}; {n}]"));
}
if let Some(def) = aty.ty_adt_def() {
// We also want to be able to select the array's type's original
// signature with no type arguments resolved
let def_ty = self.tcx.type_of(def.did()).instantiate_identity();
flags.push((sym::_Self, Some(format!("[{def_ty}; _]"))));
self_types.push(format!("[{def_ty}; _]"));
if let Some(n) = len {
flags.push((sym::_Self, Some(format!("[{def_ty}; {n}]"))));
self_types.push(format!("[{def_ty}; {n}]"));
}
}
if aty.is_integral() {
flags.push((sym::_Self, Some("[{integral}; _]".to_string())));
self_types.push("[{integral}; _]".to_string());
if let Some(n) = len {
flags.push((sym::_Self, Some(format!("[{{integral}}; {n}]"))));
self_types.push(format!("[{{integral}}; {n}]"));
}
}
}
if let ty::Dynamic(traits, _, _) = self_ty.kind() {
for t in traits.iter() {
if let ty::ExistentialPredicate::Trait(trait_ref) = t.skip_binder() {
flags.push((sym::_Self, Some(self.tcx.def_path_str(trait_ref.def_id))))
self_types.push(self.tcx.def_path_str(trait_ref.def_id));
}
}
}
@ -257,14 +249,51 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
&& let ty::Slice(sty) = ref_ty.kind()
&& sty.is_integral()
{
flags.push((sym::_Self, Some("&[{integral}]".to_owned())));
self_types.push("&[{integral}]".to_owned());
}
}));
flags.push((sym::This, Some(self.tcx.def_path_str(trait_pred.trait_ref.def_id))));
let this = self.tcx.def_path_str(trait_pred.trait_ref.def_id).to_string();
let trait_sugared = trait_pred.trait_ref.print_trait_sugared().to_string();
let condition_options = ConditionOptions {
self_types,
from_desugaring,
cause,
crate_local,
direct,
generic_args,
};
// Unlike the generic_args earlier,
// this one is *not* collected under `with_no_trimmed_paths!`
// for printing the type to the user
let generic_args = self
.tcx
.generics_of(trait_pred.trait_ref.def_id)
.own_params
.iter()
.filter_map(|param| {
let value = match param.kind {
GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
if let Some(ty) = trait_pred.trait_ref.args[param.index as usize].as_type()
{
self.tcx.short_string(ty, long_ty_file)
} else {
trait_pred.trait_ref.args[param.index as usize].to_string()
}
}
GenericParamDefKind::Lifetime => return None,
};
let name = param.name;
Some((name, value))
})
.collect();
let format_args = FormatArgs { this, trait_sugared, generic_args, item_context };
if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, def_id) {
command.evaluate(self.tcx, trait_pred.trait_ref, &flags, long_ty_file)
command.evaluate(self.tcx, trait_pred.trait_ref, &condition_options, &format_args)
} else {
OnUnimplementedNote::default()
}
@ -634,23 +663,23 @@ impl<'tcx> OnUnimplementedDirective {
&self,
tcx: TyCtxt<'tcx>,
trait_ref: ty::TraitRef<'tcx>,
options: &[(Symbol, Option<String>)],
long_ty_file: &mut Option<PathBuf>,
condition_options: &ConditionOptions,
args: &FormatArgs,
) -> OnUnimplementedNote {
let mut message = None;
let mut label = None;
let mut notes = Vec::new();
let mut parent_label = None;
let mut append_const_msg = None;
info!("evaluate({:?}, trait_ref={:?}, options={:?})", self, trait_ref, options);
let options_map: FxHashMap<Symbol, String> =
options.iter().filter_map(|(k, v)| v.clone().map(|v| (*k, v))).collect();
info!(
"evaluate({:?}, trait_ref={:?}, options={:?}, args ={:?})",
self, trait_ref, condition_options, args
);
for command in self.subcommands.iter().chain(Some(self)).rev() {
debug!(?command);
if let Some(ref condition) = command.condition
&& !condition.matches_predicate(tcx, options, &options_map)
&& !condition.matches_predicate(tcx, condition_options)
{
debug!("evaluate: skipping {:?} due to condition", command);
continue;
@ -674,14 +703,10 @@ impl<'tcx> OnUnimplementedDirective {
}
OnUnimplementedNote {
label: label.map(|l| l.1.format(tcx, trait_ref, &options_map, long_ty_file)),
message: message.map(|m| m.1.format(tcx, trait_ref, &options_map, long_ty_file)),
notes: notes
.into_iter()
.map(|n| n.format(tcx, trait_ref, &options_map, long_ty_file))
.collect(),
parent_label: parent_label
.map(|e_s| e_s.format(tcx, trait_ref, &options_map, long_ty_file)),
label: label.map(|l| l.1.format(tcx, trait_ref, args)),
message: message.map(|m| m.1.format(tcx, trait_ref, args)),
notes: notes.into_iter().map(|n| n.format(tcx, trait_ref, args)).collect(),
parent_label: parent_label.map(|e_s| e_s.format(tcx, trait_ref, args)),
append_const_msg,
}
}
@ -759,8 +784,7 @@ impl<'tcx> OnUnimplementedFormatString {
&self,
tcx: TyCtxt<'tcx>,
trait_ref: ty::TraitRef<'tcx>,
options: &FxHashMap<Symbol, String>,
long_ty_file: &mut Option<PathBuf>,
args: &FormatArgs,
) -> String {
let trait_def_id = trait_ref.def_id;
let ctx = if self.is_diagnostic_namespace_variant {
@ -770,7 +794,7 @@ impl<'tcx> OnUnimplementedFormatString {
};
if let Ok(s) = FormatString::parse(self.symbol, self.span, &ctx) {
s.format(tcx, trait_ref, options, long_ty_file)
s.format(args)
} else {
// we cannot return errors from processing the format string as hard error here
// as the diagnostic namespace guarantees that malformed input cannot cause an error

View file

@ -1,9 +1,8 @@
use rustc_ast::MetaItemInner;
use rustc_attr_parsing as attr;
use rustc_data_structures::fx::FxHashMap;
use rustc_middle::ty::{self, TyCtxt};
use rustc_parse_format::{ParseMode, Parser, Piece, Position};
use rustc_span::{Span, Symbol, sym};
use rustc_span::{Span, Symbol, kw, sym};
pub static ALLOWED_CONDITION_SYMBOLS: &[Symbol] = &[
sym::from_desugaring,
@ -26,12 +25,7 @@ impl Condition {
self.inner.span()
}
pub fn matches_predicate<'tcx>(
&self,
tcx: TyCtxt<'tcx>,
options: &[(Symbol, Option<String>)],
options_map: &FxHashMap<Symbol, String>,
) -> bool {
pub fn matches_predicate<'tcx>(&self, tcx: TyCtxt<'tcx>, options: &ConditionOptions) -> bool {
attr::eval_condition(&self.inner, tcx.sess, Some(tcx.features()), &mut |cfg| {
let value = cfg.value.map(|v| {
// `with_no_visible_paths` is also used when generating the options,
@ -44,8 +38,8 @@ impl Condition {
Piece::NextArgument(a) => match a.position {
Position::ArgumentNamed(arg) => {
let s = Symbol::intern(arg);
match options_map.get(&s) {
Some(val) => val.to_string(),
match options.generic_args.iter().find(|(k, _)| *k == s) {
Some((_, val)) => val.to_string(),
None => format!("{{{arg}}}"),
}
}
@ -58,7 +52,36 @@ impl Condition {
})
});
options.contains(&(cfg.name, value))
options.contains(cfg.name, &value)
})
}
}
#[derive(Debug)]
pub struct ConditionOptions {
pub self_types: Vec<String>,
pub from_desugaring: Option<String>,
pub cause: Option<String>,
pub crate_local: bool,
pub direct: bool,
pub generic_args: Vec<(Symbol, String)>,
}
impl ConditionOptions {
pub fn contains(&self, key: Symbol, value: &Option<String>) -> bool {
match (key, value) {
(sym::_Self | kw::SelfUpper, Some(value)) => self.self_types.contains(&value),
// from_desugaring as a flag
(sym::from_desugaring, None) => self.from_desugaring.is_some(),
// from_desugaring as key == value
(sym::from_desugaring, v) => *v == self.from_desugaring,
(sym::cause, Some(value)) => self.cause.as_deref() == Some(value),
(sym::crate_local, None) => self.crate_local,
(sym::direct, None) => self.direct,
(other, Some(value)) => {
self.generic_args.iter().any(|(k, v)| *k == other && v == value)
}
_ => false,
}
}
}

View file

@ -1,10 +1,5 @@
use std::fmt::Write;
use std::path::PathBuf;
use errors::*;
use rustc_data_structures::fx::FxHashMap;
use rustc_middle::span_bug;
use rustc_middle::ty::{self, GenericParamDefKind, TyCtxt};
use rustc_middle::ty::TyCtxt;
use rustc_parse_format::{
Alignment, Argument, Count, FormatSpec, InnerSpan, ParseError, ParseMode, Parser,
Piece as RpfPiece, Position,
@ -13,6 +8,7 @@ use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
use rustc_span::def_id::DefId;
use rustc_span::{BytePos, Pos, Span, Symbol, kw, sym};
#[allow(dead_code)]
pub struct FormatString {
input: Symbol,
input_span: Span,
@ -99,6 +95,24 @@ impl FormatWarning {
}
}
#[derive(Debug)]
pub struct ConditionOptions {
pub self_types: Vec<String>,
pub from_desugaring: Option<String>,
pub cause: Option<String>,
pub crate_local: bool,
pub direct: bool,
pub generic_args: Vec<(Symbol, String)>,
}
#[derive(Debug)]
pub struct FormatArgs {
pub this: String,
pub trait_sugared: String,
pub item_context: String,
pub generic_args: Vec<(Symbol, String)>,
}
impl FormatString {
pub fn parse(input: Symbol, input_span: Span, ctx: &Ctx<'_>) -> Result<Self, Vec<ParseError>> {
let s = input.as_str();
@ -126,92 +140,34 @@ impl FormatString {
}
}
pub fn format<'tcx>(
&self,
tcx: TyCtxt<'tcx>,
trait_ref: ty::TraitRef<'tcx>,
options: &FxHashMap<Symbol, String>,
long_ty_file: &mut Option<PathBuf>,
) -> String {
let generics = tcx.generics_of(trait_ref.def_id);
let generic_map = generics
.own_params
.iter()
.filter_map(|param| {
let value = match param.kind {
GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
if let Some(ty) = trait_ref.args[param.index as usize].as_type() {
tcx.short_string(ty, long_ty_file)
} else {
trait_ref.args[param.index as usize].to_string()
}
}
GenericParamDefKind::Lifetime => return None,
};
let name = param.name;
Some((name, value))
})
.collect::<FxHashMap<Symbol, String>>();
pub fn format(&self, args: &FormatArgs) -> String {
let mut ret = String::new();
for piece in &self.pieces {
match piece {
Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(&s),
// `A` if we have `trait Trait<A> {}` and `note = "i'm the actual type of {A}"`
Piece::Arg(FormatArg::GenericParam { generic_param, span }) => {
Piece::Arg(FormatArg::GenericParam { generic_param, .. }) => {
// Should always be some but we can't raise errors here
if let Some(value) = generic_map.get(&generic_param) {
ret.push_str(value);
} else if cfg!(debug_assertions) {
span_bug!(*span, "invalid generic parameter");
} else {
let _ = ret.write_fmt(format_args!("{{{}}}", generic_param.as_str()));
}
let value = match args.generic_args.iter().find(|(p, _)| p == generic_param) {
Some((_, val)) => val.to_string(),
None => generic_param.to_string(),
};
ret.push_str(&value);
}
// `{Self}`
Piece::Arg(FormatArg::SelfUpper) => {
let Some(slf) = generic_map.get(&kw::SelfUpper) else {
span_bug!(
self.input_span,
"broken format string {:?} for {:?}: \
no argument matching `Self`",
self.input,
trait_ref,
)
let slf = match args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) {
Some((_, val)) => val.to_string(),
None => "Self".to_string(),
};
ret.push_str(&slf);
}
// It's only `rustc_onunimplemented` from here
Piece::Arg(FormatArg::This) => {
let Some(this) = options.get(&sym::This) else {
span_bug!(
self.input_span,
"broken format string {:?} for {:?}: \
no argument matching This",
self.input,
trait_ref,
)
};
ret.push_str(this);
}
Piece::Arg(FormatArg::Trait) => {
let Some(this) = options.get(&sym::Trait) else {
span_bug!(
self.input_span,
"broken format string {:?} for {:?}: \
no argument matching Trait",
self.input,
trait_ref,
)
};
ret.push_str(this);
}
Piece::Arg(FormatArg::ItemContext) => {
let itemcontext = options.get(&sym::ItemContext);
ret.push_str(itemcontext.unwrap_or(&String::new()));
}
Piece::Arg(FormatArg::This) => ret.push_str(&args.this),
Piece::Arg(FormatArg::Trait) => ret.push_str(&args.trait_sugared),
Piece::Arg(FormatArg::ItemContext) => ret.push_str(&args.item_context),
}
}
ret