Skip to content

Commit 6a4e4f3

Browse files
committed
Improve the impl and diag output of lint type_alias_bounds
1 parent 91fbe1b commit 6a4e4f3

13 files changed

+391
-325
lines changed

compiler/rustc_lint/messages.ftl

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

+50-36
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ use crate::{
3232
BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc,
3333
BuiltinMutablesTransmutes, BuiltinNamedAsmLabel, BuiltinNoMangleGeneric,
3434
BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds,
35-
BuiltinTypeAliasGenericBounds, BuiltinTypeAliasGenericBoundsSuggestion,
36-
BuiltinTypeAliasWhereClause, BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
35+
BuiltinTypeAliasBounds, BuiltinTypeAliasParamBoundsSuggestion,
36+
BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
3737
BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe,
3838
BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
39-
BuiltinWhileTrue, SuggestChangingAssocTypes,
39+
BuiltinWhileTrue, TypeAliasBoundsQualifyAssocTysSugg,
4040
},
4141
EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext,
4242
};
@@ -1475,23 +1475,6 @@ declare_lint_pass!(
14751475
TypeAliasBounds => [TYPE_ALIAS_BOUNDS]
14761476
);
14771477

1478-
impl TypeAliasBounds {
1479-
pub(crate) fn is_type_variable_assoc(qpath: &hir::QPath<'_>) -> bool {
1480-
match *qpath {
1481-
hir::QPath::TypeRelative(ty, _) => {
1482-
// If this is a type variable, we found a `T::Assoc`.
1483-
match ty.kind {
1484-
hir::TyKind::Path(hir::QPath::Resolved(None, path)) => {
1485-
matches!(path.res, Res::Def(DefKind::TyParam, _))
1486-
}
1487-
_ => false,
1488-
}
1489-
}
1490-
hir::QPath::Resolved(..) | hir::QPath::LangItem(..) => false,
1491-
}
1492-
}
1493-
}
1494-
14951478
impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
14961479
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
14971480
let hir::ItemKind::TyAlias(hir_ty, generics) = &item.kind else { return };
@@ -1506,7 +1489,6 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
15061489
return;
15071490
}
15081491

1509-
15101492
// FIXME(generic_const_exprs): Revisit this before stabilization.
15111493
// See also `tests/ui/const-generics/generic_const_exprs/type-alias-bounds.rs`.
15121494
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
@@ -1524,6 +1506,8 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
15241506
let mut where_spans = Vec::new();
15251507
let mut inline_spans = Vec::new();
15261508
let mut inline_sugg = Vec::new();
1509+
let mut affects_object_lifetime_defaults = false;
1510+
15271511
for p in generics.predicates {
15281512
let span = p.span();
15291513
if p.in_where_clause() {
@@ -1534,31 +1518,61 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
15341518
}
15351519
inline_sugg.push((span, String::new()));
15361520
}
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+
}
15371534
}
15381535

1539-
let mut suggested_changing_assoc_types = false;
1540-
if !where_spans.is_empty() {
1541-
let sub = (!suggested_changing_assoc_types).then(|| {
1542-
suggested_changing_assoc_types = true;
1543-
SuggestChangingAssocTypes { ty: hir_ty }
1544-
});
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(());
1550+
1551+
if let [.., label_sp] = *where_spans {
15451552
cx.emit_span_lint(
15461553
TYPE_ALIAS_BOUNDS,
15471554
where_spans,
1548-
BuiltinTypeAliasWhereClause { suggestion: generics.where_clause_span, sub },
1555+
BuiltinTypeAliasBounds::WhereClause {
1556+
label: label_sp,
1557+
enable_feat_help,
1558+
suggestion: (generics.where_clause_span, applicability),
1559+
qualify_assoc_tys_sugg: qualify_assoc_tys_sugg.take(),
1560+
},
15491561
);
15501562
}
1551-
1552-
if !inline_spans.is_empty() {
1553-
let suggestion = BuiltinTypeAliasGenericBoundsSuggestion { suggestions: inline_sugg };
1554-
let sub = (!suggested_changing_assoc_types).then(|| {
1555-
suggested_changing_assoc_types = true;
1556-
SuggestChangingAssocTypes { ty: hir_ty }
1557-
});
1563+
if let [.., label_sp] = *inline_spans {
15581564
cx.emit_span_lint(
15591565
TYPE_ALIAS_BOUNDS,
15601566
inline_spans,
1561-
BuiltinTypeAliasGenericBounds { suggestion, sub },
1567+
BuiltinTypeAliasBounds::ParamBounds {
1568+
label: label_sp,
1569+
enable_feat_help,
1570+
suggestion: BuiltinTypeAliasParamBoundsSuggestion {
1571+
suggestions: inline_sugg,
1572+
applicability,
1573+
},
1574+
qualify_assoc_tys_sugg,
1575+
},
15621576
);
15631577
}
15641578
}

compiler/rustc_lint/src/lints.rs

+76-58
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)]
@@ -267,77 +265,97 @@ pub struct BuiltinUnreachablePub<'a> {
267265
pub help: Option<()>,
268266
}
269267

