Skip to content

Commit d67b616

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

File tree

2 files changed

+109
-129
lines changed

2 files changed

+109
-129
lines changed

Diff for: compiler/rustc_lint/src/builtin.rs

+48-43
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,10 @@ use crate::{
3232
BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc,
3333
BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns,
3434
BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasBounds,
35-
BuiltinTypeAliasParamBoundsSuggestion, BuiltinUngatedAsyncFnTrackCaller,
36-
BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub,
37-
BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment,
38-
BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel,
39-
TypeAliasBoundsQualifyAssocTysSugg,
35+
BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
36+
BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe,
37+
BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
38+
BuiltinWhileTrue, InvalidAsmLabel,
4039
},
4140
EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext,
4241
};
@@ -1406,9 +1405,23 @@ declare_lint_pass!(
14061405
TypeAliasBounds => [TYPE_ALIAS_BOUNDS]
14071406
);
14081407

1408+
impl TypeAliasBounds {
1409+
pub(crate) fn affects_object_lifetime_defaults(pred: &hir::WherePredicate<'_>) -> bool {
1410+
// Bounds of the form `T: 'a` with `T` type param affect object lifetime defaults.
1411+
if let hir::WherePredicate::BoundPredicate(pred) = pred
1412+
&& pred.bounds.iter().any(|bound| matches!(bound, hir::GenericBound::Outlives(_)))
1413+
&& pred.bound_generic_params.is_empty() // indeed, even if absent from the RHS
1414+
&& pred.bounded_ty.as_generic_param().is_some()
1415+
{
1416+
return true;
1417+
}
1418+
false
1419+
}
1420+
}
1421+
14091422
impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
14101423
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
1411-
let hir::ItemKind::TyAlias(hir_ty, generics) = &item.kind else { return };
1424+
let hir::ItemKind::TyAlias(hir_ty, generics) = item.kind else { return };
14121425

14131426
// There must not be a where clause.
14141427
if generics.predicates.is_empty() {
@@ -1437,7 +1450,6 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
14371450
let mut where_spans = Vec::new();
14381451
let mut inline_spans = Vec::new();
14391452
let mut inline_sugg = Vec::new();
1440-
let mut affects_object_lifetime_defaults = false;
14411453

14421454
for p in generics.predicates {
14431455
let span = p.span();
@@ -1449,66 +1461,59 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
14491461
}
14501462
inline_sugg.push((span, String::new()));
14511463
}
1452-
1453-
// FIXME(fmease): Move this into a "diagnostic decorator" for increased laziness
1454-
// Bounds of the form `T: 'a` where `T` is a type param of
1455-
// the type alias affect object lifetime defaults.
1456-
if !affects_object_lifetime_defaults
1457-
&& let hir::WherePredicate::BoundPredicate(pred) = p
1458-
&& pred.bounds.iter().any(|bound| matches!(bound, hir::GenericBound::Outlives(_)))
1459-
&& pred.bound_generic_params.is_empty()
1460-
&& let hir::TyKind::Path(hir::QPath::Resolved(None, path)) = pred.bounded_ty.kind
1461-
&& let Res::Def(DefKind::TyParam, _) = path.res
1462-
{
1463-
affects_object_lifetime_defaults = true;
1464-
}
14651464
}
14661465

1467-
// FIXME(fmease): Add a disclaimer (in the form of a multi-span note) that the removal of
1468-
// type-param-outlives-bounds affects OLDs and explicit object lifetime
1469-
// bounds might be required [...].
1470-
// FIXME(fmease): The applicability should also depend on the outcome of the HIR walker
1471-
// inside of `TypeAliasBoundsQualifyAssocTysSugg`: Whether it found a
1472-
// shorthand projection or not.
1473-
let applicability = if affects_object_lifetime_defaults {
1474-
Applicability::MaybeIncorrect
1475-
} else {
1476-
Applicability::MachineApplicable
1477-
};
1478-
1479-
let mut qualify_assoc_tys_sugg = Some(TypeAliasBoundsQualifyAssocTysSugg { ty: hir_ty });
1480-
let enable_feat_help = cx.tcx.sess.is_nightly_build().then_some(());
1466+
let mut ty = Some(hir_ty);
1467+
let enable_feat_help = cx.tcx.sess.is_nightly_build();
14811468

14821469
if let [.., label_sp] = *where_spans {
14831470
cx.emit_span_lint(
14841471
TYPE_ALIAS_BOUNDS,
14851472
where_spans,
1486-
BuiltinTypeAliasBounds::WhereClause {
1473+
BuiltinTypeAliasBounds {
1474+
in_where_clause: true,
14871475
label: label_sp,
14881476
enable_feat_help,
1489-
suggestion: (generics.where_clause_span, applicability),
1490-
qualify_assoc_tys_sugg: qualify_assoc_tys_sugg.take(),
1477+
suggestions: vec![(generics.where_clause_span, String::new())],
1478+
preds: generics.predicates,
1479+
ty: ty.take(),
14911480
},
14921481
);
14931482
}
14941483
if let [.., label_sp] = *inline_spans {
14951484
cx.emit_span_lint(
14961485
TYPE_ALIAS_BOUNDS,
14971486
inline_spans,
1498-
BuiltinTypeAliasBounds::ParamBounds {
1487+
BuiltinTypeAliasBounds {
1488+
in_where_clause: false,
14991489
label: label_sp,
15001490
enable_feat_help,
1501-
suggestion: BuiltinTypeAliasParamBoundsSuggestion {
1502-
suggestions: inline_sugg,
1503-
applicability,
1504-
},
1505-
qualify_assoc_tys_sugg,
1491+
suggestions: inline_sugg,
1492+
preds: generics.predicates,
1493+
ty,
15061494
},
15071495
);
15081496
}
15091497
}
15101498
}
15111499

1500+
pub(crate) struct ShorthandAssocTyCollector {
1501+
pub(crate) qselves: Vec<Span>,
1502+
}
1503+
1504+
impl hir::intravisit::Visitor<'_> for ShorthandAssocTyCollector {
1505+
fn visit_qpath(&mut self, qpath: &hir::QPath<'_>, id: hir::HirId, _: Span) {
1506+
// Look for "type-parameter shorthand-associated-types". I.e., paths of the
1507+
// form `T::Assoc` with `T` type param. These are reliant on trait bounds.
1508+
if let hir::QPath::TypeRelative(qself, _) = qpath
1509+
&& qself.as_generic_param().is_some()
1510+
{
1511+
self.qselves.push(qself.span);
1512+
}
1513+
hir::intravisit::walk_qpath(self, qpath, id)
1514+
}
1515+
}
1516+
15121517
declare_lint! {
15131518
/// The `trivial_bounds` lint detects trait bounds that don't depend on
15141519
/// any type parameters.

Diff for: compiler/rustc_lint/src/lints.rs

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

5-
use crate::errors::RequestedLevel;
5+
use crate::builtin::{InitError, ShorthandAssocTyCollector, TypeAliasBounds};
6+
use crate::errors::{OverruledAttributeSub, RequestedLevel};
67
use crate::fluent_generated as fluent;
8+
use crate::LateContext;
79
use rustc_errors::{
810
codes::*, Applicability, Diag, DiagArgValue, DiagMessage, DiagStyledString,
911
ElidedLifetimeInPathSubdiag, EmissionGuarantee, LintDiagnostic, MultiSpan, SubdiagMessageOp,
@@ -22,8 +24,6 @@ use rustc_span::{
2224
Span, Symbol,
2325
};
2426

25-
use crate::{builtin::InitError, errors::OverruledAttributeSub, LateContext};
26-
2727
// array_into_iter.rs
2828
#[derive(LintDiagnostic)]
2929
#[diag(lint_shadowed_into_iter)]
@@ -268,97 +268,72 @@ pub struct MacroExprFragment2024 {
268268
pub suggestion: Span,
269269
}
270270

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

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

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

0 commit comments

Comments
 (0)