Skip to content

Commit

Permalink
Auto merge of #108860 - oli-obk:tait_alias, r=compiler-errors
Browse files Browse the repository at this point in the history
Add `AliasKind::Weak` for type aliases.

`type Foo<T: Debug> = Bar<T>;` does not check `T: Debug` at use sites of `Foo<NotDebug>`, because in contrast to a

```rust
trait Identity {
    type Identity;
}
impl<T: Debug> Identity for T {
    type Identity = T;
}
<NotDebug as Identity>::Identity
```

type aliases do not exist in the type system, but are expanded to their aliased type immediately when going from HIR to the type layer.

Similarly:

* a private type alias for a public type is a completely fine thing, even though it makes it a bit hard to write out complex times sometimes
* rustdoc expands the type alias, even though often times users use them for documentation purposes
* diagnostics show the expanded type, which is confusing if the user wrote a type alias and the diagnostic talks about another type that they don't know about.

For type alias impl trait, these issues do not actually apply in most cases, but sometimes you have a type alias impl trait like `type Foo<T: Debug> = (impl Debug, Bar<T>);`, which only really checks it for `impl Debug`, but by accident prevents `Bar<T>` from only being instantiated after proving `T: Debug`. This PR makes sure that we always check these bounds explicitly and don't rely on an implementation accident.

To not break all the type aliases out there, we only use it when the type alias contains an opaque type. We can decide to do this for all type aliases over an edition.

Or we can later extend this to more types if we figure out the back-compat concerns with suddenly checking such bounds.

As a side effect, easily allows fixing #108617, which I did.

fixes #108617
  • Loading branch information
bors committed Jun 17, 2023
2 parents 670a0ed + 79c2c98 commit 0cc541e
Show file tree
Hide file tree
Showing 87 changed files with 625 additions and 344 deletions.
1 change: 1 addition & 0 deletions compiler/rustc_const_eval/src/util/type_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> {
| ty::Generator(def_id, substs, _) => self.print_def_path(def_id, substs),
ty::Foreign(def_id) => self.print_def_path(def_id, &[]),

ty::Alias(ty::Weak, _) => bug!("type_name: unexpected weak projection"),
ty::Alias(ty::Inherent, _) => bug!("type_name: unexpected inherent projection"),
ty::GeneratorWitness(_) => bug!("type_name: unexpected `GeneratorWitness`"),
ty::GeneratorWitnessMIR(..) => bug!("type_name: unexpected `GeneratorWitnessMIR`"),
Expand Down
124 changes: 124 additions & 0 deletions compiler/rustc_hir_analysis/src/astconv/lint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use rustc_ast::TraitObjectSyntax;
use rustc_errors::{Diagnostic, StashKey};
use rustc_hir as hir;
use rustc_lint_defs::{builtin::BARE_TRAIT_OBJECTS, Applicability};
use rustc_trait_selection::traits::error_reporting::suggestions::NextTypeParamName;

use super::AstConv;

impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
/// Make sure that we are in the condition to suggest the blanket implementation.
pub(super) fn maybe_lint_blanket_trait_impl(
&self,
self_ty: &hir::Ty<'_>,
diag: &mut Diagnostic,
) {
let tcx = self.tcx();
let parent_id = tcx.hir().get_parent_item(self_ty.hir_id).def_id;
if let hir::Node::Item(hir::Item {
kind:
hir::ItemKind::Impl(hir::Impl {
self_ty: impl_self_ty, of_trait: Some(of_trait_ref), generics, ..
}),
..
}) = tcx.hir().get_by_def_id(parent_id) && self_ty.hir_id == impl_self_ty.hir_id
{
if !of_trait_ref.trait_def_id().is_some_and(|def_id| def_id.is_local()) {
return;
}
let of_trait_span = of_trait_ref.path.span;
// make sure that we are not calling unwrap to abort during the compilation
let Ok(impl_trait_name) = tcx.sess.source_map().span_to_snippet(self_ty.span) else { return; };
let Ok(of_trait_name) = tcx.sess.source_map().span_to_snippet(of_trait_span) else { return; };
// check if the trait has generics, to make a correct suggestion
let param_name = generics.params.next_type_param_name(None);

let add_generic_sugg = if let Some(span) = generics.span_for_param_suggestion() {
(span, format!(", {}: {}", param_name, impl_trait_name))
} else {
(generics.span, format!("<{}: {}>", param_name, impl_trait_name))
};
diag.multipart_suggestion(
format!("alternatively use a blanket \
implementation to implement `{of_trait_name}` for \
all types that also implement `{impl_trait_name}`"),
vec![
(self_ty.span, param_name),
add_generic_sugg,
],
Applicability::MaybeIncorrect,
);
}
}

pub(super) fn maybe_lint_bare_trait(&self, self_ty: &hir::Ty<'_>, in_path: bool) {
let tcx = self.tcx();
if let hir::TyKind::TraitObject([poly_trait_ref, ..], _, TraitObjectSyntax::None) =
self_ty.kind
{
let needs_bracket = in_path
&& !tcx
.sess
.source_map()
.span_to_prev_source(self_ty.span)
.ok()
.is_some_and(|s| s.trim_end().ends_with('<'));

let is_global = poly_trait_ref.trait_ref.path.is_global();

let mut sugg = Vec::from_iter([(
self_ty.span.shrink_to_lo(),
format!(
"{}dyn {}",
if needs_bracket { "<" } else { "" },
if is_global { "(" } else { "" },
),
)]);

if is_global || needs_bracket {
sugg.push((
self_ty.span.shrink_to_hi(),
format!(
"{}{}",
if is_global { ")" } else { "" },
if needs_bracket { ">" } else { "" },
),
));
}

if self_ty.span.edition().rust_2021() {
let msg = "trait objects must include the `dyn` keyword";
let label = "add `dyn` keyword before this trait";
let mut diag =
rustc_errors::struct_span_err!(tcx.sess, self_ty.span, E0782, "{}", msg);
if self_ty.span.can_be_used_for_suggestions() {
diag.multipart_suggestion_verbose(
label,
sugg,
Applicability::MachineApplicable,
);
}
// check if the impl trait that we are considering is a impl of a local trait
self.maybe_lint_blanket_trait_impl(&self_ty, &mut diag);
diag.stash(self_ty.span, StashKey::TraitMissingMethod);
} else {
let msg = "trait objects without an explicit `dyn` are deprecated";
tcx.struct_span_lint_hir(
BARE_TRAIT_OBJECTS,
self_ty.hir_id,
self_ty.span,
msg,
|lint| {
lint.multipart_suggestion_verbose(
"use `dyn`",
sugg,
Applicability::MachineApplicable,
);
self.maybe_lint_blanket_trait_impl(&self_ty, lint);
lint
},
);
}
}
}
}
134 changes: 17 additions & 117 deletions compiler/rustc_hir_analysis/src/astconv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

mod errors;
pub mod generics;
mod lint;

use crate::astconv::errors::prohibit_assoc_ty_binding;
use crate::astconv::generics::{check_generic_arg_count, create_substs_for_generic_args};
Expand All @@ -19,7 +20,7 @@ use rustc_ast::TraitObjectSyntax;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::{
struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, FatalError,
MultiSpan, StashKey,
MultiSpan,
};
use rustc_hir as hir;
use rustc_hir::def::{CtorOf, DefKind, Namespace, Res};
Expand All @@ -33,14 +34,12 @@ use rustc_middle::ty::subst::{self, GenericArgKind, InternalSubsts, SubstsRef};
use rustc_middle::ty::GenericParamDefKind;
use rustc_middle::ty::{self, Const, IsSuggestable, Ty, TyCtxt, TypeVisitableExt};
use rustc_middle::ty::{DynKind, ToPredicate};
use rustc_session::lint::builtin::{AMBIGUOUS_ASSOCIATED_ITEMS, BARE_TRAIT_OBJECTS};
use rustc_session::lint::builtin::AMBIGUOUS_ASSOCIATED_ITEMS;
use rustc_span::edit_distance::find_best_match_for_name;
use rustc_span::symbol::{kw, Ident, Symbol};
use rustc_span::{sym, Span, DUMMY_SP};
use rustc_target::spec::abi;
use rustc_trait_selection::traits::error_reporting::{
report_object_safety_error, suggestions::NextTypeParamName,
};
use rustc_trait_selection::traits::error_reporting::report_object_safety_error;
use rustc_trait_selection::traits::wf::object_region_bounds;
use rustc_trait_selection::traits::{
self, astconv_object_safety_violations, NormalizeExt, ObligationCtxt,
Expand Down Expand Up @@ -1458,7 +1457,19 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
item_segment: &hir::PathSegment<'_>,
) -> Ty<'tcx> {
let substs = self.ast_path_substs_for_ty(span, did, item_segment);
self.tcx().at(span).type_of(did).subst(self.tcx(), substs)
let ty = self.tcx().at(span).type_of(did);

if matches!(self.tcx().def_kind(did), DefKind::TyAlias)
&& ty.skip_binder().has_opaque_types()
{
// Type aliases referring to types that contain opaque types (but aren't just directly
// referencing a single opaque type) get encoded as a type alias that normalization will
// then actually instantiate the where bounds of.
let alias_ty = self.tcx().mk_alias_ty(did, substs);
self.tcx().mk_alias(ty::Weak, alias_ty)
} else {
ty.subst(self.tcx(), substs)
}
}

fn conv_object_ty_poly_trait_ref(
Expand Down Expand Up @@ -3703,115 +3714,4 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
}
Some(r)
}

/// Make sure that we are in the condition to suggest the blanket implementation.
fn maybe_lint_blanket_trait_impl(&self, self_ty: &hir::Ty<'_>, diag: &mut Diagnostic) {
let tcx = self.tcx();
let parent_id = tcx.hir().get_parent_item(self_ty.hir_id).def_id;
if let hir::Node::Item(hir::Item {
kind:
hir::ItemKind::Impl(hir::Impl {
self_ty: impl_self_ty, of_trait: Some(of_trait_ref), generics, ..
}),
..
}) = tcx.hir().get_by_def_id(parent_id) && self_ty.hir_id == impl_self_ty.hir_id
{
if !of_trait_ref.trait_def_id().is_some_and(|def_id| def_id.is_local()) {
return;
}
let of_trait_span = of_trait_ref.path.span;
// make sure that we are not calling unwrap to abort during the compilation
let Ok(impl_trait_name) = tcx.sess.source_map().span_to_snippet(self_ty.span) else { return; };
let Ok(of_trait_name) = tcx.sess.source_map().span_to_snippet(of_trait_span) else { return; };
// check if the trait has generics, to make a correct suggestion
let param_name = generics.params.next_type_param_name(None);

let add_generic_sugg = if let Some(span) = generics.span_for_param_suggestion() {
(span, format!(", {}: {}", param_name, impl_trait_name))
} else {
(generics.span, format!("<{}: {}>", param_name, impl_trait_name))
};
diag.multipart_suggestion(
format!("alternatively use a blanket \
implementation to implement `{of_trait_name}` for \
all types that also implement `{impl_trait_name}`"),
vec![
(self_ty.span, param_name),
add_generic_sugg,
],
Applicability::MaybeIncorrect,
);
}
}

fn maybe_lint_bare_trait(&self, self_ty: &hir::Ty<'_>, in_path: bool) {
let tcx = self.tcx();
if let hir::TyKind::TraitObject([poly_trait_ref, ..], _, TraitObjectSyntax::None) =
self_ty.kind
{
let needs_bracket = in_path
&& !tcx
.sess
.source_map()
.span_to_prev_source(self_ty.span)
.ok()
.is_some_and(|s| s.trim_end().ends_with('<'));

let is_global = poly_trait_ref.trait_ref.path.is_global();

let mut sugg = Vec::from_iter([(
self_ty.span.shrink_to_lo(),
format!(
"{}dyn {}",
if needs_bracket { "<" } else { "" },
if is_global { "(" } else { "" },
),
)]);

if is_global || needs_bracket {
sugg.push((
self_ty.span.shrink_to_hi(),
format!(
"{}{}",
if is_global { ")" } else { "" },
if needs_bracket { ">" } else { "" },
),
));
}

if self_ty.span.edition().rust_2021() {
let msg = "trait objects must include the `dyn` keyword";
let label = "add `dyn` keyword before this trait";
let mut diag =
rustc_errors::struct_span_err!(tcx.sess, self_ty.span, E0782, "{}", msg);
if self_ty.span.can_be_used_for_suggestions() {
diag.multipart_suggestion_verbose(
label,
sugg,
Applicability::MachineApplicable,
);
}
// check if the impl trait that we are considering is a impl of a local trait
self.maybe_lint_blanket_trait_impl(&self_ty, &mut diag);
diag.stash(self_ty.span, StashKey::TraitMissingMethod);
} else {
let msg = "trait objects without an explicit `dyn` are deprecated";
tcx.struct_span_lint_hir(
BARE_TRAIT_OBJECTS,
self_ty.hir_id,
self_ty.span,
msg,
|lint| {
lint.multipart_suggestion_verbose(
"use `dyn`",
sugg,
Applicability::MachineApplicable,
);
self.maybe_lint_blanket_trait_impl(&self_ty, lint);
lint
},
);
}
}
}
}
40 changes: 30 additions & 10 deletions compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,10 @@ fn check_item<'tcx>(tcx: TyCtxt<'tcx>, item: &'tcx hir::Item<'tcx>) {
check_item_fn(tcx, def_id, item.ident, item.span, sig.decl);
}
hir::ItemKind::Static(ty, ..) => {
check_item_type(tcx, def_id, ty.span, false);
check_item_type(tcx, def_id, ty.span, UnsizedHandling::Forbid);
}
hir::ItemKind::Const(ty, ..) => {
check_item_type(tcx, def_id, ty.span, false);
check_item_type(tcx, def_id, ty.span, UnsizedHandling::Forbid);
}
hir::ItemKind::Struct(_, ast_generics) => {
check_type_defn(tcx, item, false);
Expand All @@ -242,6 +242,12 @@ fn check_item<'tcx>(tcx: TyCtxt<'tcx>, item: &'tcx hir::Item<'tcx>) {
}
// `ForeignItem`s are handled separately.
hir::ItemKind::ForeignMod { .. } => {}
hir::ItemKind::TyAlias(hir_ty, ..) => {
if tcx.type_of(item.owner_id.def_id).skip_binder().has_opaque_types() {
// Bounds are respected for `type X = impl Trait` and `type X = (impl Trait, Y);`
check_item_type(tcx, def_id, hir_ty.span, UnsizedHandling::Allow);
}
}
_ => {}
}
}
Expand All @@ -258,7 +264,9 @@ fn check_foreign_item(tcx: TyCtxt<'_>, item: &hir::ForeignItem<'_>) {
hir::ForeignItemKind::Fn(decl, ..) => {
check_item_fn(tcx, def_id, item.ident, item.span, decl)
}
hir::ForeignItemKind::Static(ty, ..) => check_item_type(tcx, def_id, ty.span, true),
hir::ForeignItemKind::Static(ty, ..) => {
check_item_type(tcx, def_id, ty.span, UnsizedHandling::AllowIfForeignTail)
}
hir::ForeignItemKind::Type => (),
}
}
Expand Down Expand Up @@ -1100,20 +1108,32 @@ fn check_item_fn(
})
}

fn check_item_type(tcx: TyCtxt<'_>, item_id: LocalDefId, ty_span: Span, allow_foreign_ty: bool) {
enum UnsizedHandling {
Forbid,
Allow,
AllowIfForeignTail,
}

fn check_item_type(
tcx: TyCtxt<'_>,
item_id: LocalDefId,
ty_span: Span,
unsized_handling: UnsizedHandling,
) {
debug!("check_item_type: {:?}", item_id);

enter_wf_checking_ctxt(tcx, ty_span, item_id, |wfcx| {
let ty = tcx.type_of(item_id).subst_identity();
let item_ty = wfcx.normalize(ty_span, Some(WellFormedLoc::Ty(item_id)), ty);

let mut forbid_unsized = true;
if allow_foreign_ty {
let tail = tcx.struct_tail_erasing_lifetimes(item_ty, wfcx.param_env);
if let ty::Foreign(_) = tail.kind() {
forbid_unsized = false;
let forbid_unsized = match unsized_handling {
UnsizedHandling::Forbid => true,
UnsizedHandling::Allow => false,
UnsizedHandling::AllowIfForeignTail => {
let tail = tcx.struct_tail_erasing_lifetimes(item_ty, wfcx.param_env);
!matches!(tail.kind(), ty::Foreign(_))
}
}
};

wfcx.register_wf_obligation(ty_span, Some(WellFormedLoc::Ty(item_id)), item_ty.into());
if forbid_unsized {
Expand Down
Loading

0 comments on commit 0cc541e

Please sign in to comment.