Skip to content

Commit f6d96c2

Browse files
committed
Make lint type_alias_bounds's removal sugg maybe-incorrect if the RHS contains shorthand assoc tys
1 parent 6a4e4f3 commit f6d96c2

File tree

2 files changed

+106
-125
lines changed

2 files changed

+106
-125
lines changed

compiler/rustc_lint/src/builtin.rs

+46-41
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,10 @@ use crate::{
3232
BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc,
3333
BuiltinMutablesTransmutes, BuiltinNamedAsmLabel, BuiltinNoMangleGeneric,
3434
BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds,
35-
BuiltinTypeAliasBounds, BuiltinTypeAliasParamBoundsSuggestion,
36-
BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
35+
BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
3736
BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe,
3837
BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
39-
BuiltinWhileTrue, TypeAliasBoundsQualifyAssocTysSugg,
38+
BuiltinWhileTrue,
4039
},
4140
EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext,
4241
};
@@ -1475,9 +1474,23 @@ declare_lint_pass!(
14751474
TypeAliasBounds => [TYPE_ALIAS_BOUNDS]
14761475
);
14771476

1477+
impl TypeAliasBounds {
1478+
pub(crate) fn affects_object_lifetime_defaults(pred: &hir::WherePredicate<'_>) -> bool {
1479+
// Bounds of the form `T: 'a` with `T` type param affect object lifetime defaults.
1480+
if let hir::WherePredicate::BoundPredicate(pred) = pred
1481+
&& pred.bounds.iter().any(|bound| matches!(bound, hir::GenericBound::Outlives(_)))
1482+
&& pred.bound_generic_params.is_empty() // indeed, even if absent from the RHS
1483+
&& pred.bounded_ty.as_generic_param().is_some()
1484+
{
1485+
return true;
1486+
}
1487+
false
1488+
}
1489+
}
1490+
14781491
impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
14791492
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
1480-
let hir::ItemKind::TyAlias(hir_ty, generics) = &item.kind else { return };
1493+
let hir::ItemKind::TyAlias(hir_ty, generics) = item.kind else { return };
14811494

14821495
// There must not be a where clause.
14831496
if generics.predicates.is_empty() {
@@ -1506,7 +1519,6 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
15061519
let mut where_spans = Vec::new();
15071520
let mut inline_spans = Vec::new();
15081521
let mut inline_sugg = Vec::new();
1509-
let mut affects_object_lifetime_defaults = false;
15101522

15111523
for p in generics.predicates {
15121524
let span = p.span();
@@ -1518,66 +1530,59 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
15181530
}
15191531
inline_sugg.push((span, String::new()));
15201532
}
1521-
1522-
// FIXME(fmease): Move this into a "diagnostic decorator" for increased laziness
1523-
// Bounds of the form `T: 'a` where `T` is a type param of
1524-
// the type alias affect object lifetime defaults.
1525-
if !affects_object_lifetime_defaults
1526-
&& let hir::WherePredicate::BoundPredicate(pred) = p
1527-
&& pred.bounds.iter().any(|bound| matches!(bound, hir::GenericBound::Outlives(_)))
1528-
&& pred.bound_generic_params.is_empty()
1529-
&& let hir::TyKind::Path(hir::QPath::Resolved(None, path)) = pred.bounded_ty.kind
1530-
&& let Res::Def(DefKind::TyParam, _) = path.res
1531-
{
1532-
affects_object_lifetime_defaults = true;
1533-
}
15341533
}
15351534

