Skip to content

Commit fdf8f02

Browse files
committed
Improve the impl and diag output of lint type_alias_bounds
1 parent a8b3dfd commit fdf8f02

13 files changed

+401
-335
lines changed

compiler/rustc_lint/messages.ftl

+12-7
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,18 @@ lint_builtin_special_module_name_used_main = found module declaration for main.r
139139
140140
lint_builtin_trivial_bounds = {$predicate_kind_name} bound {$predicate} does not depend on any type or lifetime parameters
141141
142-
lint_builtin_type_alias_bounds_help = use fully disambiguated paths (i.e., `<T as Trait>::Assoc`) to refer to associated types in type aliases
143-
144-
lint_builtin_type_alias_generic_bounds = bounds on generic parameters are not enforced in type aliases
145-
.suggestion = the bound will not be checked when the type alias is used, and should be removed
146-
147-
lint_builtin_type_alias_where_clause = where clauses are not enforced in type aliases
148-
.suggestion = the clause will not be checked when the type alias is used, and should be removed
142+
lint_builtin_type_alias_bounds_enable_feat_help = add `#![feature(lazy_type_alias)]` to the crate attributes to enable the desired semantics
143+
lint_builtin_type_alias_bounds_label = will not be checked at usage sites of the type alias
144+
lint_builtin_type_alias_bounds_limitation_note = this is a known limitation of the type checker that may be lifted in a future edition.
145+
see issue #112792 <https://github.com/rust-lang/rust/issues/112792> for more information
146+
lint_builtin_type_alias_bounds_param_bounds = bounds on generic parameters in type aliases are not enforced
147+
.suggestion = remove {$count ->
148+
[one] this bound
149+
*[other] these bounds
150+
}
151+
lint_builtin_type_alias_bounds_qualify_assoc_tys_sugg = fully qualify this associated type
152+
lint_builtin_type_alias_bounds_where_clause = where clauses on type aliases are not enforced
153+
.suggestion = remove this where clause
149154
150155
lint_builtin_unpermitted_type_init_label = this code causes undefined behavior when executed
151156
lint_builtin_unpermitted_type_init_label_suggestion = help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done

compiler/rustc_lint/src/builtin.rs

+53-39
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ use crate::{
3131
BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents,
3232
BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc,
3333
BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns,
34-
BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasGenericBounds,
35-
BuiltinTypeAliasGenericBoundsSuggestion, BuiltinTypeAliasWhereClause,
36-
BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
37-
BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe,
38-
BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
39-
BuiltinWhileTrue, InvalidAsmLabel, SuggestChangingAssocTypes,
34+
BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasBounds,
35+
BuiltinTypeAliasParamBoundsSuggestion, BuiltinUngatedAsyncFnTrackCaller,
36+
BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub,
37+
BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment,
38+
BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel,
39+
TypeAliasBoundsQualifyAssocTysSugg,
4040
},
4141
EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext,
4242
};
@@ -1406,23 +1406,6 @@ declare_lint_pass!(
14061406
TypeAliasBounds => [TYPE_ALIAS_BOUNDS]
14071407
);
14081408

1409-
impl TypeAliasBounds {
1410-
pub(crate) fn is_type_variable_assoc(qpath: &hir::QPath<'_>) -> bool {
1411-
match *qpath {
1412-
hir::QPath::TypeRelative(ty, _) => {
1413-
// If this is a type variable, we found a `T::Assoc`.
1414-
match ty.kind {
1415-
hir::TyKind::Path(hir::QPath::Resolved(None, path)) => {
1416-
matches!(path.res, Res::Def(DefKind::TyParam, _))
1417-
}
1418-
_ => false,
1419-
}
1420-
}
1421-
hir::QPath::Resolved(..) | hir::QPath::LangItem(..) => false,
1422-
}
1423-
}
1424-
}
1425-
14261409
impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
14271410
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
14281411
let hir::ItemKind::TyAlias(hir_ty, generics) = &item.kind else { return };
@@ -1437,7 +1420,6 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
14371420
return;
14381421
}
14391422

1440-
14411423
// FIXME(generic_const_exprs): Revisit this before stabilization.
14421424
// See also `tests/ui/const-generics/generic_const_exprs/type-alias-bounds.rs`.
14431425
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
@@ -1455,6 +1437,8 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
14551437
let mut where_spans = Vec::new();
14561438
let mut inline_spans = Vec::new();
14571439
let mut inline_sugg = Vec::new();
1440+
let mut affects_object_lifetime_defaults = false;
1441+
14581442
for p in generics.predicates {
14591443
let span = p.span();
14601444
if p.in_where_clause() {
@@ -1465,31 +1449,61 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
14651449
}
14661450
inline_sugg.push((span, String::new()));
14671451
}
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+
}
14681465
}
14691466

