Auto merge of #112702 - Dylan-DPC:rollup-12d6qay, r=Dylan-DPC
Rollup of 7 pull requests Successful merges: - #112163 (fix: inline `predicate_may_hold_fatal` and remove expect call in it) - #112399 (Instantiate closure synthetic substs in root universe) - #112443 (Opportunistically resolve regions in new solver) - #112535 (reorder attributes to make miri-test-libstd work again) - #112579 (Fix building libstd documentation on FreeBSD.) - #112639 (Fix `dead_code_cgu` computation) - #112642 (Handle interpolated literal errors) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
2304917aad
18 changed files with 208 additions and 89 deletions
|
@ -98,7 +98,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
self.tcx.typeck_root_def_id(expr_def_id.to_def_id()),
|
||||
);
|
||||
|
||||
let tupled_upvars_ty = self.next_ty_var(TypeVariableOrigin {
|
||||
let tupled_upvars_ty = self.next_root_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::ClosureSynthetic,
|
||||
span: self.tcx.def_span(expr_def_id),
|
||||
});
|
||||
|
@ -143,7 +143,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
|
||||
// Create a type variable (for now) to represent the closure kind.
|
||||
// It will be unified during the upvar inference phase (`upvar.rs`)
|
||||
None => self.next_ty_var(TypeVariableOrigin {
|
||||
None => self.next_root_ty_var(TypeVariableOrigin {
|
||||
// FIXME(eddyb) distinguish closure kind inference variables from the rest.
|
||||
kind: TypeVariableOriginKind::ClosureSynthetic,
|
||||
span: expr_span,
|
||||
|
|
|
@ -189,6 +189,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
pub fn errors_reported_since_creation(&self) -> bool {
|
||||
self.tcx.sess.err_count() > self.err_count_on_creation
|
||||
}
|
||||
|
||||
pub fn next_root_ty_var(&self, origin: TypeVariableOrigin) -> Ty<'tcx> {
|
||||
self.tcx.mk_ty_var(self.next_ty_var_id_in_universe(origin, ty::UniverseIndex::ROOT))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Deref for FnCtxt<'a, 'tcx> {
|
||||
|
|
|
@ -82,15 +82,40 @@ impl CanonicalVarValues<'_> {
|
|||
}
|
||||
|
||||
pub fn is_identity_modulo_regions(&self) -> bool {
|
||||
self.var_values.iter().enumerate().all(|(bv, arg)| match arg.unpack() {
|
||||
ty::GenericArgKind::Lifetime(_) => true,
|
||||
ty::GenericArgKind::Type(ty) => {
|
||||
matches!(*ty.kind(), ty::Bound(ty::INNERMOST, bt) if bt.var.as_usize() == bv)
|
||||
let mut var = ty::BoundVar::from_u32(0);
|
||||
for arg in self.var_values {
|
||||
match arg.unpack() {
|
||||
ty::GenericArgKind::Lifetime(r) => {
|
||||
if let ty::ReLateBound(ty::INNERMOST, br) = *r
|
||||
&& var == br.var
|
||||
{
|
||||
var = var + 1;
|
||||
} else {
|
||||
// It's ok if this region var isn't unique
|
||||
}
|
||||
},
|
||||
ty::GenericArgKind::Type(ty) => {
|
||||
if let ty::Bound(ty::INNERMOST, bt) = *ty.kind()
|
||||
&& var == bt.var
|
||||
{
|
||||
var = var + 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ty::GenericArgKind::Const(ct) => {
|
||||
if let ty::ConstKind::Bound(ty::INNERMOST, bc) = ct.kind()
|
||||
&& var == bc
|
||||
{
|
||||
var = var + 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
ty::GenericArgKind::Const(ct) => {
|
||||
matches!(ct.kind(), ty::ConstKind::Bound(ty::INNERMOST, bc) if bc.as_usize() == bv)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -155,15 +155,17 @@ where
|
|||
// functions and statics defined in the local crate.
|
||||
let PlacedRootMonoItems { mut codegen_units, internalization_candidates, unique_inlined_stats } = {
|
||||
let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_place_roots");
|
||||
place_root_mono_items(cx, mono_items)
|
||||
let mut placed = place_root_mono_items(cx, mono_items);
|
||||
|
||||
for cgu in &mut placed.codegen_units {
|
||||
cgu.create_size_estimate(tcx);
|
||||
}
|
||||
|
||||
debug_dump(tcx, "ROOTS", &placed.codegen_units, placed.unique_inlined_stats);
|
||||
|
||||
placed
|
||||
};
|
||||
|
||||
for cgu in &mut codegen_units {
|
||||
cgu.create_size_estimate(tcx);
|
||||
}
|
||||
|
||||
debug_dump(tcx, "ROOTS", &codegen_units, unique_inlined_stats);
|
||||
|
||||
// Merge until we have at most `max_cgu_count` codegen units.
|
||||
// `merge_codegen_units` is responsible for updating the CGU size
|
||||
// estimates.
|
||||
|
@ -179,59 +181,34 @@ where
|
|||
// local functions the definition of which is marked with `#[inline]`.
|
||||
{
|
||||
let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_place_inline_items");
|
||||
place_inlined_mono_items(cx, &mut codegen_units)
|
||||
};
|
||||
place_inlined_mono_items(cx, &mut codegen_units);
|
||||
|
||||
for cgu in &mut codegen_units {
|
||||
cgu.create_size_estimate(tcx);
|
||||
for cgu in &mut codegen_units {
|
||||
cgu.create_size_estimate(tcx);
|
||||
}
|
||||
|
||||
debug_dump(tcx, "INLINE", &codegen_units, unique_inlined_stats);
|
||||
}
|
||||
|
||||
debug_dump(tcx, "INLINE", &codegen_units, unique_inlined_stats);
|
||||
|
||||
// Next we try to make as many symbols "internal" as possible, so LLVM has
|
||||
// more freedom to optimize.
|
||||
if !tcx.sess.link_dead_code() {
|
||||
let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_internalize_symbols");
|
||||
internalize_symbols(cx, &mut codegen_units, internalization_candidates);
|
||||
|
||||
debug_dump(tcx, "INTERNALIZE", &codegen_units, unique_inlined_stats);
|
||||
}
|
||||
|
||||
// Mark one CGU for dead code, if necessary.
|
||||
let instrument_dead_code =
|
||||
tcx.sess.instrument_coverage() && !tcx.sess.instrument_coverage_except_unused_functions();
|
||||
|
||||
if instrument_dead_code {
|
||||
assert!(
|
||||
codegen_units.len() > 0,
|
||||
"There must be at least one CGU that code coverage data can be generated in."
|
||||
);
|
||||
|
||||
// Find the smallest CGU that has exported symbols and put the dead
|
||||
// function stubs in that CGU. We look for exported symbols to increase
|
||||
// the likelihood the linker won't throw away the dead functions.
|
||||
// FIXME(#92165): In order to truly resolve this, we need to make sure
|
||||
// the object file (CGU) containing the dead function stubs is included
|
||||
// in the final binary. This will probably require forcing these
|
||||
// function symbols to be included via `-u` or `/include` linker args.
|
||||
let mut cgus: Vec<_> = codegen_units.iter_mut().collect();
|
||||
cgus.sort_by_key(|cgu| cgu.size_estimate());
|
||||
|
||||
let dead_code_cgu =
|
||||
if let Some(cgu) = cgus.into_iter().rev().find(|cgu| {
|
||||
cgu.items().iter().any(|(_, (linkage, _))| *linkage == Linkage::External)
|
||||
}) {
|
||||
cgu
|
||||
} else {
|
||||
// If there are no CGUs that have externally linked items,
|
||||
// then we just pick the first CGU as a fallback.
|
||||
&mut codegen_units[0]
|
||||
};
|
||||
dead_code_cgu.make_code_coverage_dead_code_cgu();
|
||||
mark_code_coverage_dead_code_cgu(&mut codegen_units);
|
||||
}
|
||||
|
||||
// Ensure CGUs are sorted by name, so that we get deterministic results.
|
||||
assert!(codegen_units.is_sorted_by(|a, b| Some(a.name().as_str().cmp(b.name().as_str()))));
|
||||
|
||||
debug_dump(tcx, "FINAL", &codegen_units, unique_inlined_stats);
|
||||
|
||||
codegen_units
|
||||
}
|
||||
|
||||
|
@ -363,9 +340,7 @@ fn merge_codegen_units<'tcx>(
|
|||
|
||||
// Move the mono-items from `smallest` to `second_smallest`
|
||||
second_smallest.modify_size_estimate(smallest.size_estimate());
|
||||
for (k, v) in smallest.items_mut().drain() {
|
||||
second_smallest.items_mut().insert(k, v);
|
||||
}
|
||||
second_smallest.items_mut().extend(smallest.items_mut().drain());
|
||||
|
||||
// Record that `second_smallest` now contains all the stuff that was
|
||||
// in `smallest` before.
|
||||
|
@ -545,6 +520,28 @@ fn internalize_symbols<'tcx>(
|
|||
}
|
||||
}
|
||||
|
||||
fn mark_code_coverage_dead_code_cgu<'tcx>(codegen_units: &mut [CodegenUnit<'tcx>]) {
|
||||
assert!(!codegen_units.is_empty());
|
||||
|
||||
// Find the smallest CGU that has exported symbols and put the dead
|
||||
// function stubs in that CGU. We look for exported symbols to increase
|
||||
// the likelihood the linker won't throw away the dead functions.
|
||||
// FIXME(#92165): In order to truly resolve this, we need to make sure
|
||||
// the object file (CGU) containing the dead function stubs is included
|
||||
// in the final binary. This will probably require forcing these
|
||||
// function symbols to be included via `-u` or `/include` linker args.
|
||||
let dead_code_cgu = codegen_units
|
||||
.iter_mut()
|
||||
.filter(|cgu| cgu.items().iter().any(|(_, (linkage, _))| *linkage == Linkage::External))
|
||||
.min_by_key(|cgu| cgu.size_estimate());
|
||||
|
||||
// If there are no CGUs that have externally linked items, then we just
|
||||
// pick the first CGU as a fallback.
|
||||
let dead_code_cgu = if let Some(cgu) = dead_code_cgu { cgu } else { &mut codegen_units[0] };
|
||||
|
||||
dead_code_cgu.make_code_coverage_dead_code_cgu();
|
||||
}
|
||||
|
||||
fn characteristic_def_id_of_mono_item<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
mono_item: MonoItem<'tcx>,
|
||||
|
|
|
@ -2023,17 +2023,14 @@ impl<'a> Parser<'a> {
|
|||
let recovered = self.recover_after_dot();
|
||||
let token = recovered.as_ref().unwrap_or(&self.token);
|
||||
match token::Lit::from_token(token) {
|
||||
Some(token_lit) => {
|
||||
match MetaItemLit::from_token_lit(token_lit, token.span) {
|
||||
Some(lit) => {
|
||||
match MetaItemLit::from_token_lit(lit, token.span) {
|
||||
Ok(lit) => {
|
||||
self.bump();
|
||||
Some(lit)
|
||||
}
|
||||
Err(err) => {
|
||||
let span = token.span;
|
||||
let token::Literal(lit) = token.kind else {
|
||||
unreachable!();
|
||||
};
|
||||
let span = token.uninterpolated_span();
|
||||
self.bump();
|
||||
report_lit_error(&self.sess, err, lit, span);
|
||||
// Pack possible quotes and prefixes from the original literal into
|
||||
|
|
|
@ -208,8 +208,25 @@ impl<'tcx> TypeFolder<TyCtxt<'tcx>> for Canonicalizer<'_, 'tcx> {
|
|||
t
|
||||
}
|
||||
|
||||
fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
|
||||
let r = self.infcx.shallow_resolve(r);
|
||||
fn fold_region(&mut self, mut r: ty::Region<'tcx>) -> ty::Region<'tcx> {
|
||||
match self.canonicalize_mode {
|
||||
CanonicalizeMode::Input => {
|
||||
// Don't resolve infer vars in input, since it affects
|
||||
// caching and may cause trait selection bugs which rely
|
||||
// on regions to be equal.
|
||||
}
|
||||
CanonicalizeMode::Response { .. } => {
|
||||
if let ty::ReVar(vid) = *r {
|
||||
r = self
|
||||
.infcx
|
||||
.inner
|
||||
.borrow_mut()
|
||||
.unwrap_region_constraints()
|
||||
.opportunistic_resolve_var(self.infcx.tcx, vid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let kind = match *r {
|
||||
ty::ReLateBound(..) => return r,
|
||||
|
||||
|
|
|
@ -263,7 +263,10 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
let (_orig_values, canonical_goal) = self.canonicalize_goal(goal);
|
||||
let new_canonical_response =
|
||||
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
|
||||
if !new_canonical_response.value.var_values.is_identity() {
|
||||
// We only check for modulo regions as we convert all regions in
|
||||
// the input to new existentials, even if they're expected to be
|
||||
// `'static` or a placeholder region.
|
||||
if !new_canonical_response.value.var_values.is_identity_modulo_regions() {
|
||||
bug!(
|
||||
"unstable result: re-canonicalized goal={canonical_goal:#?} \
|
||||
first_response={canonical_response:#?} \
|
||||
|
|
|
@ -292,7 +292,12 @@ fn impl_intersection_has_impossible_obligation<'cx, 'tcx>(
|
|||
Obligation::new(infcx.tcx, ObligationCause::dummy(), param_env, predicate)
|
||||
})
|
||||
.chain(obligations)
|
||||
.find(|o| !selcx.predicate_may_hold_fatal(o));
|
||||
.find(|o| {
|
||||
selcx.evaluate_root_obligation(o).map_or(
|
||||
false, // Overflow has occurred, and treat the obligation as possibly holding.
|
||||
|result| !result.may_apply(),
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(failing_obligation) = opt_failing_obligation {
|
||||
debug!("overlap: obligation unsatisfiable {:?}", failing_obligation);
|
||||
|
|
|
@ -518,19 +518,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
// The result is "true" if the obligation *may* hold and "false" if
|
||||
// we can be sure it does not.
|
||||
|
||||
/// Evaluates whether the obligation `obligation` can be satisfied (by any means).
|
||||
pub fn predicate_may_hold_fatal(&mut self, obligation: &PredicateObligation<'tcx>) -> bool {
|
||||
debug!(?obligation, "predicate_may_hold_fatal");
|
||||
|
||||
// This fatal query is a stopgap that should only be used in standard mode,
|
||||
// where we do not expect overflow to be propagated.
|
||||
assert!(self.query_mode == TraitQueryMode::Standard);
|
||||
|
||||
self.evaluate_root_obligation(obligation)
|
||||
.expect("Overflow should be caught earlier in standard query mode")
|
||||
.may_apply()
|
||||
}
|
||||
|
||||
/// Evaluates whether the obligation `obligation` can be satisfied
|
||||
/// and returns an `EvaluationResult`. This is meant for the
|
||||
/// *initial* call.
|
||||
|
|
|
@ -56,6 +56,11 @@
|
|||
//! [`Rc`]: rc
|
||||
//! [`RefCell`]: core::cell
|
||||
|
||||
// To run alloc tests without x.py without ending up with two copies of alloc, Miri needs to be
|
||||
// able to "empty" this crate. See <https://github.com/rust-lang/miri-test-libstd/issues/4>.
|
||||
// rustc itself never sets the feature, so this line has no affect there.
|
||||
#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))]
|
||||
//
|
||||
#![allow(unused_attributes)]
|
||||
#![stable(feature = "alloc", since = "1.36.0")]
|
||||
#![doc(
|
||||
|
@ -75,11 +80,6 @@
|
|||
))]
|
||||
#![no_std]
|
||||
#![needs_allocator]
|
||||
// To run alloc tests without x.py without ending up with two copies of alloc, Miri needs to be
|
||||
// able to "empty" this crate. See <https://github.com/rust-lang/miri-test-libstd/issues/4>.
|
||||
// rustc itself never sets the feature, so this line has no affect there.
|
||||
#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))]
|
||||
//
|
||||
// Lints:
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
#![deny(fuzzy_provenance_casts)]
|
||||
|
|
|
@ -188,6 +188,13 @@
|
|||
//! [array]: prim@array
|
||||
//! [slice]: prim@slice
|
||||
|
||||
// To run std tests without x.py without ending up with two copies of std, Miri needs to be
|
||||
// able to "empty" this crate. See <https://github.com/rust-lang/miri-test-libstd/issues/4>.
|
||||
// rustc itself never sets the feature, so this line has no affect there.
|
||||
#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))]
|
||||
// miri-test-libstd also prefers to make std use the sysroot versions of the dependencies.
|
||||
#![cfg_attr(feature = "miri-test-libstd", feature(rustc_private))]
|
||||
//
|
||||
#![cfg_attr(not(feature = "restricted-std"), stable(feature = "rust1", since = "1.0.0"))]
|
||||
#![cfg_attr(feature = "restricted-std", unstable(feature = "restricted_std", issue = "none"))]
|
||||
#![doc(
|
||||
|
@ -202,12 +209,6 @@
|
|||
no_global_oom_handling,
|
||||
not(no_global_oom_handling)
|
||||
))]
|
||||
// To run std tests without x.py without ending up with two copies of std, Miri needs to be
|
||||
// able to "empty" this crate. See <https://github.com/rust-lang/miri-test-libstd/issues/4>.
|
||||
// rustc itself never sets the feature, so this line has no affect there.
|
||||
#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))]
|
||||
// miri-test-libstd also prefers to make std use the sysroot versions of the dependencies.
|
||||
#![cfg_attr(feature = "miri-test-libstd", feature(rustc_private))]
|
||||
// Don't link to std. We are std.
|
||||
#![no_std]
|
||||
// Tell the compiler to link to either panic_abort or panic_unwind
|
||||
|
|
|
@ -17,6 +17,7 @@ mod libc {
|
|||
pub use libc::c_int;
|
||||
pub struct ucred;
|
||||
pub struct cmsghdr;
|
||||
pub struct sockcred2;
|
||||
pub type pid_t = i32;
|
||||
pub type gid_t = u32;
|
||||
pub type uid_t = u32;
|
||||
|
|
10
tests/ui/parser/lit-err-in-macro.rs
Normal file
10
tests/ui/parser/lit-err-in-macro.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
macro_rules! f {
|
||||
($abi:literal) => {
|
||||
extern $abi fn f() {}
|
||||
}
|
||||
}
|
||||
|
||||
f!("Foo"__);
|
||||
//~^ ERROR suffixes on string literals are invalid
|
||||
|
||||
fn main() {}
|
8
tests/ui/parser/lit-err-in-macro.stderr
Normal file
8
tests/ui/parser/lit-err-in-macro.stderr
Normal file
|
@ -0,0 +1,8 @@
|
|||
error: suffixes on string literals are invalid
|
||||
--> $DIR/lit-err-in-macro.rs:7:4
|
||||
|
|
||||
LL | f!("Foo"__);
|
||||
| ^^^^^^^ invalid suffix `__`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
9
tests/ui/traits/issue-105231.rs
Normal file
9
tests/ui/traits/issue-105231.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
//~ ERROR overflow evaluating the requirement `A<A<A<A<A<A<A<...>>>>>>>: Send`
|
||||
struct A<T>(B<T>);
|
||||
//~^ ERROR recursive types `A` and `B` have infinite size
|
||||
struct B<T>(A<A<T>>);
|
||||
trait Foo {}
|
||||
impl<T> Foo for T where T: Send {}
|
||||
impl Foo for B<u8> {}
|
||||
|
||||
fn main() {}
|
29
tests/ui/traits/issue-105231.stderr
Normal file
29
tests/ui/traits/issue-105231.stderr
Normal file
|
@ -0,0 +1,29 @@
|
|||
error[E0072]: recursive types `A` and `B` have infinite size
|
||||
--> $DIR/issue-105231.rs:2:1
|
||||
|
|
||||
LL | struct A<T>(B<T>);
|
||||
| ^^^^^^^^^^^ ---- recursive without indirection
|
||||
LL |
|
||||
LL | struct B<T>(A<A<T>>);
|
||||
| ^^^^^^^^^^^ ------- recursive without indirection
|
||||
|
|
||||
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
||||
|
|
||||
LL ~ struct A<T>(Box<B<T>>);
|
||||
LL |
|
||||
LL ~ struct B<T>(Box<A<A<T>>>);
|
||||
|
|
||||
|
||||
error[E0275]: overflow evaluating the requirement `A<A<A<A<A<A<A<...>>>>>>>: Send`
|
||||
|
|
||||
= help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`issue_105231`)
|
||||
note: required because it appears within the type `B<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<u8>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`
|
||||
--> $DIR/issue-105231.rs:4:8
|
||||
|
|
||||
LL | struct B<T>(A<A<T>>);
|
||||
| ^
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
Some errors have detailed explanations: E0072, E0275.
|
||||
For more information about an error, try `rustc --explain E0072`.
|
7
tests/ui/traits/new-solver/closure-substs-ambiguity.rs
Normal file
7
tests/ui/traits/new-solver/closure-substs-ambiguity.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
// compile-flags: -Ztrait-solver=next
|
||||
// check-pass
|
||||
|
||||
fn main() {
|
||||
let mut x: Vec<_> = vec![];
|
||||
x.extend(Some(1i32).into_iter().map(|x| x));
|
||||
}
|
19
tests/ui/traits/new-solver/opportunistic-region-resolve.rs
Normal file
19
tests/ui/traits/new-solver/opportunistic-region-resolve.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
// compile-flags: -Ztrait-solver=next
|
||||
// check-pass
|
||||
|
||||
#![feature(rustc_attrs)]
|
||||
|
||||
#[rustc_coinductive]
|
||||
trait Trait {}
|
||||
|
||||
#[rustc_coinductive]
|
||||
trait Indirect {}
|
||||
impl<T: Trait + ?Sized> Indirect for T {}
|
||||
|
||||
impl<'a> Trait for &'a () where &'a (): Indirect {}
|
||||
|
||||
fn impls_trait<T: Trait>() {}
|
||||
|
||||
fn main() {
|
||||
impls_trait::<&'static ()>();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue