Skip to content

Commit abf4d8b

Browse files
committed
When picking a candidate, consider the unstable ones last.
If there is potential ambiguity after stabilizing those candidates, a warning will be emitted.
1 parent 1731bf8 commit abf4d8b

12 files changed

+317
-41
lines changed

src/librustc/lint/builtin.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@ declare_lint! {
260260
"floating-point literals cannot be used in patterns"
261261
}
262262

263+
declare_lint! {
264+
pub UNSTABLE_NAME_COLLISION,
265+
Warn,
266+
"detects name collision with an existing but unstable method"
267+
}
268+
263269
/// Does nothing as a lint pass, but registers some `Lint`s
264270
/// which are used by other parts of the compiler.
265271
#[derive(Copy, Clone)]
@@ -307,7 +313,8 @@ impl LintPass for HardwiredLints {
307313
SINGLE_USE_LIFETIME,
308314
TYVAR_BEHIND_RAW_POINTER,
309315
ELIDED_LIFETIME_IN_PATH,
310-
BARE_TRAIT_OBJECT
316+
BARE_TRAIT_OBJECT,
317+
UNSTABLE_NAME_COLLISION,
311318
)
312319
}
313320
}

src/librustc/middle/stability.rs

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,22 @@ struct Checker<'a, 'tcx: 'a> {
474474
tcx: TyCtxt<'a, 'tcx, 'tcx>,
475475
}
476476

477+
/// Result of `TyCtxt::eval_stability`.
478+
pub enum EvalResult {
479+
/// We can use the item because it is stable or we provided the
480+
/// corresponding feature gate.
481+
Allow,
482+
/// We cannot use the item because it is unstable and we did not provide the
483+
/// corresponding feature gate.
484+
Deny {
485+
feature: Symbol,
486+
reason: Option<Symbol>,
487+
issue: u32,
488+
},
489+
/// The item does not have the `#[stable]` or `#[unstable]` marker assigned.
490+
Unmarked,
491+
}
492+
477493
impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
478494
// (See issue #38412)
479495
fn skip_stability_check_due_to_privacy(self, mut def_id: DefId) -> bool {
@@ -509,11 +525,16 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
509525
}
510526
}
511527

512-
pub fn check_stability(self, def_id: DefId, id: NodeId, span: Span) {
528+
/// Evaluates the stability of an item.
529+
///
530+
/// Returns `None` if the item is stable, or unstable but the corresponding `#![feature]` has
531+
/// been provided. Returns the tuple `Some((feature, reason, issue))` of the offending unstable
532+
/// feature otherwise.
533+
pub fn eval_stability(self, def_id: DefId, id: NodeId, span: Span) -> EvalResult {
513534
if span.allows_unstable() {
514535
debug!("stability: \
515536
skipping span={:?} since it is internal", span);
516-
return;
537+
return EvalResult::Allow;
517538
}
518539

519540
let lint_deprecated = |def_id: DefId, note: Option<Symbol>| {
@@ -549,7 +570,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
549570
..def_id
550571
}).is_some();
551572
if !is_staged_api {
552-
return;
573+
return EvalResult::Allow;
553574
}
554575

555576
let stability = self.lookup_stability(def_id);
@@ -566,18 +587,18 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
566587
// Only the cross-crate scenario matters when checking unstable APIs
567588
let cross_crate = !def_id.is_local();
568589
if !cross_crate {
569-
return
590+
return EvalResult::Allow;
570591
}
571592

572593
// Issue 38412: private items lack stability markers.
573594
if self.skip_stability_check_due_to_privacy(def_id) {
574-
return
595+
return EvalResult::Allow;
575596
}
576597

577598
match stability {
578-
Some(&Stability { level: attr::Unstable {ref reason, issue}, ref feature, .. }) => {
579-
if self.stability().active_features.contains(feature) {
580-
return
599+
Some(&Stability { level: attr::Unstable { reason, issue }, feature, .. }) => {
600+
if self.stability().active_features.contains(&feature) {
601+
return EvalResult::Allow;
581602
}
582603

583604
// When we're compiling the compiler itself we may pull in
@@ -589,19 +610,34 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
589610
// the `-Z force-unstable-if-unmarked` flag present (we're
590611
// compiling a compiler crate), then let this missing feature
591612
// annotation slide.
592-
if *feature == "rustc_private" && issue == 27812 {
613+
if feature == "rustc_private" && issue == 27812 {
593614
if self.sess.opts.debugging_opts.force_unstable_if_unmarked {
594-
return
615+
return EvalResult::Allow;
595616
}
596617
}
597618

598-
let msg = match *reason {
599-
Some(ref r) => format!("use of unstable library feature '{}': {}",
600-
feature.as_str(), &r),
619+
EvalResult::Deny { feature, reason, issue }
620+
}
621+
Some(_) => {
622+
// Stable APIs are always ok to call and deprecated APIs are
623+
// handled by the lint emitting logic above.
624+
EvalResult::Allow
625+
}
626+
None => {
627+
EvalResult::Unmarked
628+
}
629+
}
630+
}
631+
632+
pub fn check_stability(self, def_id: DefId, id: NodeId, span: Span) {
633+
match self.eval_stability(def_id, id, span) {
634+
EvalResult::Allow => {}
635+
EvalResult::Deny { feature, reason, issue } => {
636+
let msg = match reason {
637+
Some(r) => format!("use of unstable library feature '{}': {}", feature, r),
601638
None => format!("use of unstable library feature '{}'", &feature)
602639
};
603640

604-
605641
let msp: MultiSpan = span.into();
606642
let cm = &self.sess.parse_sess.codemap();
607643
let span_key = msp.primary_span().and_then(|sp: Span|
@@ -624,12 +660,8 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
624660
GateIssue::Library(Some(issue)), &msg);
625661
}
626662
}
627-
Some(_) => {
628-
// Stable APIs are always ok to call and deprecated APIs are
629-
// handled by the lint emitting logic above.
630-
}
631-
None => {
632-
span_bug!(span, "encountered unmarked API");
663+
EvalResult::Unmarked => {
664+
span_bug!(span, "encountered unmarked API: {:?}", def_id);
633665
}
634666
}
635667
}

src/librustc_lint/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,15 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
273273
id: LintId::of(TYVAR_BEHIND_RAW_POINTER),
274274
reference: "issue #46906 <https://github.com/rust-lang/rust/issues/46906>",
275275
edition: Some(Edition::Edition2018),
276-
}
276+
},
277+
FutureIncompatibleInfo {
278+
id: LintId::of(UNSTABLE_NAME_COLLISION),
279+
reference: "pr #48552 <https://github.com/rust-lang/rust/pull/48552>",
280+
edition: None,
281+
// FIXME: create a proper tracking issue.
282+
// Note: this item represents future incompatibility of all unstable functions in the
283+
// standard library, and thus should never be removed or changed to an error.
284+
},
277285
]);
278286

279287
// Register renamed and removed lints

src/librustc_typeck/check/method/probe.rs

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ use rustc::ty::{self, Ty, ToPolyTraitRef, ToPredicate, TraitRef, TypeFoldable};
2323
use rustc::infer::type_variable::TypeVariableOrigin;
2424
use rustc::util::nodemap::FxHashSet;
2525
use rustc::infer::{self, InferOk};
26+
use rustc::middle::stability;
2627
use syntax::ast;
2728
use syntax::util::lev_distance::{lev_distance, find_best_match_for_name};
28-
use syntax_pos::Span;
29+
use syntax_pos::{Span, symbol::Symbol};
2930
use rustc::hir;
3031
use rustc::lint;
3132
use std::mem;
@@ -937,30 +938,59 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
937938
debug!("pick_method(self_ty={})", self.ty_to_string(self_ty));
938939

939940
let mut possibly_unsatisfied_predicates = Vec::new();
940-
941-
debug!("searching inherent candidates");
942-
if let Some(pick) = self.consider_candidates(self_ty,
943-
&self.inherent_candidates,
944-
&mut possibly_unsatisfied_predicates) {
945-
return Some(pick);
941+
let mut unstable_candidates = Vec::new();
942+
943+
for (kind, candidates) in &[
944+
("inherent", &self.inherent_candidates),
945+
("extension", &self.extension_candidates),
946+
] {
947+
debug!("searching {} candidates", kind);
948+
let res = self.consider_candidates(
949+
self_ty,
950+
candidates.iter(),
951+
&mut possibly_unsatisfied_predicates,
952+
Some(&mut unstable_candidates),
953+
);
954+
if let Some(pick) = res {
955+
if !unstable_candidates.is_empty() && !self_ty.is_ty_var() {
956+
if let Ok(p) = &pick {
957+
// Emit a lint if there are unstable candidates alongside the stable ones.
958+
//
959+
// Note, we suppress warning if `self_ty` is TyVar (`_`), since every
960+
// possible candidates of every type will be considered, which leads to
961+
// bogus ambiguity like `str::rsplit` vs `[_]::rsplit`. This condition is
962+
// seen in `src/test/compile-fail/occurs-check-2.rs`.
963+
self.emit_unstable_name_collision_hint(p, &unstable_candidates);
964+
}
965+
}
966+
return Some(pick);
967+
}
946968
}
947969

948-
debug!("searching extension candidates");
949-
let res = self.consider_candidates(self_ty,
950-
&self.extension_candidates,
951-
&mut possibly_unsatisfied_predicates);
952-
if let None = res {
970+
debug!("searching unstable candidates");
971+
let res = self.consider_candidates(
972+
self_ty,
973+
unstable_candidates.into_iter().map(|(c, _)| c),
974+
&mut possibly_unsatisfied_predicates,
975+
None,
976+
);
977+
if res.is_none() {
953978
self.unsatisfied_predicates.extend(possibly_unsatisfied_predicates);
954979
}
955980
res
956981
}
957982

958-
fn consider_candidates(&self,
959-
self_ty: Ty<'tcx>,
960-
probes: &[Candidate<'tcx>],
961-
possibly_unsatisfied_predicates: &mut Vec<TraitRef<'tcx>>)
962-
-> Option<PickResult<'tcx>> {
963-
let mut applicable_candidates: Vec<_> = probes.iter()
983+
fn consider_candidates<'b, ProbesIter>(
984+
&self,
985+
self_ty: Ty<'tcx>,
986+
probes: ProbesIter,
987+
possibly_unsatisfied_predicates: &mut Vec<TraitRef<'tcx>>,
988+
unstable_candidates: Option<&mut Vec<(&'b Candidate<'tcx>, Symbol)>>,
989+
) -> Option<PickResult<'tcx>>
990+
where
991+
ProbesIter: Iterator<Item = &'b Candidate<'tcx>> + Clone,
992+
{
993+
let mut applicable_candidates: Vec<_> = probes.clone()
964994
.map(|probe| {
965995
(probe, self.consider_probe(self_ty, probe, possibly_unsatisfied_predicates))
966996
})
@@ -975,8 +1005,20 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
9751005
}
9761006
}
9771007

1008+
if let Some(uc) = unstable_candidates {
1009+
applicable_candidates.retain(|&(p, _)| {
1010+
if let stability::EvalResult::Deny { feature, .. } =
1011+
self.tcx.eval_stability(p.item.def_id, ast::DUMMY_NODE_ID, self.span)
1012+
{
1013+
uc.push((p, feature));
1014+
return false;
1015+
}
1016+
true
1017+
});
1018+
}
1019+
9781020
if applicable_candidates.len() > 1 {
979-
let sources = probes.iter()
1021+
let sources = probes
9801022
.map(|p| self.candidate_source(p, self_ty))
9811023
.collect();
9821024
return Some(Err(MethodError::Ambiguity(sources)));
@@ -991,6 +1033,39 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
9911033
})
9921034
}
9931035

