diff --git a/src/librustc/ty/subst.rs b/src/librustc/ty/subst.rs index 26e8b72946b5..f786547e6e36 100644 --- a/src/librustc/ty/subst.rs +++ b/src/librustc/ty/subst.rs @@ -12,13 +12,14 @@ use hir::def_id::DefId; use infer::canonical::Canonical; -use ty::{self, Lift, List, Ty, TyCtxt}; +use ty::{self, CanonicalVar, Lift, List, Ty, TyCtxt}; use ty::fold::{TypeFoldable, TypeFolder, TypeVisitor}; use serialize::{self, Encodable, Encoder, Decodable, Decoder}; use syntax_pos::{Span, DUMMY_SP}; use rustc_data_structures::accumulate_vec::AccumulateVec; use rustc_data_structures::array_vec::ArrayVec; +use rustc_data_structures::indexed_vec::Idx; use core::intrinsics; use std::cmp::Ordering; @@ -180,8 +181,6 @@ impl<'tcx> Decodable for Kind<'tcx> { /// A substitution mapping generic parameters to new values. pub type Substs<'tcx> = List>; -pub type CanonicalSubsts<'gcx> = Canonical<'gcx, &'gcx Substs<'gcx>>; - impl<'a, 'gcx, 'tcx> Substs<'tcx> { /// Creates a Substs that maps each generic parameter to itself. pub fn identity_for_item(tcx: TyCtxt<'a, 'gcx, 'tcx>, def_id: DefId) @@ -342,6 +341,33 @@ impl<'tcx> TypeFoldable<'tcx> for &'tcx Substs<'tcx> { } } +pub type CanonicalSubsts<'gcx> = Canonical<'gcx, &'gcx Substs<'gcx>>; + +impl<'gcx> CanonicalSubsts<'gcx> { + /// True if this represents a substitution like + /// + /// ```ignore + /// [?0, ?1, ?2] + /// ``` + /// + /// i.e., each thing is mapped to a canonical variable with the same index. + pub fn is_identity(&self) -> bool { + self.value.iter().zip(CanonicalVar::new(0)..).all(|(kind, cvar)| { + match kind.unpack() { + UnpackedKind::Type(ty) => match ty.sty { + ty::Infer(ty::CanonicalTy(cvar1)) => cvar == cvar1, + _ => false, + }, + + UnpackedKind::Lifetime(r) => match r { + ty::ReCanonical(cvar1) => cvar == *cvar1, + _ => false, + }, + } + }) + } +} + impl<'tcx> serialize::UseSpecializedDecodable for &'tcx Substs<'tcx> {} /////////////////////////////////////////////////////////////////////////// diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index db4dda0da5b4..16894478df05 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -95,7 +95,7 @@ use rustc::infer::anon_types::AnonTypeDecl; use rustc::infer::type_variable::{TypeVariableOrigin}; use rustc::middle::region; use rustc::mir::interpret::{GlobalId}; -use rustc::ty::subst::{UnpackedKind, Subst, Substs}; +use rustc::ty::subst::{CanonicalSubsts, UnpackedKind, Subst, Substs}; use rustc::traits::{self, ObligationCause, ObligationCauseCode, TraitEngine}; use rustc::ty::{self, Ty, TyCtxt, GenericParamDefKind, Visibility, ToPredicate, RegionKind}; use rustc::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability}; @@ -122,6 +122,7 @@ use std::ops::{self, Deref}; use rustc_target::spec::abi::Abi; use syntax::ast; use syntax::attr; +use syntax::source_map::DUMMY_SP; use syntax::source_map::original_sp; use syntax::feature_gate::{GateIssue, emit_feature_err}; use syntax::ptr::P; @@ -2058,11 +2059,47 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { pub fn write_method_call(&self, hir_id: hir::HirId, method: MethodCallee<'tcx>) { + debug!("write_method_call(hir_id={:?}, method={:?})", hir_id, method); self.tables .borrow_mut() .type_dependent_defs_mut() .insert(hir_id, Def::Method(method.def_id)); + self.write_substs(hir_id, method.substs); + + // When the method is confirmed, the `method.substs` includes + // parameters from not just the method, but also the impl of + // the method -- in particular, the `Self` type will be fully + // resolved. However, those are not something that the "user + // specified" -- i.e., those types come from the inferred type + // of the receiver, not something the user wrote. So when we + // create the user-substs, we want to replace those earlier + // types with just the types that the user actually wrote -- + // that is, those that appear on the *method itself*. + // + // As an example, if the user wrote something like + // `foo.bar::(...)` -- the `Self` type here will be the + // type of `foo` (possibly adjusted), but we don't want to + // include that. We want just the `[_, u32]` part. + if !method.substs.is_noop() { + let method_generics = self.tcx.generics_of(method.def_id); + if !method_generics.params.is_empty() { + let user_substs = self.infcx.probe(|_| { + let just_method_substs = Substs::for_item(self.tcx, method.def_id, |param, _| { + let i = param.index as usize; + if i < method_generics.parent_count { + self.infcx.var_for_def(DUMMY_SP, param) + } else { + method.substs[i] + } + }); + self.infcx.canonicalize_response(&just_method_substs) + }); + + debug!("write_method_call: user_substs = {:?}", user_substs); + self.write_user_substs(hir_id, user_substs); + } + } } pub fn write_substs(&self, node_id: hir::HirId, substs: &'tcx Substs<'tcx>) { @@ -2076,6 +2113,21 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { } } + pub fn write_user_substs(&self, node_id: hir::HirId, substs: CanonicalSubsts<'tcx>) { + debug!( + "write_user_substs({:?}, {:?}) in fcx {}", + node_id, + substs, + self.tag(), + ); + + if !substs.is_identity() { + self.tables.borrow_mut().user_substs_mut().insert(node_id, substs); + } else { + debug!("write_user_substs: skipping identity substs"); + } + } + pub fn apply_adjustments(&self, expr: &hir::Expr, adj: Vec>) { debug!("apply_adjustments(expr={:?}, adj={:?})", expr, adj); @@ -5083,7 +5135,15 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { debug!("instantiate_value_path: type of {:?} is {:?}", node_id, ty_substituted); - self.write_substs(self.tcx.hir.node_to_hir_id(node_id), substs); + let hir_id = self.tcx.hir.node_to_hir_id(node_id); + self.write_substs(hir_id, substs); + + if !substs.is_noop() { + let user_substs = self.infcx.canonicalize_response(&substs); + debug!("instantiate_value_path: user_substs = {:?}", user_substs); + self.write_user_substs(hir_id, user_substs); + } + ty_substituted } diff --git a/src/librustc_typeck/check/writeback.rs b/src/librustc_typeck/check/writeback.rs index 319de51713b9..0d8401c1c86e 100644 --- a/src/librustc_typeck/check/writeback.rs +++ b/src/librustc_typeck/check/writeback.rs @@ -35,7 +35,11 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { let item_id = self.tcx.hir.body_owner(body.id()); let item_def_id = self.tcx.hir.local_def_id(item_id); - let mut wbcx = WritebackCx::new(self, body); + // This attribute causes us to dump some writeback information + // in the form of errors, which is used for unit tests. + let rustc_dump_user_substs = self.tcx.has_attr(item_def_id, "rustc_dump_user_substs"); + + let mut wbcx = WritebackCx::new(self, body, rustc_dump_user_substs); for arg in &body.arguments { wbcx.visit_node_id(arg.pat.span, arg.hir_id); } @@ -84,12 +88,15 @@ struct WritebackCx<'cx, 'gcx: 'cx + 'tcx, 'tcx: 'cx> { tables: ty::TypeckTables<'gcx>, body: &'gcx hir::Body, + + rustc_dump_user_substs: bool, } impl<'cx, 'gcx, 'tcx> WritebackCx<'cx, 'gcx, 'tcx> { fn new( fcx: &'cx FnCtxt<'cx, 'gcx, 'tcx>, body: &'gcx hir::Body, + rustc_dump_user_substs: bool, ) -> WritebackCx<'cx, 'gcx, 'tcx> { let owner = fcx.tcx.hir.definitions().node_to_hir_id(body.id().node_id); @@ -97,6 +104,7 @@ impl<'cx, 'gcx, 'tcx> WritebackCx<'cx, 'gcx, 'tcx> { fcx, tables: ty::TypeckTables::empty(Some(DefId::local(owner.owner))), body, + rustc_dump_user_substs, } } @@ -558,6 +566,22 @@ impl<'cx, 'gcx, 'tcx> WritebackCx<'cx, 'gcx, 'tcx> { assert!(!substs.needs_infer() && !substs.has_skol()); self.tables.node_substs_mut().insert(hir_id, substs); } + + // Copy over any user-substs + if let Some(user_substs) = self.fcx.tables.borrow().user_substs(hir_id) { + let user_substs = self.tcx().lift_to_global(&user_substs).unwrap(); + self.tables.user_substs_mut().insert(hir_id, user_substs); + + // Unit-testing mechanism: + if self.rustc_dump_user_substs { + let node_id = self.tcx().hir.hir_to_node_id(hir_id); + let span = self.tcx().hir.span(node_id); + self.tcx().sess.span_err( + span, + &format!("user substs: {:?}", user_substs), + ); + } + } } fn visit_adjustments(&mut self, span: Span, hir_id: hir::HirId) { diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 4ed96d269061..28d278360345 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -865,6 +865,12 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG is just used for rustc unit tests \ and will never be stable", cfg_fn!(rustc_attrs))), + ("rustc_dump_user_substs", Whitelisted, Gated(Stability::Unstable, + "rustc_attrs", + "the `#[rustc_error]` attribute \ + is just used for rustc unit tests \ + and will never be stable", + cfg_fn!(rustc_attrs))), ("rustc_if_this_changed", Whitelisted, Gated(Stability::Unstable, "rustc_attrs", "the `#[rustc_if_this_changed]` attribute \ diff --git a/src/test/ui/nll/user-annotations/dump-fn-method.rs b/src/test/ui/nll/user-annotations/dump-fn-method.rs new file mode 100644 index 000000000000..fa14009da5a1 --- /dev/null +++ b/src/test/ui/nll/user-annotations/dump-fn-method.rs @@ -0,0 +1,54 @@ +// Copyright 2017 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. + +// Unit test for the "user substitutions" that are annotated on each +// node. + +// compile-flags:-Zverbose + +#![feature(nll)] +#![feature(rustc_attrs)] + +trait Bazoom { + fn method(&self, arg: T, arg2: U) { } +} + +impl Bazoom for T { +} + +fn foo<'a, T>(_: T) { } + +#[rustc_dump_user_substs] +fn main() { + // Here: nothing is given, so we don't have any annotation. + let x = foo; + x(22); + + // Here: `u32` is given. + let x = foo::; //~ ERROR [u32] + x(22); + + // Here: we only want the `T` to be given, the rest should be variables. + let x = <_ as Bazoom>::method::<_>; //~ ERROR [?0, u32, ?1] + x(&22, 44, 66); + + // Here: all are given + let x = >::method::; //~ ERROR [u8, u16, u32] + x(&22, 44, 66); + + // Here: we want in particular that *only* the method `U` + // annotation is given, the rest are variables. + let y = 22_u32; + y.method::(44, 66); //~ ERROR [?0, ?1, u32] + + // Here: nothing is given, so we don't have any annotation. + let y = 22_u32; + y.method(44, 66); +} diff --git a/src/test/ui/nll/user-annotations/dump-fn-method.stderr b/src/test/ui/nll/user-annotations/dump-fn-method.stderr new file mode 100644 index 000000000000..8f3a0498e24e --- /dev/null +++ b/src/test/ui/nll/user-annotations/dump-fn-method.stderr @@ -0,0 +1,26 @@ +error: user substs: Canonical { variables: [], value: [u32] } + --> $DIR/dump-fn-method.rs:35:13 + | +LL | let x = foo::; //~ ERROR [u32] + | ^^^^^^^^^^ + +error: user substs: Canonical { variables: [CanonicalVarInfo { kind: Ty(General) }, CanonicalVarInfo { kind: Ty(General) }], value: [?0, u32, ?1] } + --> $DIR/dump-fn-method.rs:39:13 + | +LL | let x = <_ as Bazoom>::method::<_>; //~ ERROR [?0, u32, ?1] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: user substs: Canonical { variables: [], value: [u8, u16, u32] } + --> $DIR/dump-fn-method.rs:43:13 + | +LL | let x = >::method::; //~ ERROR [u8, u16, u32] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: user substs: Canonical { variables: [CanonicalVarInfo { kind: Ty(General) }, CanonicalVarInfo { kind: Ty(General) }], value: [?0, ?1, u32] } + --> $DIR/dump-fn-method.rs:49:5 + | +LL | y.method::(44, 66); //~ ERROR [?0, ?1, u32] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors +