diff --git a/compiler/rustc_error_codes/src/error_codes/E0807.md b/compiler/rustc_error_codes/src/error_codes/E0807.md new file mode 100644 index 0000000000000..b48cf873d90f6 --- /dev/null +++ b/compiler/rustc_error_codes/src/error_codes/E0807.md @@ -0,0 +1,51 @@ +The `?` operator was used to return from a `const` or `static` initializer. + +Erroneous code example: + +```compile_fail,E0807 +const fn foo() -> Result<(), ()> { Ok(()) } + +fn main() -> Result<(), ()> { + // error: the `?` operator cannot be used to return from a `const` initializer + const A: () = foo()?; + Ok(A) +} +``` + +To fix this error, either use the `?` operator outside of +the `const` initializer: + +``` +const fn foo() -> Result<(), ()> { Ok(()) } + +fn main() -> Result<(), ()> { + // store the `Result` in the const and apply `?` later + const A: Result<(), ()> = foo(); + Ok(A?) +} +``` + +or handle each arm explicitly without returning: + +``` +const fn foo() -> Result<(), ()> { Ok(()) } + +fn main() -> Result<(), ()> { + const A: () = match foo() { + Ok(x) => x, + Err(_) => panic!() + }; + Ok(A) +} +``` + +or use `let` instead of `const`: + +``` +const fn foo() -> Result<(), ()> { Ok(()) } + +fn main() -> Result<(), ()> { + let a = foo()?; + Ok(a) +} +``` diff --git a/compiler/rustc_error_codes/src/lib.rs b/compiler/rustc_error_codes/src/lib.rs index eff9b9d323c25..d57c0cfabb80d 100644 --- a/compiler/rustc_error_codes/src/lib.rs +++ b/compiler/rustc_error_codes/src/lib.rs @@ -545,6 +545,7 @@ macro_rules! error_codes { 0804, 0805, 0806, +0807, ); ) } diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index 3f8ac61eed084..f3ee17ca1d0fe 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -100,6 +100,14 @@ impl IntoDiagArg for ReturnLikeStatementKind { } } +#[derive(Diagnostic)] +#[diag("the `?` operator cannot be used to return from a `{$keyword}` initializer", code = E0807)] +pub(crate) struct QuestionMarkInConst { + #[primary_span] + pub span: Span, + pub keyword: &'static str, +} + #[derive(Diagnostic)] #[diag("functions with the \"rust-call\" ABI must take a single non-self tuple argument")] pub(crate) struct RustCallIncorrectArgs { diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index fd9c1bc8780ee..3b083534a41b9 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -20,7 +20,7 @@ use rustc_hir as hir; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::lang_items::LangItem; -use rustc_hir::{ExprKind, HirId, QPath, find_attr, is_range_literal}; +use rustc_hir::{ConstContext, ExprKind, HirId, QPath, find_attr, is_range_literal}; use rustc_hir_analysis::NoVariantNamed; use rustc_hir_analysis::errors::NoFieldOnType; use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer as _; @@ -44,8 +44,8 @@ use crate::errors::{ AddressOfTemporaryTaken, BaseExpressionDoubleDot, BaseExpressionDoubleDotAddExpr, BaseExpressionDoubleDotRemove, CantDereference, FieldMultiplySpecifiedInInitializer, FunctionalRecordUpdateOnNonStruct, HelpUseLatestEdition, NakedAsmOutsideNakedFn, - NoFieldOnVariant, ReturnLikeStatementKind, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive, - TypeMismatchFruTypo, YieldExprOutsideOfCoroutine, + NoFieldOnVariant, QuestionMarkInConst, ReturnLikeStatementKind, ReturnStmtOutsideOfFnBody, + StructExprNonExhaustive, TypeMismatchFruTypo, YieldExprOutsideOfCoroutine, }; use crate::op::contains_let_in_chain; use crate::{ @@ -862,12 +862,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expr: &'tcx hir::Expr<'tcx>, ) -> Ty<'tcx> { if self.ret_coercion.is_none() { - self.emit_return_outside_of_fn_body(expr, ReturnLikeStatementKind::Return); + let expectation = if let Some(desugar_kind) = expr.span.desugaring_kind() + && desugar_kind == DesugaringKind::QuestionMark + && let Some(ccx) = self.tcx.hir_body_const_context(self.body_id) + && matches!(ccx, ConstContext::Const { .. } | ConstContext::Static(_)) + { + let guaranteed = self + .tcx + .dcx() + .emit_err(QuestionMarkInConst { span: expr.span, keyword: ccx.keyword_name() }); + // Suppresses incorrect and unnecessary "E0283: type annotations needed" + ExpectHasType(Ty::new_error(self.tcx, guaranteed)) + } else { + self.emit_return_outside_of_fn_body(expr, ReturnLikeStatementKind::Return); + NoExpectation + }; if let Some(e) = expr_opt { // We still have to type-check `e` (issue #86188), but calling // `check_return_expr` only works inside fn bodies. - self.check_expr(e); + self.check_expr_with_expectation(e, expectation); } } else if let Some(e) = expr_opt { if self.ret_coercion_span.get().is_none() { @@ -986,7 +1000,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// /// `expr` is the `return` (`become`) "statement", `kind` is the kind of the statement /// either `Return` or `Become`. - fn emit_return_outside_of_fn_body(&self, expr: &hir::Expr<'_>, kind: ReturnLikeStatementKind) { + fn emit_return_outside_of_fn_body( + &self, + expr: &hir::Expr<'_>, + kind: ReturnLikeStatementKind, + ) -> ErrorGuaranteed { let mut err = ReturnStmtOutsideOfFnBody { span: expr.span, encl_body_span: None, @@ -1027,7 +1045,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.encl_fn_span = Some(*encl_fn_span); } - self.dcx().emit_err(err); + self.dcx().emit_err(err) } fn point_at_return_for_opaque_ty_error( diff --git a/tests/ui/consts/question-mark-returning-from-initializer.rs b/tests/ui/consts/question-mark-returning-from-initializer.rs new file mode 100644 index 0000000000000..74e50f38ae50e --- /dev/null +++ b/tests/ui/consts/question-mark-returning-from-initializer.rs @@ -0,0 +1,13 @@ +//! Regression test for + +//! Fixes misleading "return statement outside of function body" message emitted +//! when `?` was used in a `const`/`static` initializer, even if that initializer +//! was within a function body + +const fn foo() -> Result<(), ()> { Ok(()) } + +fn main() -> Result<(), ()> { + const A: () = foo()?; //~ ERROR the `?` operator cannot be used to return from a `const` initializer + static B: () = foo()?; //~ ERROR the `?` operator cannot be used to return from a `static` initializer + Ok(()) +} diff --git a/tests/ui/consts/question-mark-returning-from-initializer.stderr b/tests/ui/consts/question-mark-returning-from-initializer.stderr new file mode 100644 index 0000000000000..05bc5f0ee54f9 --- /dev/null +++ b/tests/ui/consts/question-mark-returning-from-initializer.stderr @@ -0,0 +1,15 @@ +error[E0807]: the `?` operator cannot be used to return from a `const` initializer + --> $DIR/question-mark-returning-from-initializer.rs:10:24 + | +LL | const A: () = foo()?; + | ^ + +error[E0807]: the `?` operator cannot be used to return from a `static` initializer + --> $DIR/question-mark-returning-from-initializer.rs:11:25 + | +LL | static B: () = foo()?; + | ^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0807`.