1470-
let mut suggested_changing_assoc_types = false;
1471-
if !where_spans.is_empty() {
1472-
let sub = (!suggested_changing_assoc_types).then(|| {
1473-
suggested_changing_assoc_types = true;
1474-
SuggestChangingAssocTypes { ty: hir_ty }
1475-
});
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(());
1481+
1482+
if let [.., label_sp] = *where_spans {
14761483
cx.emit_span_lint(
14771484
TYPE_ALIAS_BOUNDS,
14781485
where_spans,
1479-
BuiltinTypeAliasWhereClause { suggestion: generics.where_clause_span, sub },
1486+
BuiltinTypeAliasBounds::WhereClause {
1487+
label: label_sp,
1488+
enable_feat_help,
1489+
suggestion: (generics.where_clause_span, applicability),
1490+
qualify_assoc_tys_sugg: qualify_assoc_tys_sugg.take(),
1491+
},
14801492
);
14811493
}
1482-
1483-
if !inline_spans.is_empty() {
1484-
let suggestion = BuiltinTypeAliasGenericBoundsSuggestion { suggestions: inline_sugg };
1485-
let sub = (!suggested_changing_assoc_types).then(|| {
1486-
suggested_changing_assoc_types = true;
1487-
SuggestChangingAssocTypes { ty: hir_ty }
1488-
});
1494+
if let [.., label_sp] = *inline_spans {
14891495
cx.emit_span_lint(
14901496
TYPE_ALIAS_BOUNDS,
14911497
inline_spans,
1492-
BuiltinTypeAliasGenericBounds { suggestion, sub },
1498+
BuiltinTypeAliasBounds::ParamBounds {
1499+
label: label_sp,
1500+
enable_feat_help,
1501+
suggestion: BuiltinTypeAliasParamBoundsSuggestion {
1502+
suggestions: inline_sugg,
1503+
applicability,
1504+
},
1505+
qualify_assoc_tys_sugg,
1506+
},
14931507
);
14941508
}
14951509
}

compiler/rustc_lint/src/lints.rs

+83-65
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc_errors::{
99
ElidedLifetimeInPathSubdiag, EmissionGuarantee, LintDiagnostic, MultiSpan, SubdiagMessageOp,
1010
Subdiagnostic, SuggestionStyle,
1111
};
12-
use rustc_hir::{def::Namespace, def_id::DefId};
12+
use rustc_hir::{self as hir, def::Namespace, def_id::DefId};
1313
use rustc_macros::{LintDiagnostic, Subdiagnostic};
1414
use rustc_middle::ty::{
1515
inhabitedness::InhabitedPredicate, Clause, PolyExistentialTraitRef, Ty, TyCtxt,
@@ -22,9 +22,7 @@ use rustc_span::{
2222
Span, Symbol,
2323
};
2424

25-
use crate::{
26-
builtin::InitError, builtin::TypeAliasBounds, errors::OverruledAttributeSub, LateContext,
27-
};
25+
use crate::{builtin::InitError, errors::OverruledAttributeSub, LateContext};
2826

2927
// array_into_iter.rs
3028
#[derive(LintDiagnostic)]
@@ -263,84 +261,104 @@ pub struct BuiltinUnreachablePub<'a> {
263261
pub help: Option<()>,
264262
}
265263

266-
pub struct SuggestChangingAssocTypes<'a, 'b> {
267-
pub ty: &'a rustc_hir::Ty<'b>,
264+
#[derive(LintDiagnostic)]
265+
#[diag(lint_macro_expr_fragment_specifier_2024_migration)]
266+
pub struct MacroExprFragment2024 {
267+
#[suggestion(code = "expr_2021", applicability = "machine-applicable")]
268+
pub suggestion: Span,
269+
}
270+
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 {
300+
pub suggestions: Vec<(Span, String)>,
301+
pub applicability: Applicability,
268302
}
269303

270-
impl<'a, 'b> Subdiagnostic for SuggestChangingAssocTypes<'a, 'b> {
304+
impl Subdiagnostic for BuiltinTypeAliasParamBoundsSuggestion {
271305
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
272306
self,
273307
diag: &mut Diag<'_, G>,
274308
_f: &F,
275309
) {
276-
// Access to associates types should use `<T as Bound>::Assoc`, which does not need a
277-
// bound. Let's see if this type does that.
278-
279-
// We use a HIR visitor to walk the type.
280-
use rustc_hir::intravisit::{self, Visitor};
281-
struct WalkAssocTypes<'a, 'b, G: EmissionGuarantee> {
282-
err: &'a mut Diag<'b, G>,
283-
}
284-
impl<'a, 'b, G: EmissionGuarantee> Visitor<'_> for WalkAssocTypes<'a, 'b, G> {
285-
fn visit_qpath(
286-
&mut self,
287-
qpath: &rustc_hir::QPath<'_>,
288-
id: rustc_hir::HirId,
289-
span: Span,
290-
) {
291-
if TypeAliasBounds::is_type_variable_assoc(qpath) {
292-
self.err.span_help(span, fluent::lint_builtin_type_alias_bounds_help);
293-
}
294-
intravisit::walk_qpath(self, qpath, id)
295-
}
296-
}
297-
298-
// Let's go for a walk!
299-
let mut visitor = WalkAssocTypes { err: diag };
300-
visitor.visit_ty(self.ty);
310+
diag.arg("count", self.suggestions.len());
311+
diag.multipart_suggestion(fluent::lint_suggestion, self.suggestions, self.applicability);
301312
}
302313
}
303314

304-
#[derive(LintDiagnostic)]
305-
#[diag(lint_builtin_type_alias_where_clause)]
306-
pub struct BuiltinTypeAliasWhereClause<'a, 'b> {
307-
#[suggestion(code = "", applicability = "machine-applicable")]
308-
pub suggestion: Span,
309-
#[subdiagnostic]
310-
pub sub: Option<SuggestChangingAssocTypes<'a, 'b>>,
311-
}
312-
313-
#[derive(LintDiagnostic)]
314-
#[diag(lint_builtin_type_alias_generic_bounds)]
315-
pub struct BuiltinTypeAliasGenericBounds<'a, 'b> {
316-
#[subdiagnostic]
317-
pub suggestion: BuiltinTypeAliasGenericBoundsSuggestion,
318-
#[subdiagnostic]
319-
pub sub: Option<SuggestChangingAssocTypes<'a, 'b>>,
320-
}
321-
322-
#[derive(LintDiagnostic)]
323-
#[diag(lint_macro_expr_fragment_specifier_2024_migration)]
324-
pub struct MacroExprFragment2024 {
325-
#[suggestion(code = "expr_2021", applicability = "machine-applicable")]
326-
pub suggestion: Span,
315+
pub struct TypeAliasBoundsQualifyAssocTysSugg<'a, 'hir> {
316+
pub ty: &'a hir::Ty<'hir>,
327317
}
328318

329-
pub struct BuiltinTypeAliasGenericBoundsSuggestion {
330-
pub suggestions: Vec<(Span, String)>,
331-
}
332-
333-
impl Subdiagnostic for BuiltinTypeAliasGenericBoundsSuggestion {
319+
impl<'a, 'hir> Subdiagnostic for TypeAliasBoundsQualifyAssocTysSugg<'a, 'hir> {
334320
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
335321
self,
336322
diag: &mut Diag<'_, G>,
337323
_f: &F,
338324
) {
339-
diag.multipart_suggestion(
340-
fluent::lint_suggestion,
341-
self.suggestions,
342-
Applicability::MachineApplicable,
343-
);
325+
// We perform the walk in here instead of in `<TypeAliasBounds as LateLintPass>` to
326+
// 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>,
331+
}
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+
}
360+
}
361+
ProbeShorthandAssocTys { diag }.visit_ty(self.ty);
344362
}
345363
}
346364

