Report errors for type parameters that are not constrained, either by
variance or an associated type.
This commit is contained in:
parent
2594d56e32
commit
91eedfe18b
6 changed files with 180 additions and 70 deletions
|
@ -2955,6 +2955,13 @@ impl<'tcx> TyS<'tcx> {
|
||||||
assert_eq!(r, Some(self));
|
assert_eq!(r, Some(self));
|
||||||
walker
|
walker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_opt_param_ty(&self) -> Option<ty::ParamTy> {
|
||||||
|
match self.sty {
|
||||||
|
ty::ty_param(ref d) => Some(d.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn walk_ty<'tcx, F>(ty_root: Ty<'tcx>, mut f: F)
|
pub fn walk_ty<'tcx, F>(ty_root: Ty<'tcx>, mut f: F)
|
||||||
|
|
|
@ -10,21 +10,22 @@
|
||||||
|
|
||||||
use astconv::AstConv;
|
use astconv::AstConv;
|
||||||
use check::{FnCtxt, Inherited, blank_fn_ctxt, vtable, regionck};
|
use check::{FnCtxt, Inherited, blank_fn_ctxt, vtable, regionck};
|
||||||
|
use constrained_type_params::identify_constrained_type_params;
|
||||||
use CrateCtxt;
|
use CrateCtxt;
|
||||||
use middle::region;
|
use middle::region;
|
||||||
use middle::subst;
|
use middle::subst::{self, TypeSpace, FnSpace, ParamSpace, SelfSpace};
|
||||||
use middle::traits;
|
use middle::traits;
|
||||||
use middle::ty::{self, Ty};
|
use middle::ty::{self, Ty};
|
||||||
use middle::ty::liberate_late_bound_regions;
|
use middle::ty::liberate_late_bound_regions;
|
||||||
use middle::ty_fold::{TypeFolder, TypeFoldable, super_fold_ty};
|
use middle::ty_fold::{TypeFolder, TypeFoldable, super_fold_ty};
|
||||||
use util::ppaux::Repr;
|
use util::ppaux::{Repr, UserString};
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use syntax::ast;
|
use syntax::ast;
|
||||||
use syntax::ast_util::{local_def};
|
use syntax::ast_util::{local_def};
|
||||||
use syntax::attr;
|
use syntax::attr;
|
||||||
use syntax::codemap::Span;
|
use syntax::codemap::Span;
|
||||||
use syntax::parse::token;
|
use syntax::parse::token::{self, special_idents};
|
||||||
use syntax::visit;
|
use syntax::visit;
|
||||||
use syntax::visit::Visitor;
|
use syntax::visit::Visitor;
|
||||||
|
|
||||||
|
@ -38,6 +39,10 @@ impl<'ccx, 'tcx> CheckTypeWellFormedVisitor<'ccx, 'tcx> {
|
||||||
CheckTypeWellFormedVisitor { ccx: ccx, cache: HashSet::new() }
|
CheckTypeWellFormedVisitor { ccx: ccx, cache: HashSet::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tcx(&self) -> &ty::ctxt<'tcx> {
|
||||||
|
self.ccx.tcx
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks that the field types (in a struct def'n) or argument types (in an enum def'n) are
|
/// Checks that the field types (in a struct def'n) or argument types (in an enum def'n) are
|
||||||
/// well-formed, meaning that they do not require any constraints not declared in the struct
|
/// well-formed, meaning that they do not require any constraints not declared in the struct
|
||||||
/// definition itself. For example, this definition would be illegal:
|
/// definition itself. For example, this definition would be illegal:
|
||||||
|
@ -96,23 +101,29 @@ impl<'ccx, 'tcx> CheckTypeWellFormedVisitor<'ccx, 'tcx> {
|
||||||
ast::ItemConst(..) => {
|
ast::ItemConst(..) => {
|
||||||
self.check_item_type(item);
|
self.check_item_type(item);
|
||||||
}
|
}
|
||||||
ast::ItemStruct(ref struct_def, _) => {
|
ast::ItemStruct(ref struct_def, ref ast_generics) => {
|
||||||
self.check_type_defn(item, |fcx| {
|
self.check_type_defn(item, |fcx| {
|
||||||
vec![struct_variant(fcx, &**struct_def)]
|
vec![struct_variant(fcx, &**struct_def)]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.check_variances_for_type_defn(item, ast_generics);
|
||||||
}
|
}
|
||||||
ast::ItemEnum(ref enum_def, _) => {
|
ast::ItemEnum(ref enum_def, ref ast_generics) => {
|
||||||
self.check_type_defn(item, |fcx| {
|
self.check_type_defn(item, |fcx| {
|
||||||
enum_variants(fcx, enum_def)
|
enum_variants(fcx, enum_def)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.check_variances_for_type_defn(item, ast_generics);
|
||||||
}
|
}
|
||||||
ast::ItemTrait(..) => {
|
ast::ItemTrait(_, ref ast_generics, _, _) => {
|
||||||
let trait_predicates =
|
let trait_predicates =
|
||||||
ty::lookup_predicates(ccx.tcx, local_def(item.id));
|
ty::lookup_predicates(ccx.tcx, local_def(item.id));
|
||||||
reject_non_type_param_bounds(
|
reject_non_type_param_bounds(
|
||||||
ccx.tcx,
|
ccx.tcx,
|
||||||
item.span,
|
item.span,
|
||||||
&trait_predicates);
|
&trait_predicates);
|
||||||
|
self.check_variances(item, ast_generics, &trait_predicates,
|
||||||
|
self.tcx().lang_items.phantom_fn());
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -280,6 +291,123 @@ impl<'ccx, 'tcx> CheckTypeWellFormedVisitor<'ccx, 'tcx> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_variances_for_type_defn(&self,
|
||||||
|
item: &ast::Item,
|
||||||
|
ast_generics: &ast::Generics)
|
||||||
|
{
|
||||||
|
let item_def_id = local_def(item.id);
|
||||||
|
let predicates = ty::lookup_predicates(self.tcx(), item_def_id);
|
||||||
|
self.check_variances(item,
|
||||||
|
ast_generics,
|
||||||
|
&predicates,
|
||||||
|
self.tcx().lang_items.phantom_data());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_variances(&self,
|
||||||
|
item: &ast::Item,
|
||||||
|
ast_generics: &ast::Generics,
|
||||||
|
ty_predicates: &ty::GenericPredicates<'tcx>,
|
||||||
|
suggested_marker_id: Option<ast::DefId>)
|
||||||
|
{
|
||||||
|
let variance_lang_items = &[
|
||||||
|
self.tcx().lang_items.phantom_fn(),
|
||||||
|
self.tcx().lang_items.phantom_data(),
|
||||||
|
];
|
||||||
|
|
||||||
|
let item_def_id = local_def(item.id);
|
||||||
|
let is_lang_item = variance_lang_items.iter().any(|n| *n == Some(item_def_id));
|
||||||
|
if is_lang_item {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let variances = ty::item_variances(self.tcx(), item_def_id);
|
||||||
|
|
||||||
|
let mut constrained_parameters: HashSet<_> =
|
||||||
|
variances.types
|
||||||
|
.iter_enumerated()
|
||||||
|
.filter(|&(_, _, &variance)| variance != ty::Bivariant)
|
||||||
|
.map(|(space, index, _)| self.param_ty(ast_generics, space, index))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
identify_constrained_type_params(self.tcx(),
|
||||||
|
ty_predicates.predicates.as_slice(),
|
||||||
|
None,
|
||||||
|
&mut constrained_parameters);
|
||||||
|
|
||||||
|
for (space, index, _) in variances.types.iter_enumerated() {
|
||||||
|
let param_ty = self.param_ty(ast_generics, space, index);
|
||||||
|
if constrained_parameters.contains(¶m_ty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let span = self.ty_param_span(ast_generics, item, space, index);
|
||||||
|
self.report_bivariance(span, param_ty.name, suggested_marker_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (space, index, &variance) in variances.regions.iter_enumerated() {
|
||||||
|
if variance != ty::Bivariant {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(space, TypeSpace);
|
||||||
|
let span = ast_generics.lifetimes[index].lifetime.span;
|
||||||
|
let name = ast_generics.lifetimes[index].lifetime.name;
|
||||||
|
self.report_bivariance(span, name, suggested_marker_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_ty(&self,
|
||||||
|
ast_generics: &ast::Generics,
|
||||||
|
space: ParamSpace,
|
||||||
|
index: usize)
|
||||||
|
-> ty::ParamTy
|
||||||
|
{
|
||||||
|
let name = match space {
|
||||||
|
TypeSpace => ast_generics.ty_params[index].ident.name,
|
||||||
|
SelfSpace => special_idents::type_self.name,
|
||||||
|
FnSpace => self.tcx().sess.bug("Fn space occupied?"),
|
||||||
|
};
|
||||||
|
|
||||||
|
ty::ParamTy { space: space, idx: index as u32, name: name }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ty_param_span(&self,
|
||||||
|
ast_generics: &ast::Generics,
|
||||||
|
item: &ast::Item,
|
||||||
|
space: ParamSpace,
|
||||||
|
index: usize)
|
||||||
|
-> Span
|
||||||
|
{
|
||||||
|
match space {
|
||||||
|
TypeSpace => ast_generics.ty_params[index].span,
|
||||||
|
SelfSpace => item.span,
|
||||||
|
FnSpace => self.tcx().sess.span_bug(item.span, "Fn space occupied?"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn report_bivariance(&self,
|
||||||
|
span: Span,
|
||||||
|
param_name: ast::Name,
|
||||||
|
suggested_marker_id: Option<ast::DefId>)
|
||||||
|
{
|
||||||
|
self.tcx().sess.span_err(
|
||||||
|
span,
|
||||||
|
&format!("parameter `{}` is never used",
|
||||||
|
param_name.user_string(self.tcx()))[]);
|
||||||
|
|
||||||
|
match suggested_marker_id {
|
||||||
|
Some(def_id) => {
|
||||||
|
self.tcx().sess.span_help(
|
||||||
|
span,
|
||||||
|
format!("consider removing `{}` or using a marker such as `{}`",
|
||||||
|
param_name.user_string(self.tcx()),
|
||||||
|
ty::item_path_str(self.tcx(), def_id)).as_slice());
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// no lang items, no help!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reject any predicates that do not involve a type parameter.
|
// Reject any predicates that do not involve a type parameter.
|
||||||
|
@ -347,9 +475,9 @@ impl<'ccx, 'tcx, 'v> Visitor<'v> for CheckTypeWellFormedVisitor<'ccx, 'tcx> {
|
||||||
match fk {
|
match fk {
|
||||||
visit::FkFnBlock | visit::FkItemFn(..) => {}
|
visit::FkFnBlock | visit::FkItemFn(..) => {}
|
||||||
visit::FkMethod(..) => {
|
visit::FkMethod(..) => {
|
||||||
match ty::impl_or_trait_item(self.ccx.tcx, local_def(id)) {
|
match ty::impl_or_trait_item(self.tcx(), local_def(id)) {
|
||||||
ty::ImplOrTraitItem::MethodTraitItem(ty_method) => {
|
ty::ImplOrTraitItem::MethodTraitItem(ty_method) => {
|
||||||
reject_shadowing_type_parameters(self.ccx.tcx, span, &ty_method.generics)
|
reject_shadowing_type_parameters(self.tcx(), span, &ty_method.generics)
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -363,14 +491,14 @@ impl<'ccx, 'tcx, 'v> Visitor<'v> for CheckTypeWellFormedVisitor<'ccx, 'tcx> {
|
||||||
&ast::TraitItem::ProvidedMethod(_) |
|
&ast::TraitItem::ProvidedMethod(_) |
|
||||||
&ast::TraitItem::TypeTraitItem(_) => {},
|
&ast::TraitItem::TypeTraitItem(_) => {},
|
||||||
&ast::TraitItem::RequiredMethod(ref method) => {
|
&ast::TraitItem::RequiredMethod(ref method) => {
|
||||||
match ty::impl_or_trait_item(self.ccx.tcx, local_def(method.id)) {
|
match ty::impl_or_trait_item(self.tcx(), local_def(method.id)) {
|
||||||
ty::ImplOrTraitItem::MethodTraitItem(ty_method) => {
|
ty::ImplOrTraitItem::MethodTraitItem(ty_method) => {
|
||||||
reject_non_type_param_bounds(
|
reject_non_type_param_bounds(
|
||||||
self.ccx.tcx,
|
self.tcx(),
|
||||||
method.span,
|
method.span,
|
||||||
&ty_method.predicates);
|
&ty_method.predicates);
|
||||||
reject_shadowing_type_parameters(
|
reject_shadowing_type_parameters(
|
||||||
self.ccx.tcx,
|
self.tcx(),
|
||||||
method.span,
|
method.span,
|
||||||
&ty_method.generics);
|
&ty_method.generics);
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,7 @@ There are some shortcomings in this design:
|
||||||
|
|
||||||
use astconv::{self, AstConv, ty_of_arg, ast_ty_to_ty, ast_region_to_region};
|
use astconv::{self, AstConv, ty_of_arg, ast_ty_to_ty, ast_region_to_region};
|
||||||
use middle::def;
|
use middle::def;
|
||||||
|
use constrained_type_params::identify_constrained_type_params;
|
||||||
use middle::lang_items::SizedTraitLangItem;
|
use middle::lang_items::SizedTraitLangItem;
|
||||||
use middle::region;
|
use middle::region;
|
||||||
use middle::resolve_lifetime;
|
use middle::resolve_lifetime;
|
||||||
|
@ -1960,51 +1961,15 @@ fn enforce_impl_ty_params_are_constrained<'tcx>(tcx: &ty::ctxt<'tcx>,
|
||||||
let mut input_parameters: HashSet<_> =
|
let mut input_parameters: HashSet<_> =
|
||||||
impl_trait_ref.iter()
|
impl_trait_ref.iter()
|
||||||
.flat_map(|t| t.input_types().iter()) // Types in trait ref, if any
|
.flat_map(|t| t.input_types().iter()) // Types in trait ref, if any
|
||||||
.chain(Some(impl_scheme.ty).iter()) // Self type, always
|
.chain(Some(impl_scheme.ty).iter()) // Self type, always
|
||||||
.flat_map(|t| t.walk())
|
.flat_map(|t| t.walk())
|
||||||
.filter_map(to_opt_param_ty)
|
.filter_map(|t| t.as_opt_param_ty())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
loop {
|
identify_constrained_type_params(tcx,
|
||||||
let num_inputs = input_parameters.len();
|
impl_predicates.predicates.as_slice(),
|
||||||
|
impl_trait_ref,
|
||||||
let projection_predicates =
|
&mut input_parameters);
|
||||||
impl_predicates.predicates
|
|
||||||
.iter()
|
|
||||||
.filter_map(|predicate| {
|
|
||||||
match *predicate {
|
|
||||||
// Ignore higher-ranked binders. For the purposes
|
|
||||||
// of this check, they don't matter because they
|
|
||||||
// only affect named regions, and we're just
|
|
||||||
// concerned about type parameters here.
|
|
||||||
ty::Predicate::Projection(ref data) => Some(data.0.clone()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for projection in projection_predicates {
|
|
||||||
// Special case: watch out for some kind of sneaky attempt
|
|
||||||
// to project out an associated type defined by this very trait.
|
|
||||||
if Some(projection.projection_ty.trait_ref.clone()) == impl_trait_ref {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let relies_only_on_inputs =
|
|
||||||
projection.projection_ty.trait_ref.input_types().iter()
|
|
||||||
.flat_map(|t| t.walk())
|
|
||||||
.filter_map(to_opt_param_ty)
|
|
||||||
.all(|t| input_parameters.contains(&t));
|
|
||||||
|
|
||||||
if relies_only_on_inputs {
|
|
||||||
input_parameters.extend(
|
|
||||||
projection.ty.walk().filter_map(to_opt_param_ty));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if input_parameters.len() == num_inputs {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (index, ty_param) in ast_generics.ty_params.iter().enumerate() {
|
for (index, ty_param) in ast_generics.ty_params.iter().enumerate() {
|
||||||
let param_ty = ty::ParamTy { space: TypeSpace,
|
let param_ty = ty::ParamTy { space: TypeSpace,
|
||||||
|
@ -2025,11 +1990,4 @@ fn enforce_impl_ty_params_are_constrained<'tcx>(tcx: &ty::ctxt<'tcx>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_opt_param_ty<'tcx>(ty: Ty<'tcx>) -> Option<ty::ParamTy> {
|
|
||||||
match ty.sty {
|
|
||||||
ty::ty_param(ref d) => Some(d.clone()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,16 +23,16 @@ pub fn identify_constrained_type_params<'tcx>(_tcx: &ty::ctxt<'tcx>,
|
||||||
|
|
||||||
let projection_predicates =
|
let projection_predicates =
|
||||||
predicates.iter()
|
predicates.iter()
|
||||||
.filter_map(|predicate| {
|
.filter_map(|predicate| {
|
||||||
match *predicate {
|
match *predicate {
|
||||||
// Ignore higher-ranked binders. For the purposes
|
// Ignore higher-ranked binders. For the purposes
|
||||||
// of this check, they don't matter because they
|
// of this check, they don't matter because they
|
||||||
// only affect named regions, and we're just
|
// only affect named regions, and we're just
|
||||||
// concerned about type parameters here.
|
// concerned about type parameters here.
|
||||||
ty::Predicate::Projection(ref data) => Some(data.0.clone()),
|
ty::Predicate::Projection(ref data) => Some(data.0.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for projection in projection_predicates {
|
for projection in projection_predicates {
|
||||||
// Special case: watch out for some kind of sneaky attempt
|
// Special case: watch out for some kind of sneaky attempt
|
||||||
|
|
|
@ -123,6 +123,7 @@ mod check;
|
||||||
mod rscope;
|
mod rscope;
|
||||||
mod astconv;
|
mod astconv;
|
||||||
mod collect;
|
mod collect;
|
||||||
|
mod constrained_type_params;
|
||||||
mod coherence;
|
mod coherence;
|
||||||
mod variance;
|
mod variance;
|
||||||
|
|
||||||
|
|
16
src/test/compile-fail/issue-17904-2.rs
Normal file
16
src/test/compile-fail/issue-17904-2.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// 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 <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.
|
||||||
|
|
||||||
|
// Test that we can parse a unit struct with a where clause, even if
|
||||||
|
// it leads to a error later on since `T` is unused.
|
||||||
|
|
||||||
|
struct Foo<T> where T: Copy; //~ ERROR parameter `T` is never used
|
||||||
|
|
||||||
|
fn main() {}
|
Loading…
Add table
Add a link
Reference in a new issue