1536-
// FIXME(fmease): Add a disclaimer (in the form of a multi-span note) that the removal of
1537-
// type-param-outlives-bounds affects OLDs and explicit object lifetime
1538-
// bounds might be required [...].
1539-
// FIXME(fmease): The applicability should also depend on the outcome of the HIR walker
1540-
// inside of `TypeAliasBoundsQualifyAssocTysSugg`: Whether it found a
1541-
// shorthand projection or not.
1542-
let applicability = if affects_object_lifetime_defaults {
1543-
Applicability::MaybeIncorrect
1544-
} else {
1545-
Applicability::MachineApplicable
1546-
};
1547-
1548-
let mut qualify_assoc_tys_sugg = Some(TypeAliasBoundsQualifyAssocTysSugg { ty: hir_ty });
1549-
let enable_feat_help = cx.tcx.sess.is_nightly_build().then_some(());
1535+
let mut ty = Some(hir_ty);
1536+
let enable_feat_help = cx.tcx.sess.is_nightly_build();
15501537

15511538
if let [.., label_sp] = *where_spans {
15521539
cx.emit_span_lint(
15531540
TYPE_ALIAS_BOUNDS,
15541541
where_spans,
1555-
BuiltinTypeAliasBounds::WhereClause {
1542+
BuiltinTypeAliasBounds {
1543+
in_where_clause: true,
15561544
label: label_sp,
15571545
enable_feat_help,
1558-
suggestion: (generics.where_clause_span, applicability),
1559-
qualify_assoc_tys_sugg: qualify_assoc_tys_sugg.take(),
1546+
suggestions: vec![(generics.where_clause_span, String::new())],
1547+
preds: generics.predicates,
1548+
ty: ty.take(),
15601549
},
15611550
);
15621551
}
15631552
if let [.., label_sp] = *inline_spans {
15641553
cx.emit_span_lint(
15651554
TYPE_ALIAS_BOUNDS,
15661555
inline_spans,
1567-
BuiltinTypeAliasBounds::ParamBounds {
1556+
BuiltinTypeAliasBounds {
1557+
in_where_clause: false,
15681558
label: label_sp,
15691559
enable_feat_help,
1570-
suggestion: BuiltinTypeAliasParamBoundsSuggestion {
1571-
suggestions: inline_sugg,
1572-
applicability,
1573-
},
1574-
qualify_assoc_tys_sugg,
1560+
suggestions: inline_sugg,
1561+
preds: generics.predicates,
1562+
ty,
15751563
},
15761564
);
15771565
}
15781566
}
15791567
}
15801568

1569+
pub(crate) struct ShorthandAssocTyCollector {
1570+
pub(crate) qselves: Vec<Span>,
1571+
}
1572+
1573+
impl hir::intravisit::Visitor<'_> for ShorthandAssocTyCollector {
1574+
fn visit_qpath(&mut self, qpath: &hir::QPath<'_>, id: hir::HirId, _: Span) {
1575+
// Look for "type-parameter shorthand-associated-types". I.e., paths of the
1576+
// form `T::Assoc` with `T` type param. These are reliant on trait bounds.
1577+
if let hir::QPath::TypeRelative(qself, _) = qpath
1578+
&& qself.as_generic_param().is_some()
1579+
{
1580+
self.qselves.push(qself.span);
1581+
}
1582+
hir::intravisit::walk_qpath(self, qpath, id)
1583+
}
1584+
}
1585+
15811586
declare_lint! {
15821587
/// The `trivial_bounds` lint detects trait bounds that don't depend on
15831588
/// any type parameters.

compiler/rustc_lint/src/lints.rs

+60-84
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
#![allow(rustc::untranslatable_diagnostic)]
33
use std::num::NonZero;
44

5-
use crate::errors::RequestedLevel;
5+
use crate::builtin::TypeAliasBounds;
66
use crate::fluent_generated as fluent;
7+
use crate::{builtin::ShorthandAssocTyCollector, errors::RequestedLevel};
78
use rustc_errors::{
89
codes::*, Applicability, Diag, DiagArgValue, DiagMessage, DiagStyledString,
910
ElidedLifetimeInPathSubdiag, EmissionGuarantee, LintDiagnostic, MultiSpan, SubdiagMessageOp,
@@ -265,97 +266,72 @@ pub struct BuiltinUnreachablePub<'a> {
265266
pub help: Option<()>,
266267
}
267268

268-
#[derive(LintDiagnostic)]
269-
pub enum BuiltinTypeAliasBounds<'a, 'hir> {
270-
#[diag(lint_builtin_type_alias_bounds_where_clause)]
271-
#[note(lint_builtin_type_alias_bounds_limitation_note)]
272-
WhereClause {
273-
#[label(lint_builtin_type_alias_bounds_label)]
274-
label: Span,
275-
#[help(lint_builtin_type_alias_bounds_enable_feat_help)]
276-
enable_feat_help: Option<()>,
277-
#[suggestion(code = "")]
278-
suggestion: (Span, Applicability),
279-
#[subdiagnostic]
280-
qualify_assoc_tys_sugg: Option<TypeAliasBoundsQualifyAssocTysSugg<'a, 'hir>>,
281-
},
282-
#[diag(lint_builtin_type_alias_bounds_param_bounds)]
283-
#[note(lint_builtin_type_alias_bounds_limitation_note)]
284-
ParamBounds {
285-
#[label(lint_builtin_type_alias_bounds_label)]
286-
label: Span,
287-
#[help(lint_builtin_type_alias_bounds_enable_feat_help)]
288-
enable_feat_help: Option<()>,
289-
#[subdiagnostic]
290-
suggestion: BuiltinTypeAliasParamBoundsSuggestion,
291-
#[subdiagnostic]
292-
qualify_assoc_tys_sugg: Option<TypeAliasBoundsQualifyAssocTysSugg<'a, 'hir>>,
293-
},
294-
}
295-
296-
pub struct BuiltinTypeAliasParamBoundsSuggestion {
269+
pub struct BuiltinTypeAliasBounds<'a, 'hir> {
270+
pub in_where_clause: bool,
271+
pub label: Span,
272+
pub enable_feat_help: bool,
297273
pub suggestions: Vec<(Span, String)>,
298-
pub applicability: Applicability,
274+
pub preds: &'hir [hir::WherePredicate<'hir>],
275+
pub ty: Option<&'a hir::Ty<'hir>>,
299276
}
300277

301-
impl Subdiagnostic for BuiltinTypeAliasParamBoundsSuggestion {
302-
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
303-
self,
304-
diag: &mut Diag<'_, G>,
305-
_f: &F,
306-
) {
307-
diag.arg("count", self.suggestions.len());
308-
diag.multipart_suggestion(fluent::lint_suggestion, self.suggestions, self.applicability);
309-
}
310-
}
311-
312-
pub struct TypeAliasBoundsQualifyAssocTysSugg<'a, 'hir> {
313-
pub ty: &'a hir::Ty<'hir>,
314-
}
278+
impl<'a> LintDiagnostic<'a, ()> for BuiltinTypeAliasBounds<'_, '_> {
279+
fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
280+
diag.primary_message(if self.in_where_clause {
281+
fluent::lint_builtin_type_alias_bounds_where_clause
282+
} else {
283+
fluent::lint_builtin_type_alias_bounds_param_bounds
284+
});
285+
diag.span_label(self.label, fluent::lint_builtin_type_alias_bounds_label);
286+
diag.note(fluent::lint_builtin_type_alias_bounds_limitation_note);
287+
if self.enable_feat_help {
288+
diag.help(fluent::lint_builtin_type_alias_bounds_enable_feat_help);
289+
}
315290

316-
impl<'a, 'hir> Subdiagnostic for TypeAliasBoundsQualifyAssocTysSugg<'a, 'hir> {
317-
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
318-
self,
319-
diag: &mut Diag<'_, G>,
320-
_f: &F,
321-
) {
322291
// We perform the walk in here instead of in `<TypeAliasBounds as LateLintPass>` to
323292
// avoid doing throwaway work in case the lint ends up getting suppressed.
324-
325-
use hir::intravisit::Visitor;
326-
struct ProbeShorthandAssocTys<'a, 'b, G: EmissionGuarantee> {
327-
diag: &'a mut Diag<'b, G>,
293+
let mut collector = ShorthandAssocTyCollector { qselves: Vec::new() };
294+
if let Some(ty) = self.ty {
295+
hir::intravisit::Visitor::visit_ty(&mut collector, ty);
328296
}
329-
impl<'a, 'b, G: EmissionGuarantee> Visitor<'_> for ProbeShorthandAssocTys<'a, 'b, G> {
330-
fn visit_qpath(&mut self, qpath: &hir::QPath<'_>, id: hir::HirId, _: Span) {
331-
// Look for "type-parameter shorthand-associated-types". I.e., paths of the
332-
// form `T::Assoc` with `T` type param. These are reliant on trait bounds.
333-
// Suggest fully qualifying them via `<T as /* Trait */>::Assoc`.
334-
//
335-
// Instead of attempting to figure out the necessary trait ref, just use a
336-
// placeholder. Since we don't record type-dependent resolutions for non-body
337-
// items like type aliases, we can't simply deduce the corresp. trait from
338-
// the HIR path alone without rerunning parts of HIR ty lowering here
339-
// (namely `probe_single_ty_param_bound_for_assoc_ty`) which is infeasible.
340-
//
341-
// (We could employ some simple heuristics but that's likely not worth it).
342-
if let hir::QPath::TypeRelative(qself, _) = qpath
343-
&& let hir::TyKind::Path(hir::QPath::Resolved(None, path)) = qself.kind
344-
&& let hir::def::Res::Def(hir::def::DefKind::TyParam, _) = path.res
345-
{
346-
self.diag.multipart_suggestion(
347-
fluent::lint_builtin_type_alias_bounds_qualify_assoc_tys_sugg,
348-
vec![
349-
(qself.span.shrink_to_lo(), "<".into()),
350-
(qself.span.shrink_to_hi(), " as /* Trait */>".into()),
351-
],
352-
Applicability::HasPlaceholders,
353-
);
354-
}
355-
hir::intravisit::walk_qpath(self, qpath, id)
356-
}
297+
298+
let affect_object_lifetime_defaults = self
299+
.preds
300+
.iter()
301+
.filter(|pred| pred.in_where_clause() == self.in_where_clause)
302+
.any(|pred| TypeAliasBounds::affects_object_lifetime_defaults(pred));
303+
304+
// If there are any shorthand assoc tys, then the bounds can't be removed automatically.
305+
// The user first needs to fully qualify the assoc tys.
306+
let applicability = if !collector.qselves.is_empty() || affect_object_lifetime_defaults {
307+
Applicability::MaybeIncorrect
308+
} else {
309+
Applicability::MachineApplicable
310+
};
311+
312+
diag.arg("count", self.suggestions.len());
313+
diag.multipart_suggestion(fluent::lint_suggestion, self.suggestions, applicability);
314+
315+
// Suggest fully qualifying paths of the form `T::Assoc` with `T` type param via
316+
// `<T as /* Trait */>::Assoc` to remove their reliance on any type param bounds.
317+
//
318+
// Instead of attempting to figure out the necessary trait ref, just use a
319+
// placeholder. Since we don't record type-dependent resolutions for non-body
320+
// items like type aliases, we can't simply deduce the corresp. trait from
321+
// the HIR path alone without rerunning parts of HIR ty lowering here
322+
// (namely `probe_single_ty_param_bound_for_assoc_ty`) which is infeasible.
323+
//
324+
// (We could employ some simple heuristics but that's likely not worth it).
325+
for qself in collector.qselves {
326+
diag.multipart_suggestion(
327+
fluent::lint_builtin_type_alias_bounds_qualify_assoc_tys_sugg,
328+
vec![
329+
(qself.shrink_to_lo(), "<".into()),
330+
(qself.shrink_to_hi(), " as /* Trait */>".into()),
331+
],
332+
Applicability::HasPlaceholders,
333+
);
357334
}
358-
ProbeShorthandAssocTys { diag }.visit_ty(self.ty);
359335
}
360336
}
361337

0 commit comments

Comments
 (0)