270-
pub struct SuggestChangingAssocTypes<'a, 'b> {
271-
pub ty: &'a rustc_hir::Ty<'b>,
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+
},
272294
}
273295

274-
impl<'a, 'b> Subdiagnostic for SuggestChangingAssocTypes<'a, 'b> {
296+
pub struct BuiltinTypeAliasParamBoundsSuggestion {
297+
pub suggestions: Vec<(Span, String)>,
298+
pub applicability: Applicability,
299+
}
300+
301+
impl Subdiagnostic for BuiltinTypeAliasParamBoundsSuggestion {
275302
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
276303
self,
277304
diag: &mut Diag<'_, G>,
278305
_f: &F,
279306
) {
280-
// Access to associates types should use `<T as Bound>::Assoc`, which does not need a
281-
// bound. Let's see if this type does that.
282-
283-
// We use a HIR visitor to walk the type.
284-
use rustc_hir::intravisit::{self, Visitor};
285-
struct WalkAssocTypes<'a, 'b, G: EmissionGuarantee> {
286-
err: &'a mut Diag<'b, G>,
287-
}
288-
impl<'a, 'b, G: EmissionGuarantee> Visitor<'_> for WalkAssocTypes<'a, 'b, G> {
289-
fn visit_qpath(
290-
&mut self,
291-
qpath: &rustc_hir::QPath<'_>,
292-
id: rustc_hir::HirId,
293-
span: Span,
294-
) {
295-
if TypeAliasBounds::is_type_variable_assoc(qpath) {
296-
self.err.span_help(span, fluent::lint_builtin_type_alias_bounds_help);
297-
}
298-
intravisit::walk_qpath(self, qpath, id)
299-
}
300-
}
301-
302-
// Let's go for a walk!
303-
let mut visitor = WalkAssocTypes { err: diag };
304-
visitor.visit_ty(self.ty);
307+
diag.arg("count", self.suggestions.len());
308+
diag.multipart_suggestion(fluent::lint_suggestion, self.suggestions, self.applicability);
305309
}
306310
}
307311

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

326-
pub struct BuiltinTypeAliasGenericBoundsSuggestion {
327-
pub suggestions: Vec<(Span, String)>,
328-
}
329-
330-
impl Subdiagnostic for BuiltinTypeAliasGenericBoundsSuggestion {
316+
impl<'a, 'hir> Subdiagnostic for TypeAliasBoundsQualifyAssocTysSugg<'a, 'hir> {
331317
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
332318
self,
333319
diag: &mut Diag<'_, G>,
334320
_f: &F,
335321
) {
336-
diag.multipart_suggestion(
337-
fluent::lint_suggestion,
338-
self.suggestions,
339-
Applicability::MachineApplicable,
340-
);
322+
// We perform the walk in here instead of in `<TypeAliasBounds as LateLintPass>` to
323+
// 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>,
328+
}
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+
}
357+
}
358+
ProbeShorthandAssocTys { diag }.visit_ty(self.ty);
341359
}
342360
}
343361

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)