Associated const references using UFCS can bypass check_static_recursion
Currently, check_static_recursion is run after resolve, but before typeck. This made sense before, because after resolve it was possible to determine which constants referenced one another, and running before typeck ensured that we don't fall into an infinite loop during type checking. However, when referencing an associated constant with some UFCS forms, we can't resolve the constant until typeck, meaning that check_static_recursion can't always tell when a recursive definition is present. On top of that, this check is not currently working right even for inherent impls, where it should be possible to discover a problem ahead of typeck running.
A set of test cases:
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(associated_consts)]
trait Foo {
const BAR: u32;
}
// Check for recursion involving references to trait-associated const.
const TRAIT_REF_BAR: u32 = <GlobalTraitRef>::BAR; //~ ERROR E0265
struct GlobalTraitRef;
impl Foo for GlobalTraitRef {
const BAR: u32 = TRAIT_REF_BAR; //~ ERROR E0265
}
// Check for recursion involving references to impl-associated const.
const IMPL_REF_BAR: u32 = GlobalImplRef::BAR; //~ ERROR E0265
struct GlobalImplRef;
impl GlobalImplRef {
const BAR: u32 = IMPL_REF_BAR; //~ ERROR E0265
}
// Check for recursion involving references to trait-associated const default.
trait FooDefault {
const BAR: u32 = DEFAULT_REF_BAR; //~ ERROR E0265
}
const DEFAULT_REF_BAR: u32 = <GlobalDefaultRef>::BAR; //~ ERROR E0265
struct GlobalDefaultRef;
impl FooDefault for GlobalDefaultRef {}
fn main() {}
The above cases should all fail in the recursion check, but they all compile successfully! If any of the constants are actually used in a context that requires the compiler to evaluate them, there will be an ICE (specifically, the compiler enters an infinite loop that ends up blowing the stack).
Currently,
check_static_recursionis run afterresolve, but beforetypeck. This made sense before, because afterresolveit was possible to determine which constants referenced one another, and running beforetypeckensured that we don't fall into an infinite loop during type checking. However, when referencing an associated constant with some UFCS forms, we can't resolve the constant untiltypeck, meaning thatcheck_static_recursioncan't always tell when a recursive definition is present. On top of that, this check is not currently working right even for inherent impls, where it should be possible to discover a problem ahead oftypeckrunning.A set of test cases:
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms. #![feature(associated_consts)] trait Foo { const BAR: u32; } // Check for recursion involving references to trait-associated const. const TRAIT_REF_BAR: u32 = <GlobalTraitRef>::BAR; //~ ERROR E0265 struct GlobalTraitRef; impl Foo for GlobalTraitRef { const BAR: u32 = TRAIT_REF_BAR; //~ ERROR E0265 } // Check for recursion involving references to impl-associated const. const IMPL_REF_BAR: u32 = GlobalImplRef::BAR; //~ ERROR E0265 struct GlobalImplRef; impl GlobalImplRef { const BAR: u32 = IMPL_REF_BAR; //~ ERROR E0265 } // Check for recursion involving references to trait-associated const default. trait FooDefault { const BAR: u32 = DEFAULT_REF_BAR; //~ ERROR E0265 } const DEFAULT_REF_BAR: u32 = <GlobalDefaultRef>::BAR; //~ ERROR E0265 struct GlobalDefaultRef; impl FooDefault for GlobalDefaultRef {} fn main() {}The above cases should all fail in the recursion check, but they all compile successfully! If any of the constants are actually used in a context that requires the compiler to evaluate them, there will be an ICE (specifically, the compiler enters an infinite loop that ends up blowing the stack).
Update playground link: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=76e9edaf2827806e18a557f37bc930ea
<!-- TRIAGEBOT_START --> <!-- TRIAGEBOT_ASSIGN_START -->This issue has been assigned to @Daniel-Worrall via this comment.
<!-- TRIAGEBOT_ASSIGN_DATA_START$${"user":"Daniel-Worrall"}$$TRIAGEBOT_ASSIGN_DATA_END --> <!-- TRIAGEBOT_ASSIGN_END --> <!-- TRIAGEBOT_END -->Dylan DPC at 2020-04-27 13:38:00
A simpler example of this:
trait Foo { const FOO: Self; } impl Foo for u8 { const FOO: u8 = u8::FOO; }compiles.
Aria Desires at 2015-06-02 22:43:03
Triage: @Gankro 's example still compiles.
Steve Klabnik at 2017-01-03 18:00:36
E-needstest.
Mark Rousskov at 2017-05-18 16:49:53
trait Foo { const FOO: Self; } impl Foo for u8 { const FOO: u8 = u8::FOO; }fails to compile, but adding:
fn main() {}makes it compile without an error.
varkor at 2019-02-25 23:53:01
@rustbot claim
Daniel Worrall at 2020-04-27 21:52:38
This isn't fixed. The example in this comment still compiles.
varkor at 2020-04-29 18:09:06