Skip to content

Commit

Permalink
Rollup merge of rust-lang#135044 - compiler-errors:better-infer-sugge…
Browse files Browse the repository at this point in the history
…stions-in-const, r=oli-obk

Improve infer (`_`) suggestions in `const`s and `static`s

Fixes rust-lang#135010.

This PR does a few things to (imo) greatly improve the error message when users write something like `static FOO: [i32; _] = [1, 2, 3]`.

Firstly, it adapts the recovery code for when we encounter `_` in a const/static to work a bit more like `fn foo() -> _`, and removes the somewhat redundant query `diagnostic_only_typeck`.

Secondly, it changes the lowering for `[T; _]` to always lower under the `feature(generic_arg_infer)` logic to `ConstArgKind::Infer`. We still issue the feature error, so it's not doing anything *observable* on the good path, but it does mean that we no longer erroneously interpret `[T; _]`'s array length as a `_` **wildcard expression** (à la destructuring assignment, like `(_, y) = expr`).

Lastly it makes the suggestions verbose and fixes (well, suppresses) a bug with stashing and suggestions.

r? oli-obk
  • Loading branch information
matthiaskrgr authored Jan 3, 2025
2 parents 48b4d40 + 0fd64ef commit 58a5b51
Show file tree
Hide file tree
Showing 33 changed files with 388 additions and 316 deletions.
9 changes: 3 additions & 6 deletions compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2031,20 +2031,17 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
fn lower_array_length_to_const_arg(&mut self, c: &AnonConst) -> &'hir hir::ConstArg<'hir> {
match c.value.kind {
ExprKind::Underscore => {
if self.tcx.features().generic_arg_infer() {
let ct_kind = hir::ConstArgKind::Infer(self.lower_span(c.value.span));
self.arena
.alloc(hir::ConstArg { hir_id: self.lower_node_id(c.id), kind: ct_kind })
} else {
if !self.tcx.features().generic_arg_infer() {
feature_err(
&self.tcx.sess,
sym::generic_arg_infer,
c.value.span,
fluent_generated::ast_lowering_underscore_array_length_unstable,
)
.stash(c.value.span, StashKey::UnderscoreForArrayLengths);
self.lower_anon_const_to_const_arg(c)
}
let ct_kind = hir::ConstArgKind::Infer(self.lower_span(c.value.span));
self.arena.alloc(hir::ConstArg { hir_id: self.lower_node_id(c.id), kind: ct_kind })
}
_ => self.lower_anon_const_to_const_arg(c),
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3387,7 +3387,7 @@ impl<'hir> FnRetTy<'hir> {
}
}

pub fn get_infer_ret_ty(&self) -> Option<&'hir Ty<'hir>> {
pub fn is_suggestable_infer_ty(&self) -> Option<&'hir Ty<'hir>> {
if let Self::Return(ty) = self {
if ty.is_suggestable_infer_ty() {
return Some(*ty);
Expand Down
64 changes: 46 additions & 18 deletions compiler/rustc_hir_analysis/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,25 @@ pub struct ItemCtxt<'tcx> {
///////////////////////////////////////////////////////////////////////////

#[derive(Default)]
pub(crate) struct HirPlaceholderCollector(pub(crate) Vec<Span>);
pub(crate) struct HirPlaceholderCollector {
pub spans: Vec<Span>,
// If any of the spans points to a const infer var, then suppress any messages
// that may try to turn that const infer into a type parameter.
pub may_contain_const_infer: bool,
}

impl<'v> Visitor<'v> for HirPlaceholderCollector {
fn visit_ty(&mut self, t: &'v hir::Ty<'v>) {
if let hir::TyKind::Infer = t.kind {
self.0.push(t.span);
self.spans.push(t.span);
}
intravisit::walk_ty(self, t)
}
fn visit_generic_arg(&mut self, generic_arg: &'v hir::GenericArg<'v>) {
match generic_arg {
hir::GenericArg::Infer(inf) => {
self.0.push(inf.span);
self.spans.push(inf.span);
self.may_contain_const_infer = true;
intravisit::walk_inf(self, inf);
}
hir::GenericArg::Type(t) => self.visit_ty(t),
Expand All @@ -152,7 +158,8 @@ impl<'v> Visitor<'v> for HirPlaceholderCollector {
}
fn visit_const_arg(&mut self, const_arg: &'v hir::ConstArg<'v>) {
if let hir::ConstArgKind::Infer(span) = const_arg.kind {
self.0.push(span);
self.may_contain_const_infer = true;
self.spans.push(span);
}
intravisit::walk_const_arg(self, const_arg)
}
Expand Down Expand Up @@ -277,8 +284,8 @@ fn reject_placeholder_type_signatures_in_item<'tcx>(
placeholder_type_error(
icx.lowerer(),
Some(generics),
visitor.0,
suggest,
visitor.spans,
suggest && !visitor.may_contain_const_infer,
None,
item.kind.descr(),
);
Expand Down Expand Up @@ -607,16 +614,16 @@ impl<'tcx> HirTyLowerer<'tcx> for ItemCtxt<'tcx> {
hir::FnRetTy::DefaultReturn(..) => tcx.types.unit,
};

if !(visitor.0.is_empty() && infer_replacements.is_empty()) {
if !(visitor.spans.is_empty() && infer_replacements.is_empty()) {
// We check for the presence of
// `ident_span` to not emit an error twice when we have `fn foo(_: fn() -> _)`.

let mut diag = crate::collect::placeholder_type_error_diag(
self,
generics,
visitor.0,
visitor.spans,
infer_replacements.iter().map(|(s, _)| *s).collect(),
true,
!visitor.may_contain_const_infer,
hir_ty,
"function",
);
Expand Down Expand Up @@ -712,7 +719,7 @@ fn lower_item(tcx: TyCtxt<'_>, item_id: hir::ItemId) {
placeholder_type_error(
icx.lowerer(),
None,
visitor.0,
visitor.spans,
false,
None,
"static variable",
Expand Down Expand Up @@ -780,7 +787,7 @@ fn lower_item(tcx: TyCtxt<'_>, item_id: hir::ItemId) {
placeholder_type_error(
icx.lowerer(),
None,
visitor.0,
visitor.spans,
false,
None,
it.kind.descr(),
Expand Down Expand Up @@ -822,7 +829,7 @@ fn lower_trait_item(tcx: TyCtxt<'_>, trait_item_id: hir::TraitItemId) {
placeholder_type_error(
icx.lowerer(),
None,
visitor.0,
visitor.spans,
false,
None,
"associated constant",
Expand All @@ -837,7 +844,14 @@ fn lower_trait_item(tcx: TyCtxt<'_>, trait_item_id: hir::TraitItemId) {
// Account for `type T = _;`.
let mut visitor = HirPlaceholderCollector::default();
visitor.visit_trait_item(trait_item);
placeholder_type_error(icx.lowerer(), None, visitor.0, false, None, "associated type");
placeholder_type_error(
icx.lowerer(),
None,
visitor.spans,
false,
None,
"associated type",
);
}

hir::TraitItemKind::Type(_, None) => {
Expand All @@ -848,7 +862,14 @@ fn lower_trait_item(tcx: TyCtxt<'_>, trait_item_id: hir::TraitItemId) {
let mut visitor = HirPlaceholderCollector::default();
visitor.visit_trait_item(trait_item);

placeholder_type_error(icx.lowerer(), None, visitor.0, false, None, "associated type");
placeholder_type_error(
icx.lowerer(),
None,
visitor.spans,
false,
None,
"associated type",
);
}
};

Expand All @@ -872,7 +893,14 @@ fn lower_impl_item(tcx: TyCtxt<'_>, impl_item_id: hir::ImplItemId) {
let mut visitor = HirPlaceholderCollector::default();
visitor.visit_impl_item(impl_item);

placeholder_type_error(icx.lowerer(), None, visitor.0, false, None, "associated type");
placeholder_type_error(
icx.lowerer(),
None,
visitor.spans,
false,
None,
"associated type",
);
}
hir::ImplItemKind::Const(ty, _) => {
// Account for `const T: _ = ..;`
Expand All @@ -882,7 +910,7 @@ fn lower_impl_item(tcx: TyCtxt<'_>, impl_item_id: hir::ImplItemId) {
placeholder_type_error(
icx.lowerer(),
None,
visitor.0,
visitor.spans,
false,
None,
"associated constant",
Expand Down Expand Up @@ -1371,7 +1399,7 @@ fn lower_fn_sig_recovering_infer_ret_ty<'tcx>(
generics: &'tcx hir::Generics<'tcx>,
def_id: LocalDefId,
) -> ty::PolyFnSig<'tcx> {
if let Some(infer_ret_ty) = sig.decl.output.get_infer_ret_ty() {
if let Some(infer_ret_ty) = sig.decl.output.is_suggestable_infer_ty() {
return recover_infer_ret_ty(icx, infer_ret_ty, generics, def_id);
}

Expand Down Expand Up @@ -1422,7 +1450,7 @@ fn recover_infer_ret_ty<'tcx>(
let mut visitor = HirPlaceholderCollector::default();
visitor.visit_ty(infer_ret_ty);

let mut diag = bad_placeholder(icx.lowerer(), visitor.0, "return type");
let mut diag = bad_placeholder(icx.lowerer(), visitor.spans, "return type");
let ret_ty = fn_sig.output();

// Don't leak types into signatures unless they're nameable!
Expand Down
50 changes: 42 additions & 8 deletions compiler/rustc_hir_analysis/src/collect/type_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use rustc_errors::{Applicability, StashKey, Suggestions};
use rustc_hir as hir;
use rustc_hir::HirId;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::Visitor;
use rustc_middle::query::plumbing::CyclePlaceholder;
use rustc_middle::ty::fold::fold_regions;
use rustc_middle::ty::print::with_forced_trimmed_paths;
Expand All @@ -12,7 +13,7 @@ use rustc_middle::ty::{self, Article, IsSuggestable, Ty, TyCtxt, TypeVisitableEx
use rustc_middle::{bug, span_bug};
use rustc_span::{DUMMY_SP, Ident, Span};

use super::{ItemCtxt, bad_placeholder};
use super::{HirPlaceholderCollector, ItemCtxt, bad_placeholder};
use crate::errors::TypeofReservedKeywordUsed;
use crate::hir_ty_lowering::HirTyLowerer;

Expand Down Expand Up @@ -412,7 +413,7 @@ fn infer_placeholder_type<'tcx>(
kind: &'static str,
) -> Ty<'tcx> {
let tcx = cx.tcx();
let ty = tcx.diagnostic_only_typeck(def_id).node_type(body_id.hir_id);
let ty = tcx.typeck(def_id).node_type(body_id.hir_id);

// If this came from a free `const` or `static mut?` item,
// then the user may have written e.g. `const A = 42;`.
Expand Down Expand Up @@ -447,13 +448,37 @@ fn infer_placeholder_type<'tcx>(
}
})
.unwrap_or_else(|| {
let mut diag = bad_placeholder(cx, vec![span], kind);

if !ty.references_error() {
let mut visitor = HirPlaceholderCollector::default();
let node = tcx.hir_node_by_def_id(def_id);
if let Some(ty) = node.ty() {
visitor.visit_ty(ty);
}
// If we have just one span, let's try to steal a const `_` feature error.
let try_steal_span = if !tcx.features().generic_arg_infer() && visitor.spans.len() == 1
{
visitor.spans.first().copied()
} else {
None
};
// If we didn't find any infer tys, then just fallback to `span`.
if visitor.spans.is_empty() {
visitor.spans.push(span);
}
let mut diag = bad_placeholder(cx, visitor.spans, kind);

// HACK(#69396): Stashing and stealing diagnostics does not interact
// well with macros which may delay more than one diagnostic on the
// same span. If this happens, we will fall through to this arm, so
// we need to suppress the suggestion since it's invalid. Ideally we
// would suppress the duplicated error too, but that's really hard.
if span.is_empty() && span.from_expansion() {
// An approximately better primary message + no suggestion...
diag.primary_message("missing type for item");
} else if !ty.references_error() {
if let Some(ty) = ty.make_suggestable(tcx, false, None) {
diag.span_suggestion(
diag.span_suggestion_verbose(
span,
"replace with the correct type",
"replace this with a fully-specified type",
ty,
Applicability::MachineApplicable,
);
Expand All @@ -464,7 +489,16 @@ fn infer_placeholder_type<'tcx>(
));
}
}
diag.emit()

if let Some(try_steal_span) = try_steal_span {
cx.dcx().try_steal_replace_and_emit_err(
try_steal_span,
StashKey::UnderscoreForArrayLengths,
diag,
)
} else {
diag.emit()
}
});
Ty::new_error(tcx, guar)
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ impl TaitConstraintLocator<'_> {
"foreign items cannot constrain opaque types",
);
if let Some(hir_sig) = hir_node.fn_sig()
&& hir_sig.decl.output.get_infer_ret_ty().is_some()
&& hir_sig.decl.output.is_suggestable_infer_ty().is_some()
{
let guar = self.tcx.dcx().span_delayed_bug(
hir_sig.decl.output.span(),
Expand Down
7 changes: 1 addition & 6 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1771,12 +1771,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let parent_node = self.tcx.hir().parent_iter(expr.hir_id).find(|(_, node)| {
!matches!(node, hir::Node::Expr(hir::Expr { kind: hir::ExprKind::AddrOf(..), .. }))
});
let Some((
_,
hir::Node::LetStmt(hir::LetStmt { ty: Some(ty), .. })
| hir::Node::Item(hir::Item { kind: hir::ItemKind::Const(ty, _, _), .. }),
)) = parent_node
else {
let Some((_, hir::Node::LetStmt(hir::LetStmt { ty: Some(ty), .. }))) = parent_node else {
return;
};
if let hir::TyKind::Array(_, ct) = ty.peel_refs().kind {
Expand Down
45 changes: 22 additions & 23 deletions compiler/rustc_hir_typeck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,21 +87,7 @@ fn used_trait_imports(tcx: TyCtxt<'_>, def_id: LocalDefId) -> &UnordSet<LocalDef
}

fn typeck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &'tcx ty::TypeckResults<'tcx> {
let fallback = move || tcx.type_of(def_id.to_def_id()).instantiate_identity();
typeck_with_fallback(tcx, def_id, fallback, None)
}

/// Used only to get `TypeckResults` for type inference during error recovery.
/// Currently only used for type inference of `static`s and `const`s to avoid type cycle errors.
fn diagnostic_only_typeck<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: LocalDefId,
) -> &'tcx ty::TypeckResults<'tcx> {
let fallback = move || {
let span = tcx.hir().span(tcx.local_def_id_to_hir_id(def_id));
Ty::new_error_with_message(tcx, span, "diagnostic only typeck table used")
};
typeck_with_fallback(tcx, def_id, fallback, None)
typeck_with_fallback(tcx, def_id, None)
}

/// Same as `typeck` but `inspect` is invoked on evaluation of each root obligation.
Expand All @@ -113,15 +99,13 @@ pub fn inspect_typeck<'tcx>(
def_id: LocalDefId,
inspect: ObligationInspector<'tcx>,
) -> &'tcx ty::TypeckResults<'tcx> {
let fallback = move || tcx.type_of(def_id.to_def_id()).instantiate_identity();
typeck_with_fallback(tcx, def_id, fallback, Some(inspect))
typeck_with_fallback(tcx, def_id, Some(inspect))
}

#[instrument(level = "debug", skip(tcx, fallback, inspector), ret)]
#[instrument(level = "debug", skip(tcx, inspector), ret)]
fn typeck_with_fallback<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: LocalDefId,
fallback: impl Fn() -> Ty<'tcx> + 'tcx,
inspector: Option<ObligationInspector<'tcx>>,
) -> &'tcx ty::TypeckResults<'tcx> {
// Closures' typeck results come from their outermost function,
Expand Down Expand Up @@ -150,7 +134,11 @@ fn typeck_with_fallback<'tcx>(
let mut fcx = FnCtxt::new(&root_ctxt, param_env, def_id);

if let Some(hir::FnSig { header, decl, .. }) = node.fn_sig() {
let fn_sig = if decl.output.get_infer_ret_ty().is_some() {
let fn_sig = if decl.output.is_suggestable_infer_ty().is_some() {
// In the case that we're recovering `fn() -> W<_>` or some other return
// type that has an infer in it, lower the type directly so that it'll
// be correctly filled with infer. We'll use this inference to provide
// a suggestion later on.
fcx.lowerer().lower_fn_ty(id, header.safety, header.abi, decl, None, None)
} else {
tcx.fn_sig(def_id).instantiate_identity()
Expand All @@ -164,8 +152,19 @@ fn typeck_with_fallback<'tcx>(

check_fn(&mut fcx, fn_sig, None, decl, def_id, body, tcx.features().unsized_fn_params());
} else {
let expected_type = infer_type_if_missing(&fcx, node);
let expected_type = expected_type.unwrap_or_else(fallback);
let expected_type = if let Some(infer_ty) = infer_type_if_missing(&fcx, node) {
infer_ty
} else if let Some(ty) = node.ty()
&& ty.is_suggestable_infer_ty()
{
// In the case that we're recovering `const X: [T; _]` or some other
// type that has an infer in it, lower the type directly so that it'll
// be correctly filled with infer. We'll use this inference to provide
// a suggestion later on.
fcx.lowerer().lower_ty(ty)
} else {
tcx.type_of(def_id).instantiate_identity()
};

let expected_type = fcx.normalize(body.value.span, expected_type);

Expand Down Expand Up @@ -506,5 +505,5 @@ fn fatally_break_rust(tcx: TyCtxt<'_>, span: Span) -> ! {

pub fn provide(providers: &mut Providers) {
method::provide(providers);
*providers = Providers { typeck, diagnostic_only_typeck, used_trait_imports, ..*providers };
*providers = Providers { typeck, used_trait_imports, ..*providers };
}
Loading

0 comments on commit 58a5b51

Please sign in to comment.