1036+
fn emit_unstable_name_collision_hint(
1037+
&self,
1038+
stable_pick: &Pick,
1039+
unstable_candidates: &[(&Candidate<'tcx>, Symbol)],
1040+
) {
1041+
let mut diag = self.tcx.struct_span_lint_node(
1042+
lint::builtin::UNSTABLE_NAME_COLLISION,
1043+
self.fcx.body_id,
1044+
self.span,
1045+
"a method with this name will be added to the standard library in the future",
1046+
);
1047+
1048+
// FIXME: This should be a `span_suggestion` instead of `help`. However `self.span` only
1049+
// highlights the method name, so we can't use it. Also consider reusing the code from
1050+
// `report_method_error()`.
1051+
diag.help(&format!(
1052+
"call with fully qualified syntax `{}(...)` to keep using the current method",
1053+
self.tcx.item_path_str(stable_pick.item.def_id),
1054+
));
1055+
1056+
if ::rustc::session::config::nightly_options::is_nightly_build() {
1057+
for (candidate, feature) in unstable_candidates {
1058+
diag.note(&format!(
1059+
"add #![feature({})] to the crate attributes to enable `{}`",
1060+
feature,
1061+
self.tcx.item_path_str(candidate.item.def_id),
1062+
));
1063+
}
1064+
}
1065+
1066+
diag.emit();
1067+
}
1068+
9941069
fn select_trait_candidate(&self, trait_ref: ty::TraitRef<'tcx>)
9951070
-> traits::SelectionResult<'tcx, traits::Selection<'tcx>>
9961071
{
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![feature(staged_api)]
12+
13+
#![stable(feature = "ipu_iterator", since = "1.0.0")]
14+
15+
#[stable(feature = "ipu_iterator", since = "1.0.0")]
16+
pub trait IpuIterator {
17+
#[unstable(feature = "ipu_flatten", issue = "99999")]
18+
fn ipu_flatten(&self) -> u32 {
19+
0
20+
}
21+
}
22+
23+
#[stable(feature = "ipu_iterator", since = "1.0.0")]
24+
impl IpuIterator for char {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
pub trait IpuItertools {
12+
fn ipu_flatten(&self) -> u32 {
13+
1
14+
}
15+
}
16+
17+
impl IpuItertools for char {}

src/test/ui/inference_unstable.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Ensures #[unstable] functions without opting in the corresponding #![feature]
12+
// will not break inference.
13+
14+
// aux-build:inference_unstable_iterator.rs
15+
// aux-build:inference_unstable_itertools.rs
16+
// run-pass
17+
18+
extern crate inference_unstable_iterator;
19+
extern crate inference_unstable_itertools;
20+
21+
#[allow(unused_imports)]
22+
use inference_unstable_iterator::IpuIterator;
23+
use inference_unstable_itertools::IpuItertools;
24+
25+
fn main() {
26+
assert_eq!('x'.ipu_flatten(), 1);
27+
//~^ WARN a method with this name will be added to the standard library in the future
28+
//~^^ WARN it will become a hard error in a future release
29+
}

0 commit comments

Comments
 (0)