Skip to content

Commit 6a8eabb

Browse files
committed
Add ergonomic_clones feature flag
1 parent ed49386 commit 6a8eabb

File tree

8 files changed

+481
-0
lines changed

8 files changed

+481
-0
lines changed

compiler/rustc_ast_passes/src/feature_gate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
501501
gate_all!(dyn_star, "`dyn*` trait objects are experimental");
502502
gate_all!(const_closures, "const closures are experimental");
503503
gate_all!(builtin_syntax, "`builtin #` syntax is unstable");
504+
gate_all!(ergonomic_clones, "`.use` calls are experimental");
504505
gate_all!(explicit_tail_calls, "`become` expression is experimental");
505506
gate_all!(generic_const_items, "generic const items are experimental");
506507
gate_all!(guard_patterns, "guard patterns are experimental", "consider using match arm guards");

compiler/rustc_feature/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,8 @@ declare_features! (
483483
(unstable, doc_masked, "1.21.0", Some(44027)),
484484
/// Allows `dyn* Trait` objects.
485485
(incomplete, dyn_star, "1.65.0", Some(102425)),
486+
/// Allows ergonomic clones.
487+
(unstable, ergonomic_clones, "CURRENT_RUSTC_VERSION", Some(132290)),
486488
/// Allows exhaustive pattern matching on types that contain uninhabited types.
487489
(unstable, exhaustive_patterns, "1.13.0", Some(51085)),
488490
/// Allows explicit tail calls via `become` expression.

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,7 @@ symbols! {
857857
eprint_macro,
858858
eprintln_macro,
859859
eq,
860+
ergonomic_clones,
860861
ermsb_target_feature,
861862
exact_div,
862863
except,
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#![feature(rustc_private)]
2+
3+
extern crate rustc_driver;
4+
extern crate rustc_error_codes;
5+
extern crate rustc_errors;
6+
extern crate rustc_hash;
7+
extern crate rustc_hir;
8+
extern crate rustc_interface;
9+
extern crate rustc_session;
10+
extern crate rustc_span;
11+
12+
use rustc_errors::{
13+
emitter::Emitter, registry, translation::Translate, DiagCtxt, DiagInner, FluentBundle,
14+
};
15+
use rustc_session::config;
16+
use rustc_span::source_map::SourceMap;
17+
18+
use std::{
19+
path, process, str,
20+
sync::{Arc, Mutex},
21+
};
22+
23+
struct DebugEmitter {
24+
source_map: Arc<SourceMap>,
25+
diagnostics: Arc<Mutex<Vec<DiagInner>>>,
26+
}
27+
28+
impl Translate for DebugEmitter {
29+
fn fluent_bundle(&self) -> Option<&Arc<FluentBundle>> {
30+
None
31+
}
32+
33+
fn fallback_fluent_bundle(&self) -> &FluentBundle {
34+
panic!("this emitter should not translate message")
35+
}
36+
}
37+
38+
impl Emitter for DebugEmitter {
39+
fn emit_diagnostic(&mut self, diag: DiagInner) {
40+
self.diagnostics.lock().unwrap().push(diag);
41+
}
42+
43+
fn source_map(&self) -> Option<&Arc<SourceMap>> {
44+
Some(&self.source_map)
45+
}
46+
}
47+
48+
fn main() {
49+
let out = process::Command::new("rustc")
50+
.arg("--print=sysroot")
51+
.current_dir(".")
52+
.output()
53+
.unwrap();
54+
let sysroot = str::from_utf8(&out.stdout).unwrap().trim();
55+
let buffer: Arc<Mutex<Vec<DiagInner>>> = Arc::default();
56+
let diagnostics = buffer.clone();
57+
let config = rustc_interface::Config {
58+
opts: config::Options {
59+
maybe_sysroot: Some(path::PathBuf::from(sysroot)),
60+
..config::Options::default()
61+
},
62+
// This program contains a type error.
63+
input: config::Input::Str {
64+
name: rustc_span::FileName::Custom("main.rs".into()),
65+
input: "
66+
fn main() {
67+
let x: &str = 1;
68+
}
69+
"
70+
.into(),
71+
},
72+
crate_cfg: Vec::new(),
73+
crate_check_cfg: Vec::new(),
74+
output_dir: None,
75+
output_file: None,
76+
file_loader: None,
77+
locale_resources: rustc_driver::DEFAULT_LOCALE_RESOURCES,
78+
lint_caps: rustc_hash::FxHashMap::default(),
79+
psess_created: Some(Box::new(|parse_sess| {
80+
parse_sess.set_dcx(DiagCtxt::new(Box::new(DebugEmitter {
81+
source_map: parse_sess.clone_source_map(),
82+
diagnostics,
83+
})));
84+
})),
85+
register_lints: None,
86+
override_queries: None,
87+
registry: registry::Registry::new(rustc_errors::codes::DIAGNOSTICS),
88+
make_codegen_backend: None,
89+
expanded_args: Vec::new(),
90+
ice_file: None,
91+
hash_untracked_state: None,
92+
using_internal_features: Arc::default(),
93+
};
94+
rustc_interface::run_compiler(config, |compiler| {
95+
compiler.enter(|queries| {
96+
queries.global_ctxt().unwrap().enter(|tcx| {
97+
// Run the analysis phase on the local crate to trigger the type error.
98+
let _ = tcx.analysis(());
99+
});
100+
});
101+
// If the compiler has encountered errors when this closure returns, it will abort (!) the program.
102+
// We avoid this by resetting the error count before returning
103+
compiler.sess.dcx().reset_err_count();
104+
});
105+
// Read buffered diagnostics.
106+
buffer.lock().unwrap().iter().for_each(|diagnostic| {
107+
println!("{diagnostic:#?}");
108+
});
109+
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# Early and Late Bound Parameter Implementation Nuances
2+
3+
> Note: this chapter makes reference to information discussed later on in the [representing types][ch_representing_types] chapter. Specifically, it uses concise notation to represent some more complex kinds of types that have not yet been discussed, such as inference variables.
4+
5+
[ch_representing_types]: ../ty.md
6+
7+
Understanding this page likely requires a rudimentary understanding of higher ranked
8+
trait bounds/`for<'a>`and also what types such as `dyn for<'a> Trait<'a>` and
9+
`for<'a> fn(&'a u32)` mean. Reading [the nomincon chapter](https://doc.rust-lang.org/nomicon/hrtb.html)
10+
on HRTB may be useful for understanding this syntax. The meaning of `for<'a> fn(&'a u32)`
11+
is incredibly similar to the meaning of `T: for<'a> Trait<'a>`.
12+
13+
## What does it mean for parameters to be early or late bound
14+
15+
All function definitions conceptually have a ZST (this is represented by `TyKind::FnDef` in rustc).
16+
The only generics on this ZST are the early bound parameters of the function definition. e.g.
17+
```rust
18+
fn foo<'a>(_: &'a u32) {}
19+
20+
fn main() {
21+
let b = foo;
22+
// ^ `b` has type `FnDef(foo, [])` (no args because `'a` is late bound)
23+
assert!(std::mem::size_of_val(&b) == 0);
24+
}
25+
```
26+
27+
In order to call `b` the late bound parameters do need to be provided, these are inferred at the
28+
call site instead of when we refer to `foo`.
29+
```rust
30+
fn main() {
31+
let b = foo;
32+
let a: &'static u32 = &10;
33+
foo(a);
34+
// the lifetime argument for `'a` on `foo` is inferred at the callsite
35+
// the generic parameter `'a` on `foo` is inferred to `'static` here
36+
}
37+
```
38+
39+
Because late bound parameters are not part of the `FnDef`'s args this allows us to prove trait
40+
bounds such as `F: for<'a> Fn(&'a u32)` where `F` is `foo`'s `FnDef`. e.g.
41+
```rust
42+
fn foo_early<'a, T: Trait<'a>>(_: &'a u32, _: T) {}
43+
fn foo_late<'a, T>(_: &'a u32, _: T) {}
44+
45+
fn accepts_hr_func<F: for<'a> Fn(&'a u32, u32)>(_: F) {}
46+
47+
fn main() {
48+
// doesn't work, the instantiated bound is `for<'a> FnDef<'?0>: Fn(&'a u32, u32)`
49+
// `foo_early` only implements `for<'a> FnDef<'a>: Fn(&'a u32, u32)`- the lifetime
50+
// of the borrow in the function argument must be the same as the lifetime
51+
// on the `FnDef`.
52+
accepts_hr_func(foo_early);
53+
54+
// works, the instantiated bound is `for<'a> FnDef: Fn(&'a u32, u32)`
55+
accepts_hr_func(foo_late);
56+
}
57+
58+
// the builtin `Fn` impls for `foo_early` and `foo_late` look something like:
59+
// `foo_early`
60+
impl<'a, T: Trait<'a>> Fn(&'a u32, T) for FooEarlyFnDef<'a, T> { ... }
61+
// `foo_late`
62+
impl<'a, T> Fn(&'a u32, T) for FooLateFnDef<T> { ... }
63+
64+
```
65+
66+
Early bound parameters are present on the `FnDef`. Late bound generic parameters are not present
67+
on the `FnDef` but are instead constrained by the builtin `Fn*` impl.
68+
69+
The same distinction applies to closures. Instead of `FnDef` we are talking about the anonymous
70+
closure type. Closures are [currently unsound](https://github.com/rust-lang/rust/issues/84366) in
71+
ways that are closely related to the distinction between early/late bound
72+
parameters (more on this later)
73+
74+
The early/late boundness of generic parameters is only relevant for the desugaring of
75+
functions/closures into types with builtin `Fn*` impls. It does not make sense to talk about
76+
in other contexts.
77+
78+
The `generics_of` query in rustc only contains early bound parameters. In this way it acts more
79+
like `generics_of(my_func)` is the generics for the FnDef than the generics provided to the function
80+
body although it's not clear to the author of this section if this was the actual justification for
81+
making `generics_of` behave this way.
82+
83+
## What parameters are currently late bound
84+
85+
Below are the current requirements for determining if a generic parameter is late bound. It is worth
86+
keeping in mind that these are not necessarily set in stone and it is almost certainly possible to
87+
be more flexible.
88+
89+
### Must be a lifetime parameter
90+
91+
Rust can't support types such as `for<T> dyn Trait<T>` or `for<T> fn(T)`, this is a
92+
fundamental limitation of the language as we are required to monomorphize type/const
93+
parameters and cannot do so behind dynamic dispatch. (technically we could probably
94+
support `for<T> dyn MarkerTrait<T>` as there is nothing to monomorphize)
95+
96+
Not being able to support `for<T> dyn Trait<T>` resulted in making all type and const
97+
parameters early bound. Only lifetime parameters can be late bound.
98+
99+
### Must not appear in the where clauses
100+
101+
In order for a generic parameter to be late bound it must not appear in any where clauses.
102+
This is currently an incredibly simplistic check that causes lifetimes to be early bound even
103+
if the where clause they appear in are always true, or implied by well formedness of function
104+
arguments. e.g.
105+
```rust
106+
fn foo1<'a: 'a>(_: &'a u32) {}
107+
// ^^ early bound parameter because it's in a `'a: 'a` clause
108+
// even though the bound obviously holds all the time
109+
fn foo2<'a, T: Trait<'a>(a: T, b: &'a u32) {}
110+
// ^^ early bound parameter because it's used in the `T: Trait<'a>` clause
111+
fn foo3<'a, T: 'a>(_: &'a T) {}
112+
// ^^ early bound parameter because it's used in the `T: 'a` clause
113+
// even though that bound is implied by wellformedness of `&'a T`
114+
fn foo4<'a, 'b: 'a>(_: Inv<&'a ()>, _: Inv<&'b ()>) {}
115+
// ^^ ^^ ^^^ note:
116+
// ^^ ^^ `Inv` stands for `Invariant` and is used to
117+
// ^^ ^^ make the type parameter invariant. This
118+
// ^^ ^^ is necessary for demonstration purposes as
119+
// ^^ ^^ `for<'a, 'b> fn(&'a (), &'b ())` and
120+
// ^^ ^^ `for<'a> fn(&'a u32, &'a u32)` are subtypes-
121+
// ^^ ^^ of each other which makes the bound trivially
122+
// ^^ ^^ satisfiable when making the fnptr. `Inv`
123+
// ^^ ^^ disables this subtyping.
124+
// ^^ ^^
125+
// ^^^^^^ both early bound parameters because they are present in the
126+
// `'b: 'a` clause
127+
```
128+
129+
The reason for this requirement is that we cannot represent the `T: Trait<'a>` or `'a: 'b` clauses
130+
on a function pointer. `for<'a, 'b> fn(Inv<&'a ()>, Inv<&'b ()>)` is not a valid function pointer to
131+
represent`foo4` as it would allow calling the function without `'b: 'a` holding.
132+
133+
### Must be constrained by where clauses or function argument types
134+
135+
The builtin impls of the `Fn*` traits for closures and `FnDef`s cannot not have any unconstrained
136+
parameters. For example the following impl is illegal:
137+
```rust
138+
impl<'a> Trait for u32 { type Assoc = &'a u32; }
139+
```
140+
We must not end up with a similar impl for the `Fn*` traits e.g.
141+
```rust
142+
impl<'a> Fn<()> for FnDef { type Assoc = &'a u32 }
143+
```
144+
145+
Violating this rule can trivially lead to unsoundness as seen in [#84366](https://github.com/rust-lang/rust/issues/84366).
146+
Additionally if we ever support late bound type params then an impl like:
147+
```rust
148+
impl<T> Fn<()> for FnDef { type Assoc = T; }
149+
```
150+
would break the compiler in various ways.
151+
152+
In order to ensure that everything functions correctly, we do not allow generic parameters to
153+
be late bound if it would result in a builtin impl that does not constrain all of the generic
154+
parameters on the builtin impl. Making a generic parameter be early bound trivially makes it be
155+
constrained by the builtin impl as it ends up on the self type.
156+
157+
Because of the requirement that late bound parameters must not appear in where clauses, checking
158+
this is simpler than the rules for checking impl headers constrain all the parameters on the impl.
159+
We only have to ensure that all late bound parameters appear at least once in the function argument
160+
types outside of an alias (e.g. an associated type).
161+
162+
The requirement that they not indirectly be in the args of an alias for it to count is the
163+
same as why the follow code is forbidden:
164+
```rust
165+
impl<T: Trait> OtherTrait for <T as Trait>::Assoc { type Assoc = T }
166+
```
167+
There is no guarantee that `<T as Trait>::Assoc` will normalize to different types for every
168+
instantiation of `T`. If we were to allow this impl we could get overlapping impls and the
169+
same is true of the builtin `Fn*` impls.
170+
171+
## Making more generic parameters late bound
172+
173+
It is generally considered desirable for more parameters to be late bound as it makes
174+
the builtin `Fn*` impls more flexible. Right now many of the requirements for making
175+
a parameter late bound are overly restrictive as they are tied to what we can currently
176+
(or can ever) do with fn ptrs.
177+
178+
It would be theoretically possible to support late bound params in `where`-clauses in the
179+
language by introducing implication types which would allow us to express types such as:
180+
`for<'a, 'b: 'a> fn(Inv<&'a u32>, Inv<&'b u32>)` which would ensure `'b: 'a` is upheld when
181+
calling the function pointer.
182+
183+
It would also be theoretically possible to support it by making the coercion to a fn ptr
184+
instantiate the parameter with an infer var while still allowing the FnDef to not have the
185+
generic parameter present as trait impls are perfectly capable of representing the where clauses
186+
on the function on the impl itself. This would also allow us to support late bound type/const
187+
vars allowing bounds like `F: for<T> Fn(T)` to hold.
188+
189+
It is almost somewhat unclear if we can change the `Fn` traits to be structured differently
190+
so that we never have to make a parameter early bound just to make the builtin impl have all
191+
generics be constrained. Of all the possible causes of a generic parameter being early bound
192+
this seems the most difficult to remove.
193+
194+
Whether these would be good ideas to implement is a separate question- they are only brought
195+
up to illustrate that the current rules are not necessarily set in stone and a result of
196+
"its the only way of doing this".
197+

0 commit comments

Comments
 (0)