tests/ui/associated-inherent-types/type-alias-bounds.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
// automatically lead to full wfchecking and lint TAB getting suppressed.
2020

2121
pub type Alias<T: Bound> = (Source<T>::Assoc,);
22-
//~^ WARN bounds on generic parameters are not enforced in type aliases
22+
//~^ WARN bounds on generic parameters in type aliases are not enforced
2323

2424
pub struct Source<T>(T);
2525
pub trait Bound {}
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
warning: bounds on generic parameters are not enforced in type aliases
1+
warning: bounds on generic parameters in type aliases are not enforced
22
--> $DIR/type-alias-bounds.rs:21:19
33
|
44
LL | pub type Alias<T: Bound> = (Source<T>::Assoc,);
5-
| ^^^^^
5+
| --^^^^^
6+
| | |
7+
| | will not be checked at usage sites of the type alias
8+
| help: remove this bound
69
|
10+
= note: this is a known limitation of the type checker that may be lifted in a future edition.
11+
see issue #112792 <https://github.com/rust-lang/rust/issues/112792> for more information
12+
= help: add `#![feature(lazy_type_alias)]` to the crate attributes to enable the desired semantics
713
= note: `#[warn(type_alias_bounds)]` on by default
8-
help: the bound will not be checked when the type alias is used, and should be removed
9-
|
10-
LL - pub type Alias<T: Bound> = (Source<T>::Assoc,);
11-
LL + pub type Alias<T> = (Source<T>::Assoc,);
12-
|
1314

1415
warning: 1 warning emitted
1516

0 commit comments

Comments
 (0)