Skip to content

Fortify generic param default checks #144977

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 101 additions & 99 deletions compiler/rustc_hir_analysis/src/collect/generics_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,60 +223,27 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics {
"synthetic HIR should have its `generics_of` explicitly fed"
),

_ => span_bug!(tcx.def_span(def_id), "unhandled node {node:?}"),
_ => span_bug!(tcx.def_span(def_id), "generics_of: unexpected node kind {node:?}"),
};

enum Defaults {
Allowed,
// See #36887
FutureCompatDisallowed,
Deny,
}

let hir_generics = node.generics().unwrap_or(hir::Generics::empty());
let (opt_self, allow_defaults) = match node {
Node::Item(item) => {
match item.kind {
ItemKind::Trait(..) | ItemKind::TraitAlias(..) => {
// Add in the self type parameter.
//
// Something of a hack: use the node id for the trait, also as
// the node id for the Self type parameter.
let opt_self = Some(ty::GenericParamDef {
index: 0,
name: kw::SelfUpper,
def_id: def_id.to_def_id(),
pure_wrt_drop: false,
kind: ty::GenericParamDefKind::Type {
has_default: false,
synthetic: false,
},
});

(opt_self, Defaults::Allowed)
}
ItemKind::TyAlias(..)
| ItemKind::Enum(..)
| ItemKind::Struct(..)
| ItemKind::Union(..) => (None, Defaults::Allowed),
ItemKind::Const(..) => (None, Defaults::Deny),
_ => (None, Defaults::FutureCompatDisallowed),
}
}

Node::OpaqueTy(..) => (None, Defaults::Allowed),

// GATs
Node::TraitItem(item) if matches!(item.kind, TraitItemKind::Type(..)) => {
(None, Defaults::Deny)
}
Node::ImplItem(item) if matches!(item.kind, ImplItemKind::Type(..)) => {
(None, Defaults::Deny)
}

_ => (None, Defaults::FutureCompatDisallowed),
// Add in the self type parameter.
let opt_self = if let Node::Item(item) = node
&& let ItemKind::Trait(..) | ItemKind::TraitAlias(..) = item.kind
{
// Something of a hack: We reuse the node ID of the trait for the self type parameter.
Some(ty::GenericParamDef {
index: 0,
name: kw::SelfUpper,
def_id: def_id.to_def_id(),
pure_wrt_drop: false,
kind: ty::GenericParamDefKind::Type { has_default: false, synthetic: false },
})
} else {
None
};

let param_default_policy = param_default_policy(node);
let hir_generics = node.generics().unwrap_or(hir::Generics::empty());
let has_self = opt_self.is_some();
let mut parent_has_self = false;
let mut own_start = has_self as u32;
Expand Down Expand Up @@ -312,60 +279,53 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics {
prev + type_start
};

const TYPE_DEFAULT_NOT_ALLOWED: &'static str = "defaults for type parameters are only allowed in \
`struct`, `enum`, `type`, or `trait` definitions";

own_params.extend(hir_generics.params.iter().filter_map(|param| match param.kind {
GenericParamKind::Lifetime { .. } => None,
GenericParamKind::Type { default, synthetic, .. } => {
if default.is_some() {
match allow_defaults {
Defaults::Allowed => {}
Defaults::FutureCompatDisallowed => {
tcx.node_span_lint(
lint::builtin::INVALID_TYPE_PARAM_DEFAULT,
param.hir_id,
param.span,
|lint| {
lint.primary_message(TYPE_DEFAULT_NOT_ALLOWED);
},
);
}
Defaults::Deny => {
tcx.dcx().span_err(param.span, TYPE_DEFAULT_NOT_ALLOWED);
own_params.extend(hir_generics.params.iter().filter_map(|param| {
const MESSAGE: &str = "defaults for generic parameters are not allowed here";
let kind = match param.kind {
GenericParamKind::Lifetime { .. } => return None,
GenericParamKind::Type { default, synthetic } => {
if default.is_some() {
match param_default_policy.expect("no policy for generic param default") {
ParamDefaultPolicy::Allowed => {}
ParamDefaultPolicy::FutureCompatForbidden => {
tcx.node_span_lint(
lint::builtin::INVALID_TYPE_PARAM_DEFAULT,
param.hir_id,
param.span,
|lint| {
lint.primary_message(MESSAGE);
},
);
}
ParamDefaultPolicy::Forbidden => {
tcx.dcx().span_err(param.span, MESSAGE);
}
}
}
}

let kind = ty::GenericParamDefKind::Type { has_default: default.is_some(), synthetic };

Some(ty::GenericParamDef {
index: next_index(),
name: param.name.ident().name,
def_id: param.def_id.to_def_id(),
pure_wrt_drop: param.pure_wrt_drop,
kind,
})
}
GenericParamKind::Const { ty: _, default, synthetic } => {
if !matches!(allow_defaults, Defaults::Allowed) && default.is_some() {
tcx.dcx().span_err(
param.span,
"defaults for const parameters are only allowed in \
`struct`, `enum`, `type`, or `trait` definitions",
);
ty::GenericParamDefKind::Type { has_default: default.is_some(), synthetic }
}
GenericParamKind::Const { ty: _, default, synthetic } => {
if default.is_some() {
match param_default_policy.expect("no policy for generic param default") {
Copy link
Member Author

@fmease fmease Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, instead of panicking we could simply default to Forbidden (on master we default to FutureCompatForbidden which I heavily dislike). Just LMK :)

ParamDefaultPolicy::Allowed => {}
ParamDefaultPolicy::FutureCompatForbidden
| ParamDefaultPolicy::Forbidden => {
tcx.dcx().span_err(param.span, MESSAGE);
}
}
}

let index = next_index();

Some(ty::GenericParamDef {
index,
name: param.name.ident().name,
def_id: param.def_id.to_def_id(),
pure_wrt_drop: param.pure_wrt_drop,
kind: ty::GenericParamDefKind::Const { has_default: default.is_some(), synthetic },
})
}
ty::GenericParamDefKind::Const { has_default: default.is_some(), synthetic }
}
};
Some(ty::GenericParamDef {
index: next_index(),
name: param.name.ident().name,
def_id: param.def_id.to_def_id(),
pure_wrt_drop: param.pure_wrt_drop,
kind,
})
}));

// provide junk type parameter defs - the only place that
Expand Down Expand Up @@ -438,6 +398,48 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics {
}
}

#[derive(Clone, Copy)]
enum ParamDefaultPolicy {
Allowed,
/// Tracked in <https://github.com/rust-lang/rust/issues/36887>.
FutureCompatForbidden,
Forbidden,
}

fn param_default_policy(node: Node<'_>) -> Option<ParamDefaultPolicy> {
use rustc_hir::*;

Some(match node {
Node::Item(item) => match item.kind {
ItemKind::Trait(..)
| ItemKind::TraitAlias(..)
| ItemKind::TyAlias(..)
| ItemKind::Enum(..)
| ItemKind::Struct(..)
| ItemKind::Union(..) => ParamDefaultPolicy::Allowed,
ItemKind::Fn { .. } | ItemKind::Impl(_) => ParamDefaultPolicy::FutureCompatForbidden,
// Re. GCI, we're not bound by backward compatibility.
ItemKind::Const(..) => ParamDefaultPolicy::Forbidden,
_ => return None,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could exhaustively match on item.kind if you want me to.

},
Node::TraitItem(item) => match item.kind {
// Re. GATs and GACs (generic_const_items), we're not bound by backward compatibility.
TraitItemKind::Const(..) | TraitItemKind::Type(..) => ParamDefaultPolicy::Forbidden,
TraitItemKind::Fn(..) => ParamDefaultPolicy::FutureCompatForbidden,
},
Node::ImplItem(item) => match item.kind {
// Re. GATs and GACs (generic_const_items), we're not bound by backward compatibility.
ImplItemKind::Const(..) | ImplItemKind::Type(..) => ParamDefaultPolicy::Forbidden,
ImplItemKind::Fn(..) => ParamDefaultPolicy::FutureCompatForbidden,
},
// Generic params are (semantically) invalid on foreign items. Still, for maximum forward
// compatibility, let's hard-reject defaults on them.
Node::ForeignItem(_) => ParamDefaultPolicy::Forbidden,
Node::OpaqueTy(..) => ParamDefaultPolicy::Allowed,
_ => return None,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exhaustively matching on node: hir::Node doesn't really buy us much and it would be pretty overkill.

})
}

fn has_late_bound_regions<'tcx>(tcx: TyCtxt<'tcx>, node: Node<'tcx>) -> Option<Span> {
struct LateBoundRegionsDetector<'tcx> {
tcx: TyCtxt<'tcx>,
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/const-generics/defaults/default-on-impl.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
struct Foo<const N: usize>;

impl<const N: usize = 1> Foo<N> {}
//~^ ERROR defaults for const parameters are only allowed
//~^ ERROR defaults for generic parameters are not allowed here

fn main() {}
2 changes: 1 addition & 1 deletion tests/ui/const-generics/defaults/default-on-impl.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: defaults for const parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions
error: defaults for generic parameters are not allowed here
--> $DIR/default-on-impl.rs:3:6
|
LL | impl<const N: usize = 1> Foo<N> {}
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/const-generics/generic_const_exprs/issue-105257.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#![feature(generic_const_exprs)]
#![allow(incomplete_features)]
#![expect(incomplete_features)]

trait Trait<T> {
fn fnc<const N: usize = "">(&self) {} //~ERROR defaults for const parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions
fn fnc<const N: usize = "">(&self) {} //~ERROR defaults for generic parameters are not allowed here
//~^ ERROR: mismatched types
fn foo<const N: usize = { std::mem::size_of::<T>() }>(&self) {} //~ERROR defaults for const parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions
fn foo<const N: usize = { std::mem::size_of::<T>() }>(&self) {} //~ERROR defaults for generic parameters are not allowed here
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
error: defaults for const parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions
error: defaults for generic parameters are not allowed here
--> $DIR/issue-105257.rs:5:12
|
LL | fn fnc<const N: usize = "">(&self) {}
| ^^^^^^^^^^^^^^^^^^^

error: defaults for const parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions
error: defaults for generic parameters are not allowed here
--> $DIR/issue-105257.rs:7:12
|
LL | fn foo<const N: usize = { std::mem::size_of::<T>() }>(&self) {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![crate_type = "lib"]

fn foo<const SIZE: usize = 5usize>() {}
//~^ ERROR defaults for const parameters are
//~^ ERROR defaults for generic parameters are not allowed here
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: defaults for const parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions
error: defaults for generic parameters are not allowed here
--> $DIR/default_function_param.rs:3:8
|
LL | fn foo<const SIZE: usize = 5usize>() {}
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/generic-associated-types/type-param-defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@

trait Trait {
type Assoc<T = u32>;
//~^ ERROR defaults for type parameters are only allowed
//~^ ERROR defaults for generic parameters are not allowed here
}

impl Trait for () {
type Assoc<T = u32> = u64;
//~^ ERROR defaults for type parameters are only allowed
//~^ ERROR defaults for generic parameters are not allowed here
}

impl Trait for u32 {
type Assoc<T = u32> = T;
//~^ ERROR defaults for type parameters are only allowed
//~^ ERROR defaults for generic parameters are not allowed here
}

trait Other {}
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/generic-associated-types/type-param-defaults.stderr
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
error: defaults for type parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions
error: defaults for generic parameters are not allowed here
--> $DIR/type-param-defaults.rs:6:16
|
LL | type Assoc<T = u32>;
| ^^^^^^^

error: defaults for type parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions
error: defaults for generic parameters are not allowed here
--> $DIR/type-param-defaults.rs:11:16
|
LL | type Assoc<T = u32> = u64;
| ^^^^^^^

error: defaults for type parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions
error: defaults for generic parameters are not allowed here
--> $DIR/type-param-defaults.rs:16:16
|
LL | type Assoc<T = u32> = T;
Expand Down
16 changes: 12 additions & 4 deletions tests/ui/generic-const-items/parameter-defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@

// FIXME(default_type_parameter_fallback): Consider reallowing them once they work properly.

const NONE<T = ()>: Option<T> = None::<T>; //~ ERROR defaults for type parameters are only allowed
const NONE<T = ()>: Option<T> = None::<T>;
//~^ ERROR defaults for generic parameters are not allowed here

fn main() {
let _ = NONE;
//~^ ERROR type annotations needed
impl Host {
const NADA<T = ()>: Option<T> = None::<T>;
//~^ ERROR defaults for generic parameters are not allowed here
}

enum Host {}

fn body0() { let _ = NONE; } //~ ERROR type annotations needed
fn body1() { let _ = Host::NADA; } //~ ERROR type annotations needed

fn main() {}
31 changes: 24 additions & 7 deletions tests/ui/generic-const-items/parameter-defaults.stderr
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
error: defaults for type parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions
error: defaults for generic parameters are not allowed here
--> $DIR/parameter-defaults.rs:10:12
|
LL | const NONE<T = ()>: Option<T> = None::<T>;
| ^^^^^^

error: defaults for generic parameters are not allowed here
--> $DIR/parameter-defaults.rs:14:16
|
LL | const NADA<T = ()>: Option<T> = None::<T>;
| ^^^^^^

error[E0282]: type annotations needed for `Option<_>`
--> $DIR/parameter-defaults.rs:20:18
|
LL | fn body0() { let _ = NONE; }
| ^ ---- type must be known at this point
|
help: consider giving this pattern a type, where the type for type parameter `T` is specified
|
LL | fn body0() { let _: Option<T> = NONE; }
| +++++++++++

error[E0282]: type annotations needed for `Option<_>`
--> $DIR/parameter-defaults.rs:13:9
--> $DIR/parameter-defaults.rs:21:18
|
LL | let _ = NONE;
| ^ ---- type must be known at this point
LL | fn body1() { let _ = Host::NADA; }
| ^ ---------- type must be known at this point
|
help: consider giving this pattern a type, where the type for type parameter `T` is specified
|
LL | let _: Option<T> = NONE;
| +++++++++++
LL | fn body1() { let _: Option<T> = Host::NADA; }
| +++++++++++

error: aborting due to 2 previous errors
error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0282`.
Loading
Loading