Skip to content

Reject duplicate lifetime parameters with E0403 - #4933

Open
u7k4rs6 wants to merge 2 commits into
Rust-GCC:masterfrom
u7k4rs6:fix-dup-lifetime-params
Open

u7k4rs6 wants to merge 2 commits into
Rust-GCC:masterfrom
u7k4rs6:fix-dup-lifetime-params

Conversation

@u7k4rs6

@u7k4rs6 u7k4rs6 commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

gccrs currently accepts struct Foo<'a, 'a>, for<'a, 'a>, etc. rustc rejects these with E0403, so this adds the check.

Two commits: first adds visit_generic_params / visit_for_lifetimes to DefaultASTVisitor (pure refactor, no behavior change), then overrides both in TopLevel to actually report E0403.

Went with TopLevel since it's the pass that collects definitions and enforces uniqueness (per Arthur on Zulip), while Late is for resolving uses.

Check is scoped to one binder, so an inner for<'a> reusing an outer 'a is untouched, that's shadowing (E0496), different issue.

TopLevel visits each item 3x, so I track reported params in NameResolutionContext to avoid tripling the error. This 3x-visit thing is an existing bug on master too, unrelated to this PR:

$ gccrs probe.rs      # extern crate does_not_exist;
error: unknown crate 'does_not_exist'
error: unknown crate 'does_not_exist'
error: unknown crate 'does_not_exist'

Output matches rustc:

error: the name ''a' is already used for a generic parameter in this item's generic parameters [E0403]
struct Foo<'a, 'a>(&'a u8);
           ~~  ^~

Tests: 11646 pass / 0 fail (+7 new, covering struct/fn/trait/type alias/impl/HRTB plus the shadowing and distinct-lifetime cases that should stay silent).

Lets derived visitors override just the traversal of a binder, like
visit_function_params already does for function params. No functional
change.

gcc/rust/ChangeLog:

	* ast/rust-ast-visitor.h (DefaultASTVisitor::visit_generic_params):
	Declare.
	(DefaultASTVisitor::visit_for_lifetimes): Likewise.
	* ast/rust-ast-visitor.cc (DefaultASTVisitor::visit_generic_params):
	Define.
	(DefaultASTVisitor::visit_for_lifetimes): Likewise.
	(DefaultASTVisitor::visit): Call them instead of walking the
	parameter lists inline.
	* resolve/rust-default-resolver.cc (DefaultResolver::visit): Call
	visit_generic_params for Trait, InherentImpl and TraitImpl.

Signed-off-by: Utkarsh Bahuguna <utkarshbahuguna10@gmail.com>
@u7k4rs6
u7k4rs6 force-pushed the fix-dup-lifetime-params branch from 720808d to b335f24 Compare September 22, 2026 14:34

@CohenArthur CohenArthur left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this is a good start! The changes to the visitor are great, just need to make sure everything aligns together properly :) good work!!

if (param->get_kind () == AST::GenericParam::Kind::Lifetime)
lifetimes.push_back (static_cast<AST::LifetimeParam *> (param.get ()));

check_duplicate_lifetimes (lifetimes);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we not define lifetimes in the Labels namespace? Why can't we do something like insert_or_error_out(...) like in the rest of TopLevel?

for (auto &param : for_lifetimes)
lifetimes.push_back (&param);

check_duplicate_lifetimes (lifetimes);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Likewise here if this is a definition spot

@u7k4rs6

u7k4rs6 commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

@CohenArthur We don't currently insert lifetime params into Labels, and Labels is shadowable for loop labels, so insert_or_error_out wouldn't catch duplicate lifetime params.

I'm happy to make lifetime params proper non-shadowable definitions instead. That would need a small change to the Labels insertion path and E0403 handling. Would you prefer that in this PR?

struct Foo<'a, 'a> and for<'a, 'a> were both accepted. Lifetimes are
definitions now, inserted into the label namespace, so a duplicate falls
out of the rib as E0403. They go in non-shadowably - loop labels are
allowed to shadow, lifetime params are not.

Each for<...> binder gets its own rib, since reusing an outer lifetime
name there is shadowing (E0496) rather than a duplicate.

gcc/rust/ChangeLog:

	* resolve/rust-forever-stack.h
	(ForeverStack::insert_non_shadowable): Declare.
	* resolve/rust-forever-stack.hxx
	(ForeverStack::insert_non_shadowable): Define.
	* resolve/rust-name-resolution-context.h
	(NameResolutionContext::insert_non_shadowable): Declare.
	* resolve/rust-name-resolution-context.cc
	(NameResolutionContext::insert_non_shadowable): Define.
	* resolve/rust-toplevel-name-resolver-2.0.h
	(TopLevel::visit): Declare for lifetime parameters.
	(TopLevel::visit_for_lifetimes): Likewise.
	* resolve/rust-toplevel-name-resolver-2.0.cc
	(TopLevel::visit): Insert lifetime parameters as definitions.
	(TopLevel::visit_for_lifetimes): Scope each binder.

gcc/testsuite/ChangeLog:

	* rust/compile/dup_lifetime_params.rs: New test.

Signed-off-by: Utkarsh Bahuguna <utkarshbahuguna10@gmail.com>
@u7k4rs6
u7k4rs6 force-pushed the fix-dup-lifetime-params branch from b335f24 to 77c5445 Compare September 25, 2026 09:41
@u7k4rs6

u7k4rs6 commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor Author

@CohenArthur took your suggestion lifetimes are definitions in the Labels namespace now rather than a separate check.

Two things made that work. ForeverStack<Namespace::Labels>::insert is specialised to shadowable so loop labels can nest ('a: loop { 'a: loop {} }), which meant insert_or_error_out would silently accept a duplicate param. Added an insert_non_shadowable alongside the existing insert_shadowable and used it only for params, so loop labels keep their current behaviour.

The second is scoping: with every binder sharing one rib, struct A<'a>(&'a u8) where for<'a> &'a u8: Tr; started erroring, but that's shadowing (E0496), not a duplicate. Each for<...> binder now gets its own Rib::Kind::Generics rib, so inner binders shadow instead of colliding.

Net effect is smaller than before check_duplicate_lifetimes and the reported_duplicate_lifetimes set in NameResolutionContext are both gone, since the fixed-point dedup falls out of the rib. make check-rust is clean (5529 passes, 0 failures) and CI is green.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants