diff --git a/src/libcore/ops.rs b/src/libcore/ops.rs index 8d072fffb60a4..ac735492be4a5 100644 --- a/src/libcore/ops.rs +++ b/src/libcore/ops.rs @@ -866,13 +866,45 @@ pub trait FnOnce { extern "rust-call" fn call_once(self, args: Args) -> Result; } -macro_rules! def_fn_mut( +impl FnMut for F + where F : Fn +{ + extern "rust-call" fn call_mut(&mut self, args: A) -> R { + self.call(args) + } +} + +impl FnOnce for F + where F : FnMut +{ + extern "rust-call" fn call_once(mut self, args: A) -> R { + self.call_mut(args) + } +} + + +impl Fn<(),Result> for extern "Rust" fn() -> Result { + #[allow(non_snake_case)] + extern "rust-call" fn call(&self, _args: ()) -> Result { + (*self)() + } +} + +impl Fn<(A0,),Result> for extern "Rust" fn(A0) -> Result { + #[allow(non_snake_case)] + extern "rust-call" fn call(&self, args: (A0,)) -> Result { + let (a0,) = args; + (*self)(a0) + } +} + +macro_rules! def_fn( ($($args:ident)*) => ( impl - FnMut<($($args,)*),Result> + Fn<($($args,)*),Result> for extern "Rust" fn($($args: $args,)*) -> Result { #[allow(non_snake_case)] - extern "rust-call" fn call_mut(&mut self, args: ($($args,)*)) -> Result { + extern "rust-call" fn call(&self, args: ($($args,)*)) -> Result { let ($($args,)*) = args; (*self)($($args,)*) } @@ -880,20 +912,18 @@ macro_rules! def_fn_mut( ) ) -def_fn_mut!() -def_fn_mut!(A0) -def_fn_mut!(A0 A1) -def_fn_mut!(A0 A1 A2) -def_fn_mut!(A0 A1 A2 A3) -def_fn_mut!(A0 A1 A2 A3 A4) -def_fn_mut!(A0 A1 A2 A3 A4 A5) -def_fn_mut!(A0 A1 A2 A3 A4 A5 A6) -def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7) -def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7 A8) -def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9) -def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10) -def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11) -def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12) -def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13) -def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14) -def_fn_mut!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15) +def_fn!(A0 A1) +def_fn!(A0 A1 A2) +def_fn!(A0 A1 A2 A3) +def_fn!(A0 A1 A2 A3 A4) +def_fn!(A0 A1 A2 A3 A4 A5) +def_fn!(A0 A1 A2 A3 A4 A5 A6) +def_fn!(A0 A1 A2 A3 A4 A5 A6 A7) +def_fn!(A0 A1 A2 A3 A4 A5 A6 A7 A8) +def_fn!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9) +def_fn!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10) +def_fn!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11) +def_fn!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12) +def_fn!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13) +def_fn!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14) +def_fn!(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15) diff --git a/src/librustc/middle/traits/coherence.rs b/src/librustc/middle/traits/coherence.rs index 8b2ddca313197..09490f9bdf7e9 100644 --- a/src/librustc/middle/traits/coherence.rs +++ b/src/librustc/middle/traits/coherence.rs @@ -43,7 +43,7 @@ pub fn impl_can_satisfy(infcx: &InferCtxt, // Determine whether `impl2` can provide an implementation for those // same types. let param_env = ty::empty_parameter_environment(); - let mut selcx = SelectionContext::new(infcx, ¶m_env, infcx.tcx); + let mut selcx = SelectionContext::intercrate(infcx, ¶m_env, infcx.tcx); let obligation = Obligation::misc(DUMMY_SP, impl1_trait_ref); debug!("impl_can_satisfy obligation={}", obligation.repr(infcx.tcx)); selcx.evaluate_impl(impl2_def_id, &obligation) diff --git a/src/librustc/middle/traits/select.rs b/src/librustc/middle/traits/select.rs index 9a2d5b4d4dc0a..09bd0f52985b1 100644 --- a/src/librustc/middle/traits/select.rs +++ b/src/librustc/middle/traits/select.rs @@ -45,6 +45,22 @@ pub struct SelectionContext<'cx, 'tcx:'cx> { /// which is important for checking for trait bounds that /// recursively require themselves. skolemizer: TypeSkolemizer<'cx, 'tcx>, + + /// If true, indicates that the evaluation should be conservative + /// and consider the possibility of types outside this crate. + /// This comes up primarily when resolving ambiguity. Imagine + /// there is some trait reference `$0 : Bar` where `$0` is an + /// inference variable. If `intercrate` is true, then we can never + /// say for sure that this reference is not implemented, even if + /// there are *no impls at all for `Bar`*, because `$0` could be + /// bound to some type that in a downstream crate that implements + /// `Bar`. This is the suitable mode for coherence. Elsewhere, + /// though, we set this to false, because we are only interested + /// in types that the user could actually have written --- in + /// other words, we consider `$0 : Bar` to be unimplemented if + /// there is no type that the user could *actually name* that + /// would satisfy it. This avoids crippling inference, basically. + intercrate: bool, } // A stack that walks back up the stack frame. @@ -142,6 +158,20 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { param_env: param_env, typer: typer, skolemizer: infcx.skolemizer(), + intercrate: false, + } + } + + pub fn intercrate(infcx: &'cx InferCtxt<'cx, 'tcx>, + param_env: &'cx ty::ParameterEnvironment, + typer: &'cx Typer<'tcx>) + -> SelectionContext<'cx, 'tcx> { + SelectionContext { + infcx: infcx, + param_env: param_env, + typer: typer, + skolemizer: infcx.skolemizer(), + intercrate: true, } } @@ -214,44 +244,20 @@ 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. - pub fn evaluate_obligation_intercrate(&mut self, - obligation: &Obligation) - -> bool + pub fn evaluate_obligation(&mut self, + obligation: &Obligation) + -> bool { /*! * Evaluates whether the obligation `obligation` can be - * satisfied (by any means). This "intercrate" version allows - * for the possibility that unbound type variables may be - * instantiated with types from another crate. This is - * important for coherence. In practice this means that - * unbound type variables must always be considered ambiguous. + * satisfied (by any means). */ - debug!("evaluate_obligation_intercrate({})", + debug!("evaluate_obligation({})", obligation.repr(self.tcx())); let stack = self.push_stack(None, obligation); - self.evaluate_stack_intercrate(&stack).may_apply() - } - - pub fn evaluate_obligation_intracrate(&mut self, - obligation: &Obligation) - -> bool - { - /*! - * Evaluates whether the obligation `obligation` can be - * satisfied (by any means). This "intracrate" version does - * not allow for the possibility that unbound type variables - * may be instantiated with types from another crate; hence, - * if there are unbound inputs but no crates locally visible, - * it considers the result to be unimplemented. - */ - - debug!("evaluate_obligation_intracrate({})", - obligation.repr(self.tcx())); - - let stack = self.push_stack(None, obligation); - self.evaluate_stack_intracrate(&stack).may_apply() + self.evaluate_stack(&stack).may_apply() } fn evaluate_builtin_bound_recursively(&mut self, @@ -288,46 +294,53 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let stack = self.push_stack(previous_stack.map(|x| x), obligation); - // FIXME(#17901) -- Intercrate vs intracrate resolution is a - // tricky question here. For coherence, we want - // intercrate. Also, there was a nasty cycle around impls like - // `impl Eq for Vec` (which would wind up checking - // whether `$0:Eq`, where $0 was the value substituted for - // `T`, which could then be checked against the very same - // impl). This problem is avoided by the stricter rules around - // unbound type variables by intercrate. I suspect that in the - // latter case a more fine-grained rule would suffice (i.e., - // consider it ambiguous if even 1 impl matches, no need to - // figure out which one, but call it unimplemented if 0 impls - // match). - let result = self.evaluate_stack_intercrate(&stack); + let result = self.evaluate_stack(&stack); debug!("result: {}", result); result } - fn evaluate_stack_intercrate(&mut self, + fn evaluate_stack(&mut self, stack: &ObligationStack) -> EvaluationResult { - // Whenever any of the types are unbound, there can always be - // an impl. Even if there are no impls in this crate, perhaps - // the type would be unified with something from another crate - // that does provide an impl. + // In intercrate mode, whenever any of the types are unbound, + // there can always be an impl. Even if there are no impls in + // this crate, perhaps the type would be unified with + // something from another crate that does provide an impl. + // + // In intracrate mode, we must still be conservative. The reason is + // that we want to avoid cycles. Imagine an impl like: + // + // impl Eq for Vec + // + // and a trait reference like `$0 : Eq` where `$0` is an + // unbound variable. When we evaluate this trait-reference, we + // will unify `$0` with `Vec<$1>` (for some fresh variable + // `$1`), on the condition that `$1 : Eq`. We will then wind + // up with many candidates (since that are other `Eq` impls + // that apply) and try to winnow things down. This results in + // a recurssive evaluation that `$1 : Eq` -- as you can + // imagine, this is just where we started. To avoid that, we + // check for unbound variables and return an ambiguous (hence possible) + // match if we've seen this trait before. + // + // This suffices to allow chains like `FnMut` implemented in + // terms of `Fn` etc, but we could probably make this more + // precise still. let input_types = stack.skol_trait_ref.input_types(); - if input_types.iter().any(|&t| ty::type_is_skolemized(t)) { - debug!("evaluate_stack_intercrate({}) --> unbound argument, must be ambiguous", + let unbound_input_types = input_types.iter().any(|&t| ty::type_is_skolemized(t)); + if + unbound_input_types && + (self.intercrate || + stack.iter().skip(1).any( + |prev| stack.skol_trait_ref.def_id == prev.skol_trait_ref.def_id)) + { + debug!("evaluate_stack_intracrate({}) --> unbound argument, recursion --> ambiguous", stack.skol_trait_ref.repr(self.tcx())); return EvaluatedToAmbig; } - self.evaluate_stack_intracrate(stack) - } - - fn evaluate_stack_intracrate(&mut self, - stack: &ObligationStack) - -> EvaluationResult - { // If there is any previous entry on the stack that precisely // matches this obligation, then we can assume that the // obligation is satisfied for now (still all other conditions @@ -592,7 +605,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { Err(_) => { return Err(()); } } - if self.evaluate_obligation_intracrate(obligation) { + if self.evaluate_obligation(obligation) { Ok(()) } else { Err(()) @@ -804,12 +817,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { &candidates[i], &candidates[j])); if is_dup { - debug!("Dropping candidate #{}/#{}: {}", + debug!("Dropping candidate #{}/{}: {}", i, candidates.len(), candidates[i].repr(self.tcx())); candidates.swap_remove(i); } else { - debug!("Retaining candidate #{}/#{}", - i, candidates.len()); + debug!("Retaining candidate #{}/{}: {}", + i, candidates.len(), candidates[i].repr(self.tcx())); i += 1; } } @@ -828,7 +841,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // be the case that you could still satisfy the obligation // from another crate by instantiating the type variables with // a type from another crate that does have an impl. This case - // is checked for in `evaluate_obligation` (and hence users + // is checked for in `evaluate_stack` (and hence users // who might care about this case, like coherence, should use // that function). if candidates.len() == 0 { @@ -849,6 +862,17 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // global cache. We want the cache that is specific to this // scope whenever where clauses might affect the result. + // Avoid using the master cache during coherence and just rely + // on the local cache. This effectively disables caching + // during coherence. It is really just a simplification to + // avoid us having to fear that coherence results "pollute" + // the master cache. Since coherence executes pretty quickly, + // it's not worth going to more trouble to increase the + // hit-rate I don't think. + if self.intercrate { + return &self.param_env.selection_cache; + } + // If the trait refers to any parameters in scope, then use // the cache of the param-environment. if diff --git a/src/librustc/middle/typeck/check/method.rs b/src/librustc/middle/typeck/check/method.rs index abffa857a089a..f854bc52acd7b 100644 --- a/src/librustc/middle/typeck/check/method.rs +++ b/src/librustc/middle/typeck/check/method.rs @@ -235,7 +235,7 @@ pub fn lookup_in_trait_adjusted<'a, 'tcx>( let mut selcx = traits::SelectionContext::new(fcx.infcx(), &fcx.inh.param_env, fcx); - if !selcx.evaluate_obligation_intracrate(&obligation) { + if !selcx.evaluate_obligation(&obligation) { debug!("--> Cannot match obligation"); return None; // Cannot be matched, no such method resolution is possible. } diff --git a/src/librustc/middle/typeck/check/mod.rs b/src/librustc/middle/typeck/check/mod.rs index 0e3a77ba9639c..bcb875a6aa830 100644 --- a/src/librustc/middle/typeck/check/mod.rs +++ b/src/librustc/middle/typeck/check/mod.rs @@ -2147,11 +2147,11 @@ fn try_overloaded_call<'a>(fcx: &FnCtxt, _ => {} } - // Try `FnOnce`, then `FnMut`, then `Fn`. + // Try the options that are least restrictive on the caller first. for &(maybe_function_trait, method_name) in [ - (fcx.tcx().lang_items.fn_once_trait(), token::intern("call_once")), + (fcx.tcx().lang_items.fn_trait(), token::intern("call")), (fcx.tcx().lang_items.fn_mut_trait(), token::intern("call_mut")), - (fcx.tcx().lang_items.fn_trait(), token::intern("call")) + (fcx.tcx().lang_items.fn_once_trait(), token::intern("call_once")), ].iter() { let function_trait = match maybe_function_trait { None => continue, @@ -3493,6 +3493,11 @@ fn check_expr_with_unifier(fcx: &FnCtxt, ast::FnOnceUnboxedClosureKind => ty::FnOnceUnboxedClosureKind, }; + debug!("unboxed_closure for {} --> sig={} kind={}", + local_def(expr.id).repr(fcx.tcx()), + fn_ty.sig.repr(fcx.tcx()), + kind); + let unboxed_closure = ty::UnboxedClosure { closure_type: fn_ty, kind: kind, diff --git a/src/test/compile-fail/unboxed-closures-fnmut-as-fn.rs b/src/test/compile-fail/unboxed-closures-fnmut-as-fn.rs new file mode 100644 index 0000000000000..20d7262432f0c --- /dev/null +++ b/src/test/compile-fail/unboxed-closures-fnmut-as-fn.rs @@ -0,0 +1,34 @@ +// Copyright 2014 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Checks that the Fn trait hierarchy rules do not permit +// Fn to be used where FnMut is implemented. + +#![feature(unboxed_closure_sugar)] +#![feature(overloaded_calls)] + +use std::ops::{Fn,FnMut,FnOnce}; + +struct S; + +impl FnMut<(int,),int> for S { + extern "rust-call" fn call_mut(&mut self, (x,): (int,)) -> int { + x * x + } +} + +fn call_itint>(f: &F, x: int) -> int { + f.call((x,)) +} + +fn main() { + let x = call_it(&S, 22); //~ ERROR not implemented +} + diff --git a/src/test/compile-fail/unboxed-closures-static-call-wrong-trait.rs b/src/test/compile-fail/unboxed-closures-static-call-wrong-trait.rs index 871889f26dfd2..4fa72b383067d 100644 --- a/src/test/compile-fail/unboxed-closures-static-call-wrong-trait.rs +++ b/src/test/compile-fail/unboxed-closures-static-call-wrong-trait.rs @@ -12,6 +12,6 @@ fn main() { let mut_ = |&mut: x| x; - mut_.call_once((0i, )); //~ ERROR type `closure` does not implement + mut_.call((0i, )); //~ ERROR type `closure` does not implement } diff --git a/src/test/compile-fail/unboxed-closures-vtable-mismatch.rs b/src/test/compile-fail/unboxed-closures-vtable-mismatch.rs index a96bde7cca4cd..5a22490b6d616 100644 --- a/src/test/compile-fail/unboxed-closures-vtable-mismatch.rs +++ b/src/test/compile-fail/unboxed-closures-vtable-mismatch.rs @@ -18,7 +18,7 @@ fn call_it>(y: int, mut f: F) -> int { pub fn main() { let f = |&mut: x: uint, y: int| -> int { (x as int) + y }; - let z = call_it(3, f); //~ ERROR type mismatch + let z = call_it(3, f); //~ ERROR not implemented println!("{}", z); } diff --git a/src/test/compile-fail/unboxed-closures-wrong-trait.rs b/src/test/compile-fail/unboxed-closures-wrong-trait.rs index 97ad64a77baf4..e15fe8ad049b0 100644 --- a/src/test/compile-fail/unboxed-closures-wrong-trait.rs +++ b/src/test/compile-fail/unboxed-closures-wrong-trait.rs @@ -10,13 +10,13 @@ #![feature(lang_items, overloaded_calls, unboxed_closures)] -fn c int>(f: F) -> int { +fn c int>(f: F) -> int { f(5, 6) } fn main() { let z: int = 7; - assert_eq!(c(|&: x: int, y| x + y + z), 10); + assert_eq!(c(|&mut: x: int, y| x + y + z), 10); //~^ ERROR not implemented } diff --git a/src/test/run-pass/issue-16668.rs b/src/test/run-pass/issue-16668.rs index 92f8030a0dc56..f36594cb40145 100644 --- a/src/test/run-pass/issue-16668.rs +++ b/src/test/run-pass/issue-16668.rs @@ -20,8 +20,8 @@ impl<'a, I, O: 'a> Parser<'a, I, O> { fn compose(mut self, mut rhs: Parser<'a, O, K>) -> Parser<'a, I, K> { Parser { parse: box move |&mut: x: I| { - match self.parse.call_mut((x,)) { - Ok(r) => rhs.parse.call_mut((r,)), + match (*self.parse).call_mut((x,)) { + Ok(r) => (*rhs.parse).call_mut((r,)), Err(e) => Err(e) } } diff --git a/src/test/run-pass/unboxed-closures-extern-fn.rs b/src/test/run-pass/unboxed-closures-extern-fn.rs new file mode 100644 index 0000000000000..82d51ba1f1645 --- /dev/null +++ b/src/test/run-pass/unboxed-closures-extern-fn.rs @@ -0,0 +1,40 @@ +// Copyright 2014 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Checks that extern fn points implement the full range of Fn traits. + +#![feature(unboxed_closure_sugar)] +#![feature(overloaded_calls)] + +use std::ops::{Fn,FnMut,FnOnce}; + +fn square(x: int) -> int { x * x } + +fn call_itint>(f: &F, x: int) -> int { + f.call((x,)) +} + +fn call_it_mutint>(f: &mut F, x: int) -> int { + f.call_mut((x,)) +} + +fn call_it_onceint>(f: F, x: int) -> int { + f.call_once((x,)) +} + +fn main() { + let x = call_it(&square, 22); + let y = call_it_mut(&mut square, 22); + let z = call_it_once(square, 22); + assert_eq!(x, square(22)); + assert_eq!(y, square(22)); + assert_eq!(z, square(22)); +} + diff --git a/src/test/run-pass/unboxed-closures-fn-as-fnmut-and-fnonce.rs b/src/test/run-pass/unboxed-closures-fn-as-fnmut-and-fnonce.rs new file mode 100644 index 0000000000000..90272636bc59d --- /dev/null +++ b/src/test/run-pass/unboxed-closures-fn-as-fnmut-and-fnonce.rs @@ -0,0 +1,46 @@ +// Copyright 2014 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Checks that the Fn trait hierarchy rules permit +// any Fn trait to be used where Fn is implemented. + +#![feature(unboxed_closure_sugar)] +#![feature(overloaded_calls)] + +use std::ops::{Fn,FnMut,FnOnce}; + +struct S; + +impl Fn<(int,),int> for S { + extern "rust-call" fn call(&self, (x,): (int,)) -> int { + x * x + } +} + +fn call_itint>(f: &F, x: int) -> int { + f.call((x,)) +} + +fn call_it_mutint>(f: &mut F, x: int) -> int { + f.call_mut((x,)) +} + +fn call_it_onceint>(f: F, x: int) -> int { + f.call_once((x,)) +} + +fn main() { + let x = call_it(&S, 22); + let y = call_it_mut(&mut S, 22); + let z = call_it_once(S, 22); + assert_eq!(x, y); + assert_eq!(y, z); +} + diff --git a/src/test/run-pass/unboxed-closures-fnmut-as-fnonce.rs b/src/test/run-pass/unboxed-closures-fnmut-as-fnonce.rs new file mode 100644 index 0000000000000..bd01910a210ab --- /dev/null +++ b/src/test/run-pass/unboxed-closures-fnmut-as-fnonce.rs @@ -0,0 +1,40 @@ +// Copyright 2014 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Checks that the Fn trait hierarchy rules permit +// FnMut or FnOnce to be used where FnMut is implemented. + +#![feature(unboxed_closure_sugar)] +#![feature(overloaded_calls)] + +use std::ops::{FnMut,FnOnce}; + +struct S; + +impl FnMut<(int,),int> for S { + extern "rust-call" fn call_mut(&mut self, (x,): (int,)) -> int { + x * x + } +} + +fn call_it_mutint>(f: &mut F, x: int) -> int { + f.call_mut((x,)) +} + +fn call_it_onceint>(f: F, x: int) -> int { + f.call_once((x,)) +} + +fn main() { + let y = call_it_mut(&mut S, 22); + let z = call_it_once(S, 22); + assert_eq!(y, z); +} + diff --git a/src/test/run-pass/fn-trait-sugar.rs b/src/test/run-pass/unboxed-closures-manual-impl.rs similarity index 100% rename from src/test/run-pass/fn-trait-sugar.rs rename to src/test/run-pass/unboxed-closures-manual-impl.rs diff --git a/src/test/run-pass/unboxed-closures-zero-args.rs b/src/test/run-pass/unboxed-closures-zero-args.rs index 6d6d81fd0ef12..f2eddd84af832 100644 --- a/src/test/run-pass/unboxed-closures-zero-args.rs +++ b/src/test/run-pass/unboxed-closures-zero-args.rs @@ -12,6 +12,6 @@ fn main() { let mut zero = |&mut:| {}; - zero.call_mut(()); + let () = zero.call_mut(()); }