Rollup merge of #114819 - estebank:issue-78124, r=compiler-errors
Point at return type when it influences non-first `match` arm When encountering code like ```rust fn foo() -> i32 { match 0 { 1 => return 0, 2 => "", _ => 1, } } ``` Point at the return type and not at the prior arm, as that arm has type `!` which isn't influencing the arm corresponding to arm `2`. Fix #78124.
This commit is contained in:
commit
8db5a6d8ee
38 changed files with 157 additions and 73 deletions
|
@ -1648,7 +1648,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
||||||
hir::ExprKind::Match(
|
hir::ExprKind::Match(
|
||||||
scrutinee,
|
scrutinee,
|
||||||
arena_vec![self; break_arm, continue_arm],
|
arena_vec![self; break_arm, continue_arm],
|
||||||
hir::MatchSource::TryDesugar,
|
hir::MatchSource::TryDesugar(scrutinee.hir_id),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2148,7 +2148,7 @@ pub enum MatchSource {
|
||||||
/// A desugared `for _ in _ { .. }` loop.
|
/// A desugared `for _ in _ { .. }` loop.
|
||||||
ForLoopDesugar,
|
ForLoopDesugar,
|
||||||
/// A desugared `?` operator.
|
/// A desugared `?` operator.
|
||||||
TryDesugar,
|
TryDesugar(HirId),
|
||||||
/// A desugared `<expr>.await`.
|
/// A desugared `<expr>.await`.
|
||||||
AwaitDesugar,
|
AwaitDesugar,
|
||||||
/// A desugared `format_args!()`.
|
/// A desugared `format_args!()`.
|
||||||
|
@ -2162,7 +2162,7 @@ impl MatchSource {
|
||||||
match self {
|
match self {
|
||||||
Normal => "match",
|
Normal => "match",
|
||||||
ForLoopDesugar => "for",
|
ForLoopDesugar => "for",
|
||||||
TryDesugar => "?",
|
TryDesugar(_) => "?",
|
||||||
AwaitDesugar => ".await",
|
AwaitDesugar => ".await",
|
||||||
FormatArgs => "format_args!()",
|
FormatArgs => "format_args!()",
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
let (span, code) = match prior_arm {
|
let (span, code) = match prior_arm {
|
||||||
// The reason for the first arm to fail is not that the match arms diverge,
|
// The reason for the first arm to fail is not that the match arms diverge,
|
||||||
// but rather that there's a prior obligation that doesn't hold.
|
// but rather that there's a prior obligation that doesn't hold.
|
||||||
None => (arm_span, ObligationCauseCode::BlockTailExpression(arm.body.hir_id)),
|
None => {
|
||||||
|
(arm_span, ObligationCauseCode::BlockTailExpression(arm.body.hir_id, match_src))
|
||||||
|
}
|
||||||
Some((prior_arm_block_id, prior_arm_ty, prior_arm_span)) => (
|
Some((prior_arm_block_id, prior_arm_ty, prior_arm_span)) => (
|
||||||
expr.span,
|
expr.span,
|
||||||
ObligationCauseCode::MatchExpressionArm(Box::new(MatchExpressionArmCause {
|
ObligationCauseCode::MatchExpressionArm(Box::new(MatchExpressionArmCause {
|
||||||
|
@ -120,7 +122,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
scrut_span: scrut.span,
|
scrut_span: scrut.span,
|
||||||
source: match_src,
|
source: match_src,
|
||||||
prior_arms: other_arms.clone(),
|
prior_arms: other_arms.clone(),
|
||||||
scrut_hir_id: scrut.hir_id,
|
|
||||||
opt_suggest_box_span,
|
opt_suggest_box_span,
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
|
@ -145,7 +146,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
other_arms.remove(0);
|
other_arms.remove(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
prior_arm = Some((arm_block_id, arm_ty, arm_span));
|
if !arm_ty.is_never() {
|
||||||
|
// When a match arm has type `!`, then it doesn't influence the expected type for
|
||||||
|
// the following arm. If all of the prior arms are `!`, then the influence comes
|
||||||
|
// from elsewhere and we shouldn't point to any previous arm.
|
||||||
|
prior_arm = Some((arm_block_id, arm_ty, arm_span));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all of the arms in the `match` diverge,
|
// If all of the arms in the `match` diverge,
|
||||||
|
|
|
@ -1603,7 +1603,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
|
||||||
);
|
);
|
||||||
err.span_label(cause.span, "return type is not `()`");
|
err.span_label(cause.span, "return type is not `()`");
|
||||||
}
|
}
|
||||||
ObligationCauseCode::BlockTailExpression(blk_id) => {
|
ObligationCauseCode::BlockTailExpression(blk_id, ..) => {
|
||||||
let parent_id = fcx.tcx.hir().parent_id(blk_id);
|
let parent_id = fcx.tcx.hir().parent_id(blk_id);
|
||||||
err = self.report_return_mismatched_types(
|
err = self.report_return_mismatched_types(
|
||||||
cause,
|
cause,
|
||||||
|
@ -1748,7 +1748,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
|
||||||
) && !in_external_macro(fcx.tcx.sess, cond_expr.span)
|
) && !in_external_macro(fcx.tcx.sess, cond_expr.span)
|
||||||
&& !matches!(
|
&& !matches!(
|
||||||
cond_expr.kind,
|
cond_expr.kind,
|
||||||
hir::ExprKind::Match(.., hir::MatchSource::TryDesugar)
|
hir::ExprKind::Match(.., hir::MatchSource::TryDesugar(_))
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
err.span_label(cond_expr.span, "expected this to be `()`");
|
err.span_label(cond_expr.span, "expected this to be `()`");
|
||||||
|
|
|
@ -1580,7 +1580,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
let coerce = ctxt.coerce.as_mut().unwrap();
|
let coerce = ctxt.coerce.as_mut().unwrap();
|
||||||
if let Some((tail_expr, tail_expr_ty)) = tail_expr_ty {
|
if let Some((tail_expr, tail_expr_ty)) = tail_expr_ty {
|
||||||
let span = self.get_expr_coercion_span(tail_expr);
|
let span = self.get_expr_coercion_span(tail_expr);
|
||||||
let cause = self.cause(span, ObligationCauseCode::BlockTailExpression(blk.hir_id));
|
let cause = self.cause(
|
||||||
|
span,
|
||||||
|
ObligationCauseCode::BlockTailExpression(blk.hir_id, hir::MatchSource::Normal),
|
||||||
|
);
|
||||||
let ty_for_diagnostic = coerce.merged_ty();
|
let ty_for_diagnostic = coerce.merged_ty();
|
||||||
// We use coerce_inner here because we want to augment the error
|
// We use coerce_inner here because we want to augment the error
|
||||||
// suggesting to wrap the block in square brackets if it might've
|
// suggesting to wrap the block in square brackets if it might've
|
||||||
|
|
|
@ -743,6 +743,35 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||||
ObligationCauseCode::Pattern { origin_expr: false, span: Some(span), .. } => {
|
ObligationCauseCode::Pattern { origin_expr: false, span: Some(span), .. } => {
|
||||||
err.span_label(span, "expected due to this");
|
err.span_label(span, "expected due to this");
|
||||||
}
|
}
|
||||||
|
ObligationCauseCode::BlockTailExpression(
|
||||||
|
_,
|
||||||
|
hir::MatchSource::TryDesugar(scrut_hir_id),
|
||||||
|
) => {
|
||||||
|
if let Some(ty::error::ExpectedFound { expected, .. }) = exp_found {
|
||||||
|
let scrut_expr = self.tcx.hir().expect_expr(scrut_hir_id);
|
||||||
|
let scrut_ty = if let hir::ExprKind::Call(_, args) = &scrut_expr.kind {
|
||||||
|
let arg_expr = args.first().expect("try desugaring call w/out arg");
|
||||||
|
self.typeck_results.as_ref().and_then(|typeck_results| {
|
||||||
|
typeck_results.expr_ty_opt(arg_expr)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
bug!("try desugaring w/out call expr as scrutinee");
|
||||||
|
};
|
||||||
|
|
||||||
|
match scrut_ty {
|
||||||
|
Some(ty) if expected == ty => {
|
||||||
|
let source_map = self.tcx.sess.source_map();
|
||||||
|
err.span_suggestion(
|
||||||
|
source_map.end_point(cause.span()),
|
||||||
|
"try removing this `?`",
|
||||||
|
"",
|
||||||
|
Applicability::MachineApplicable,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
|
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
|
||||||
arm_block_id,
|
arm_block_id,
|
||||||
arm_span,
|
arm_span,
|
||||||
|
@ -752,12 +781,11 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||||
prior_arm_ty,
|
prior_arm_ty,
|
||||||
source,
|
source,
|
||||||
ref prior_arms,
|
ref prior_arms,
|
||||||
scrut_hir_id,
|
|
||||||
opt_suggest_box_span,
|
opt_suggest_box_span,
|
||||||
scrut_span,
|
scrut_span,
|
||||||
..
|
..
|
||||||
}) => match source {
|
}) => match source {
|
||||||
hir::MatchSource::TryDesugar => {
|
hir::MatchSource::TryDesugar(scrut_hir_id) => {
|
||||||
if let Some(ty::error::ExpectedFound { expected, .. }) = exp_found {
|
if let Some(ty::error::ExpectedFound { expected, .. }) = exp_found {
|
||||||
let scrut_expr = self.tcx.hir().expect_expr(scrut_hir_id);
|
let scrut_expr = self.tcx.hir().expect_expr(scrut_hir_id);
|
||||||
let scrut_ty = if let hir::ExprKind::Call(_, args) = &scrut_expr.kind {
|
let scrut_ty = if let hir::ExprKind::Call(_, args) = &scrut_expr.kind {
|
||||||
|
@ -1978,7 +2006,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||||
trace: &TypeTrace<'tcx>,
|
trace: &TypeTrace<'tcx>,
|
||||||
terr: TypeError<'tcx>,
|
terr: TypeError<'tcx>,
|
||||||
) -> Vec<TypeErrorAdditionalDiags> {
|
) -> Vec<TypeErrorAdditionalDiags> {
|
||||||
use crate::traits::ObligationCauseCode::MatchExpressionArm;
|
use crate::traits::ObligationCauseCode::{BlockTailExpression, MatchExpressionArm};
|
||||||
let mut suggestions = Vec::new();
|
let mut suggestions = Vec::new();
|
||||||
let span = trace.cause.span();
|
let span = trace.cause.span();
|
||||||
let values = self.resolve_vars_if_possible(trace.values);
|
let values = self.resolve_vars_if_possible(trace.values);
|
||||||
|
@ -1996,11 +2024,17 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||||
// specify a byte literal
|
// specify a byte literal
|
||||||
(ty::Uint(ty::UintTy::U8), ty::Char) => {
|
(ty::Uint(ty::UintTy::U8), ty::Char) => {
|
||||||
if let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span)
|
if let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span)
|
||||||
&& let Some(code) = code.strip_prefix('\'').and_then(|s| s.strip_suffix('\''))
|
&& let Some(code) =
|
||||||
&& !code.starts_with("\\u") // forbid all Unicode escapes
|
code.strip_prefix('\'').and_then(|s| s.strip_suffix('\''))
|
||||||
&& code.chars().next().is_some_and(|c| c.is_ascii()) // forbids literal Unicode characters beyond ASCII
|
// forbid all Unicode escapes
|
||||||
|
&& !code.starts_with("\\u")
|
||||||
|
// forbids literal Unicode characters beyond ASCII
|
||||||
|
&& code.chars().next().is_some_and(|c| c.is_ascii())
|
||||||
{
|
{
|
||||||
suggestions.push(TypeErrorAdditionalDiags::MeantByteLiteral { span, code: escape_literal(code) })
|
suggestions.push(TypeErrorAdditionalDiags::MeantByteLiteral {
|
||||||
|
span,
|
||||||
|
code: escape_literal(code),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If a character was expected and the found expression is a string literal
|
// If a character was expected and the found expression is a string literal
|
||||||
|
@ -2011,7 +2045,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||||
&& let Some(code) = code.strip_prefix('"').and_then(|s| s.strip_suffix('"'))
|
&& let Some(code) = code.strip_prefix('"').and_then(|s| s.strip_suffix('"'))
|
||||||
&& code.chars().count() == 1
|
&& code.chars().count() == 1
|
||||||
{
|
{
|
||||||
suggestions.push(TypeErrorAdditionalDiags::MeantCharLiteral { span, code: escape_literal(code) })
|
suggestions.push(TypeErrorAdditionalDiags::MeantCharLiteral {
|
||||||
|
span,
|
||||||
|
code: escape_literal(code),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If a string was expected and the found expression is a character literal,
|
// If a string was expected and the found expression is a character literal,
|
||||||
|
@ -2021,7 +2058,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||||
if let Some(code) =
|
if let Some(code) =
|
||||||
code.strip_prefix('\'').and_then(|s| s.strip_suffix('\''))
|
code.strip_prefix('\'').and_then(|s| s.strip_suffix('\''))
|
||||||
{
|
{
|
||||||
suggestions.push(TypeErrorAdditionalDiags::MeantStrLiteral { span, code: escape_literal(code) })
|
suggestions.push(TypeErrorAdditionalDiags::MeantStrLiteral {
|
||||||
|
span,
|
||||||
|
code: escape_literal(code),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2030,17 +2070,24 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||||
(ty::Bool, ty::Tuple(list)) => if list.len() == 0 {
|
(ty::Bool, ty::Tuple(list)) => if list.len() == 0 {
|
||||||
suggestions.extend(self.suggest_let_for_letchains(&trace.cause, span));
|
suggestions.extend(self.suggest_let_for_letchains(&trace.cause, span));
|
||||||
}
|
}
|
||||||
(ty::Array(_, _), ty::Array(_, _)) => suggestions.extend(self.suggest_specify_actual_length(terr, trace, span)),
|
(ty::Array(_, _), ty::Array(_, _)) => {
|
||||||
|
suggestions.extend(self.suggest_specify_actual_length(terr, trace, span))
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let code = trace.cause.code();
|
let code = trace.cause.code();
|
||||||
if let &MatchExpressionArm(box MatchExpressionArmCause { source, .. }) = code
|
if let &(MatchExpressionArm(box MatchExpressionArmCause { source, .. })
|
||||||
&& let hir::MatchSource::TryDesugar = source
|
| BlockTailExpression(.., source)
|
||||||
&& let Some((expected_ty, found_ty, _, _)) = self.values_str(trace.values)
|
) = code
|
||||||
{
|
&& let hir::MatchSource::TryDesugar(_) = source
|
||||||
suggestions.push(TypeErrorAdditionalDiags::TryCannotConvert { found: found_ty.content(), expected: expected_ty.content() });
|
&& let Some((expected_ty, found_ty, _, _)) = self.values_str(trace.values)
|
||||||
}
|
{
|
||||||
|
suggestions.push(TypeErrorAdditionalDiags::TryCannotConvert {
|
||||||
|
found: found_ty.content(),
|
||||||
|
expected: expected_ty.content(),
|
||||||
|
});
|
||||||
|
}
|
||||||
suggestions
|
suggestions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2910,8 +2957,11 @@ impl<'tcx> ObligationCauseExt<'tcx> for ObligationCause<'tcx> {
|
||||||
CompareImplItemObligation { kind: ty::AssocKind::Const, .. } => {
|
CompareImplItemObligation { kind: ty::AssocKind::Const, .. } => {
|
||||||
ObligationCauseFailureCode::ConstCompat { span, subdiags }
|
ObligationCauseFailureCode::ConstCompat { span, subdiags }
|
||||||
}
|
}
|
||||||
|
BlockTailExpression(.., hir::MatchSource::TryDesugar(_)) => {
|
||||||
|
ObligationCauseFailureCode::TryCompat { span, subdiags }
|
||||||
|
}
|
||||||
MatchExpressionArm(box MatchExpressionArmCause { source, .. }) => match source {
|
MatchExpressionArm(box MatchExpressionArmCause { source, .. }) => match source {
|
||||||
hir::MatchSource::TryDesugar => {
|
hir::MatchSource::TryDesugar(_) => {
|
||||||
ObligationCauseFailureCode::TryCompat { span, subdiags }
|
ObligationCauseFailureCode::TryCompat { span, subdiags }
|
||||||
}
|
}
|
||||||
_ => ObligationCauseFailureCode::MatchCompat { span, subdiags },
|
_ => ObligationCauseFailureCode::MatchCompat { span, subdiags },
|
||||||
|
|
|
@ -146,7 +146,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
|
||||||
|
|
||||||
if let SubregionOrigin::Subtype(box TypeTrace { cause, .. }) = sub_origin {
|
if let SubregionOrigin::Subtype(box TypeTrace { cause, .. }) = sub_origin {
|
||||||
if let ObligationCauseCode::ReturnValue(hir_id)
|
if let ObligationCauseCode::ReturnValue(hir_id)
|
||||||
| ObligationCauseCode::BlockTailExpression(hir_id) = cause.code()
|
| ObligationCauseCode::BlockTailExpression(hir_id, ..) = cause.code()
|
||||||
{
|
{
|
||||||
let parent_id = tcx.hir().get_parent_item(*hir_id);
|
let parent_id = tcx.hir().get_parent_item(*hir_id);
|
||||||
if let Some(fn_decl) = tcx.hir().fn_decl_by_hir_id(parent_id.into()) {
|
if let Some(fn_decl) = tcx.hir().fn_decl_by_hir_id(parent_id.into()) {
|
||||||
|
|
|
@ -346,6 +346,7 @@ pub enum ExprKind<'tcx> {
|
||||||
/// A `match` expression.
|
/// A `match` expression.
|
||||||
Match {
|
Match {
|
||||||
scrutinee: ExprId,
|
scrutinee: ExprId,
|
||||||
|
scrutinee_hir_id: hir::HirId,
|
||||||
arms: Box<[ArmId]>,
|
arms: Box<[ArmId]>,
|
||||||
},
|
},
|
||||||
/// A block.
|
/// A block.
|
||||||
|
|
|
@ -70,7 +70,7 @@ pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Exp
|
||||||
visitor.visit_expr(&visitor.thir()[expr]);
|
visitor.visit_expr(&visitor.thir()[expr]);
|
||||||
}
|
}
|
||||||
Loop { body } => visitor.visit_expr(&visitor.thir()[body]),
|
Loop { body } => visitor.visit_expr(&visitor.thir()[body]),
|
||||||
Match { scrutinee, ref arms } => {
|
Match { scrutinee, ref arms, .. } => {
|
||||||
visitor.visit_expr(&visitor.thir()[scrutinee]);
|
visitor.visit_expr(&visitor.thir()[scrutinee]);
|
||||||
for &arm in &**arms {
|
for &arm in &**arms {
|
||||||
visitor.visit_arm(&visitor.thir()[arm]);
|
visitor.visit_arm(&visitor.thir()[arm]);
|
||||||
|
|
|
@ -402,7 +402,7 @@ pub enum ObligationCauseCode<'tcx> {
|
||||||
OpaqueReturnType(Option<(Ty<'tcx>, Span)>),
|
OpaqueReturnType(Option<(Ty<'tcx>, Span)>),
|
||||||
|
|
||||||
/// Block implicit return
|
/// Block implicit return
|
||||||
BlockTailExpression(hir::HirId),
|
BlockTailExpression(hir::HirId, hir::MatchSource),
|
||||||
|
|
||||||
/// #[feature(trivial_bounds)] is not enabled
|
/// #[feature(trivial_bounds)] is not enabled
|
||||||
TrivialBound,
|
TrivialBound,
|
||||||
|
@ -543,7 +543,6 @@ pub struct MatchExpressionArmCause<'tcx> {
|
||||||
pub scrut_span: Span,
|
pub scrut_span: Span,
|
||||||
pub source: hir::MatchSource,
|
pub source: hir::MatchSource,
|
||||||
pub prior_arms: Vec<Span>,
|
pub prior_arms: Vec<Span>,
|
||||||
pub scrut_hir_id: hir::HirId,
|
|
||||||
pub opt_suggest_box_span: Option<Span>,
|
pub opt_suggest_box_span: Option<Span>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
|
||||||
let target = self.parse_block(args[1])?;
|
let target = self.parse_block(args[1])?;
|
||||||
self.parse_call(args[2], destination, target)
|
self.parse_call(args[2], destination, target)
|
||||||
},
|
},
|
||||||
ExprKind::Match { scrutinee, arms } => {
|
ExprKind::Match { scrutinee, arms, .. } => {
|
||||||
let discr = self.parse_operand(*scrutinee)?;
|
let discr = self.parse_operand(*scrutinee)?;
|
||||||
self.parse_match(arms, expr.span).map(|t| TerminatorKind::SwitchInt { discr, targets: t })
|
self.parse_match(arms, expr.span).map(|t| TerminatorKind::SwitchInt { discr, targets: t })
|
||||||
},
|
},
|
||||||
|
|
|
@ -47,7 +47,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
ExprKind::Block { block: ast_block } => {
|
ExprKind::Block { block: ast_block } => {
|
||||||
this.ast_block(destination, block, ast_block, source_info)
|
this.ast_block(destination, block, ast_block, source_info)
|
||||||
}
|
}
|
||||||
ExprKind::Match { scrutinee, ref arms } => {
|
ExprKind::Match { scrutinee, ref arms, .. } => {
|
||||||
this.match_expr(destination, expr_span, block, &this.thir[scrutinee], arms)
|
this.match_expr(destination, expr_span, block, &this.thir[scrutinee], arms)
|
||||||
}
|
}
|
||||||
ExprKind::If { cond, then, else_opt, if_then_scope } => {
|
ExprKind::If { cond, then, else_opt, if_then_scope } => {
|
||||||
|
|
|
@ -732,6 +732,7 @@ impl<'tcx> Cx<'tcx> {
|
||||||
},
|
},
|
||||||
hir::ExprKind::Match(ref discr, ref arms, _) => ExprKind::Match {
|
hir::ExprKind::Match(ref discr, ref arms, _) => ExprKind::Match {
|
||||||
scrutinee: self.mirror_expr(discr),
|
scrutinee: self.mirror_expr(discr),
|
||||||
|
scrutinee_hir_id: discr.hir_id,
|
||||||
arms: arms.iter().map(|a| self.convert_arm(a)).collect(),
|
arms: arms.iter().map(|a| self.convert_arm(a)).collect(),
|
||||||
},
|
},
|
||||||
hir::ExprKind::Loop(ref body, ..) => {
|
hir::ExprKind::Loop(ref body, ..) => {
|
||||||
|
|
|
@ -135,10 +135,12 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for MatchVisitor<'a, '_, 'tcx> {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ExprKind::Match { scrutinee, box ref arms } => {
|
ExprKind::Match { scrutinee, scrutinee_hir_id, box ref arms } => {
|
||||||
let source = match ex.span.desugaring_kind() {
|
let source = match ex.span.desugaring_kind() {
|
||||||
Some(DesugaringKind::ForLoop) => hir::MatchSource::ForLoopDesugar,
|
Some(DesugaringKind::ForLoop) => hir::MatchSource::ForLoopDesugar,
|
||||||
Some(DesugaringKind::QuestionMark) => hir::MatchSource::TryDesugar,
|
Some(DesugaringKind::QuestionMark) => {
|
||||||
|
hir::MatchSource::TryDesugar(scrutinee_hir_id)
|
||||||
|
}
|
||||||
Some(DesugaringKind::Await) => hir::MatchSource::AwaitDesugar,
|
Some(DesugaringKind::Await) => hir::MatchSource::AwaitDesugar,
|
||||||
_ => hir::MatchSource::Normal,
|
_ => hir::MatchSource::Normal,
|
||||||
};
|
};
|
||||||
|
@ -277,7 +279,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
|
||||||
| hir::MatchSource::FormatArgs => report_arm_reachability(&cx, &report),
|
| hir::MatchSource::FormatArgs => report_arm_reachability(&cx, &report),
|
||||||
// Unreachable patterns in try and await expressions occur when one of
|
// Unreachable patterns in try and await expressions occur when one of
|
||||||
// the arms are an uninhabited type. Which is OK.
|
// the arms are an uninhabited type. Which is OK.
|
||||||
hir::MatchSource::AwaitDesugar | hir::MatchSource::TryDesugar => {}
|
hir::MatchSource::AwaitDesugar | hir::MatchSource::TryDesugar(_) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the match is exhaustive.
|
// Check if the match is exhaustive.
|
||||||
|
|
|
@ -321,7 +321,7 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> {
|
||||||
print_indented!(self, format!("pat: {:?}", pat), depth_lvl + 1);
|
print_indented!(self, format!("pat: {:?}", pat), depth_lvl + 1);
|
||||||
print_indented!(self, "}", depth_lvl);
|
print_indented!(self, "}", depth_lvl);
|
||||||
}
|
}
|
||||||
Match { scrutinee, arms } => {
|
Match { scrutinee, arms, .. } => {
|
||||||
print_indented!(self, "Match {", depth_lvl);
|
print_indented!(self, "Match {", depth_lvl);
|
||||||
print_indented!(self, "scrutinee:", depth_lvl + 1);
|
print_indented!(self, "scrutinee:", depth_lvl + 1);
|
||||||
self.print_expr(*scrutinee, depth_lvl + 2);
|
self.print_expr(*scrutinee, depth_lvl + 2);
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl NonConstExpr {
|
||||||
|
|
||||||
Self::Loop(ForLoop) | Self::Match(ForLoopDesugar) => &[sym::const_for],
|
Self::Loop(ForLoop) | Self::Match(ForLoopDesugar) => &[sym::const_for],
|
||||||
|
|
||||||
Self::Match(TryDesugar) => &[sym::const_try],
|
Self::Match(TryDesugar(_)) => &[sym::const_try],
|
||||||
|
|
||||||
// All other expressions are allowed.
|
// All other expressions are allowed.
|
||||||
Self::Loop(Loop | While) | Self::Match(Normal | FormatArgs) => &[],
|
Self::Loop(Loop | While) | Self::Match(Normal | FormatArgs) => &[],
|
||||||
|
|
|
@ -2700,7 +2700,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||||
| ObligationCauseCode::MatchImpl(..)
|
| ObligationCauseCode::MatchImpl(..)
|
||||||
| ObligationCauseCode::ReturnType
|
| ObligationCauseCode::ReturnType
|
||||||
| ObligationCauseCode::ReturnValue(_)
|
| ObligationCauseCode::ReturnValue(_)
|
||||||
| ObligationCauseCode::BlockTailExpression(_)
|
| ObligationCauseCode::BlockTailExpression(..)
|
||||||
| ObligationCauseCode::AwaitableExpr(_)
|
| ObligationCauseCode::AwaitableExpr(_)
|
||||||
| ObligationCauseCode::ForLoopIterator
|
| ObligationCauseCode::ForLoopIterator
|
||||||
| ObligationCauseCode::QuestionMark
|
| ObligationCauseCode::QuestionMark
|
||||||
|
|
|
@ -802,7 +802,8 @@ fn in_postfix_position<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> boo
|
||||||
match parent.kind {
|
match parent.kind {
|
||||||
ExprKind::Call(child, _) | ExprKind::MethodCall(_, child, _, _) | ExprKind::Index(child, _, _)
|
ExprKind::Call(child, _) | ExprKind::MethodCall(_, child, _, _) | ExprKind::Index(child, _, _)
|
||||||
if child.hir_id == e.hir_id => true,
|
if child.hir_id == e.hir_id => true,
|
||||||
ExprKind::Field(_, _) | ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) => true,
|
ExprKind::Match(.., MatchSource::TryDesugar(_) | MatchSource::AwaitDesugar)
|
||||||
|
| ExprKind::Field(_, _) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1038,7 +1038,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
|
||||||
wild_in_or_pats::check(cx, arms);
|
wild_in_or_pats::check(cx, arms);
|
||||||
}
|
}
|
||||||
|
|
||||||
if source == MatchSource::TryDesugar {
|
if let MatchSource::TryDesugar(_) = source {
|
||||||
try_err::check(cx, expr, ex);
|
try_err::check(cx, expr, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutine
|
||||||
|
|
||||||
/// Finds function return type by examining return expressions in match arms.
|
/// Finds function return type by examining return expressions in match arms.
|
||||||
fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> Option<Ty<'tcx>> {
|
fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> Option<Ty<'tcx>> {
|
||||||
if let ExprKind::Match(_, arms, MatchSource::TryDesugar) = expr {
|
if let ExprKind::Match(_, arms, MatchSource::TryDesugar(_)) = expr {
|
||||||
for arm in *arms {
|
for arm in *arms {
|
||||||
if let ExprKind::Ret(Some(ret)) = arm.body.kind {
|
if let ExprKind::Ret(Some(ret)) = arm.body.kind {
|
||||||
return Some(cx.typeck_results().expr_ty(ret));
|
return Some(cx.typeck_results().expr_ty(ret));
|
||||||
|
|
|
@ -64,7 +64,7 @@ pub(super) fn check(
|
||||||
ExprKind::Path(QPath::LangItem(rustc_hir::LangItem::TryTraitBranch, _, _))
|
ExprKind::Path(QPath::LangItem(rustc_hir::LangItem::TryTraitBranch, _, _))
|
||||||
),
|
),
|
||||||
ExprKind::MethodCall(_, self_arg, ..) if expr.hir_id == self_arg.hir_id => true,
|
ExprKind::MethodCall(_, self_arg, ..) if expr.hir_id == self_arg.hir_id => true,
|
||||||
ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
|
ExprKind::Match(_, _, MatchSource::TryDesugar(_) | MatchSource::AwaitDesugar)
|
||||||
| ExprKind::Field(..)
|
| ExprKind::Field(..)
|
||||||
| ExprKind::Index(..) => true,
|
| ExprKind::Index(..) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
|
|
|
@ -236,7 +236,7 @@ fn indirect_usage<'tcx>(
|
||||||
!matches!(
|
!matches!(
|
||||||
node,
|
node,
|
||||||
Node::Expr(Expr {
|
Node::Expr(Expr {
|
||||||
kind: ExprKind::Match(.., MatchSource::TryDesugar),
|
kind: ExprKind::Match(.., MatchSource::TryDesugar(_)),
|
||||||
..
|
..
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -122,7 +122,7 @@ fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &arg.kind;
|
if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar(_)) = &arg.kind;
|
||||||
if let ExprKind::Call(called, [inner_expr]) = &inner_expr_with_q.kind;
|
if let ExprKind::Call(called, [inner_expr]) = &inner_expr_with_q.kind;
|
||||||
if let ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)) = &called.kind;
|
if let ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)) = &called.kind;
|
||||||
if expr.span.ctxt() == inner_expr.span.ctxt();
|
if expr.span.ctxt() == inner_expr.span.ctxt();
|
||||||
|
|
|
@ -34,7 +34,7 @@ declare_lint_pass!(QuestionMarkUsed => [QUESTION_MARK_USED]);
|
||||||
|
|
||||||
impl<'tcx> LateLintPass<'tcx> for QuestionMarkUsed {
|
impl<'tcx> LateLintPass<'tcx> for QuestionMarkUsed {
|
||||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||||
if let ExprKind::Match(_, _, MatchSource::TryDesugar) = expr.kind {
|
if let ExprKind::Match(_, _, MatchSource::TryDesugar(_)) = expr.kind {
|
||||||
if !span_is_local(expr.span) {
|
if !span_is_local(expr.span) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ impl ReturnVisitor {
|
||||||
|
|
||||||
impl<'tcx> Visitor<'tcx> for ReturnVisitor {
|
impl<'tcx> Visitor<'tcx> for ReturnVisitor {
|
||||||
fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
|
fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
|
||||||
if let hir::ExprKind::Ret(_) | hir::ExprKind::Match(.., hir::MatchSource::TryDesugar) = ex.kind {
|
if let hir::ExprKind::Ret(_) | hir::ExprKind::Match(.., hir::MatchSource::TryDesugar(_)) = ex.kind {
|
||||||
self.found_return = true;
|
self.found_return = true;
|
||||||
} else {
|
} else {
|
||||||
hir_visit::walk_expr(self, ex);
|
hir_visit::walk_expr(self, ex);
|
||||||
|
|
|
@ -164,7 +164,7 @@ impl<'tcx> LateLintPass<'tcx> for Return {
|
||||||
if !in_external_macro(cx.sess(), stmt.span)
|
if !in_external_macro(cx.sess(), stmt.span)
|
||||||
&& let StmtKind::Semi(expr) = stmt.kind
|
&& let StmtKind::Semi(expr) = stmt.kind
|
||||||
&& let ExprKind::Ret(Some(ret)) = expr.kind
|
&& let ExprKind::Ret(Some(ret)) = expr.kind
|
||||||
&& let ExprKind::Match(.., MatchSource::TryDesugar) = ret.kind
|
&& let ExprKind::Match(.., MatchSource::TryDesugar(_)) = ret.kind
|
||||||
// Ensure this is not the final stmt, otherwise removing it would cause a compile error
|
// Ensure this is not the final stmt, otherwise removing it would cause a compile error
|
||||||
&& let OwnerNode::Item(item) = cx.tcx.hir().owner(cx.tcx.hir().get_parent_item(expr.hir_id))
|
&& let OwnerNode::Item(item) = cx.tcx.hir().owner(cx.tcx.hir().get_parent_item(expr.hir_id))
|
||||||
&& let ItemKind::Fn(_, _, body) = item.kind
|
&& let ItemKind::Fn(_, _, body) = item.kind
|
||||||
|
|
|
@ -42,7 +42,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||||
if cx.typeck_results().expr_ty(arg).is_unit() && !utils::is_unit_literal(arg) {
|
if cx.typeck_results().expr_ty(arg).is_unit() && !utils::is_unit_literal(arg) {
|
||||||
!matches!(
|
!matches!(
|
||||||
&arg.kind,
|
&arg.kind,
|
||||||
ExprKind::Match(.., MatchSource::TryDesugar) | ExprKind::Path(..)
|
ExprKind::Match(.., MatchSource::TryDesugar(_)) | ExprKind::Path(..)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
|
@ -113,7 +113,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
||||||
}
|
}
|
||||||
|
|
||||||
match e.kind {
|
match e.kind {
|
||||||
ExprKind::Match(_, arms, MatchSource::TryDesugar) => {
|
ExprKind::Match(_, arms, MatchSource::TryDesugar(_)) => {
|
||||||
let (ExprKind::Ret(Some(e)) | ExprKind::Break(_, Some(e))) = arms[0].body.kind else {
|
let (ExprKind::Ret(Some(e)) | ExprKind::Break(_, Some(e))) = arms[0].body.kind else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
|
@ -149,7 +149,7 @@ fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) {
|
||||||
(Pat::Str("for"), Pat::Str("}"))
|
(Pat::Str("for"), Pat::Str("}"))
|
||||||
},
|
},
|
||||||
ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")),
|
ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")),
|
||||||
ExprKind::Match(e, _, MatchSource::TryDesugar) => (expr_search_pat(tcx, e).0, Pat::Str("?")),
|
ExprKind::Match(e, _, MatchSource::TryDesugar(_)) => (expr_search_pat(tcx, e).0, Pat::Str("?")),
|
||||||
ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => {
|
ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => {
|
||||||
(expr_search_pat(tcx, e).0, Pat::Str("await"))
|
(expr_search_pat(tcx, e).0, Pat::Str("await"))
|
||||||
},
|
},
|
||||||
|
|
|
@ -1765,7 +1765,7 @@ pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tc
|
||||||
|
|
||||||
if let ExprKind::Match(_, arms, ref source) = expr.kind {
|
if let ExprKind::Match(_, arms, ref source) = expr.kind {
|
||||||
// desugared from a `?` operator
|
// desugared from a `?` operator
|
||||||
if *source == MatchSource::TryDesugar {
|
if let MatchSource::TryDesugar(_) = *source {
|
||||||
return Some(expr);
|
return Some(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,7 @@ pub fn for_each_expr_with_closures<'tcx, B, C: Continue>(
|
||||||
/// returns `true` if expr contains match expr desugared from try
|
/// returns `true` if expr contains match expr desugared from try
|
||||||
fn contains_try(expr: &hir::Expr<'_>) -> bool {
|
fn contains_try(expr: &hir::Expr<'_>) -> bool {
|
||||||
for_each_expr(expr, |e| {
|
for_each_expr(expr, |e| {
|
||||||
if matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar)) {
|
if matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar(_))) {
|
||||||
ControlFlow::Break(())
|
ControlFlow::Break(())
|
||||||
} else {
|
} else {
|
||||||
ControlFlow::Continue(())
|
ControlFlow::Continue(())
|
||||||
|
|
|
@ -98,7 +98,7 @@ fn if_same_then_else2() -> Result<&'static str, ()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
if true {
|
if true {
|
||||||
//~^ ERROR: this `if` has identical blocks
|
// FIXME: should emit "this `if` has identical blocks"
|
||||||
Ok("foo")?;
|
Ok("foo")?;
|
||||||
} else {
|
} else {
|
||||||
Ok("foo")?;
|
Ok("foo")?;
|
||||||
|
|
|
@ -82,25 +82,6 @@ LL | | f32::NAN
|
||||||
LL | | };
|
LL | | };
|
||||||
| |_____^
|
| |_____^
|
||||||
|
|
||||||
error: this `if` has identical blocks
|
|
||||||
--> $DIR/if_same_then_else2.rs:100:13
|
|
||||||
|
|
|
||||||
LL | if true {
|
|
||||||
| _____________^
|
|
||||||
LL | |
|
|
||||||
LL | | Ok("foo")?;
|
|
||||||
LL | | } else {
|
|
||||||
| |_____^
|
|
||||||
|
|
|
||||||
note: same as this
|
|
||||||
--> $DIR/if_same_then_else2.rs:103:12
|
|
||||||
|
|
|
||||||
LL | } else {
|
|
||||||
| ____________^
|
|
||||||
LL | | Ok("foo")?;
|
|
||||||
LL | | }
|
|
||||||
| |_____^
|
|
||||||
|
|
||||||
error: this `if` has identical blocks
|
error: this `if` has identical blocks
|
||||||
--> $DIR/if_same_then_else2.rs:124:20
|
--> $DIR/if_same_then_else2.rs:124:20
|
||||||
|
|
|
|
||||||
|
@ -122,5 +103,5 @@ LL | | return Ok(&foo[0..]);
|
||||||
LL | | }
|
LL | | }
|
||||||
| |_____^
|
| |_____^
|
||||||
|
|
||||||
error: aborting due to 6 previous errors
|
error: aborting due to 5 previous errors
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,8 @@ LL + Some(())
|
||||||
error[E0308]: `?` operator has incompatible types
|
error[E0308]: `?` operator has incompatible types
|
||||||
--> $DIR/compatible-variants.rs:35:5
|
--> $DIR/compatible-variants.rs:35:5
|
||||||
|
|
|
|
||||||
|
LL | fn d() -> Option<()> {
|
||||||
|
| ---------- expected `Option<()>` because of return type
|
||||||
LL | c()?
|
LL | c()?
|
||||||
| ^^^^ expected `Option<()>`, found `()`
|
| ^^^^ expected `Option<()>`, found `()`
|
||||||
|
|
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
error[E0308]: `?` operator has incompatible types
|
error[E0308]: `?` operator has incompatible types
|
||||||
--> $DIR/issue-51632-try-desugar-incompatible-types.rs:8:5
|
--> $DIR/issue-51632-try-desugar-incompatible-types.rs:8:5
|
||||||
|
|
|
|
||||||
|
LL | fn forbidden_narratives() -> Result<isize, ()> {
|
||||||
|
| ----------------- expected `Result<isize, ()>` because of return type
|
||||||
LL | missing_discourses()?
|
LL | missing_discourses()?
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^ expected `Result<isize, ()>`, found `isize`
|
| ^^^^^^^^^^^^^^^^^^^^^ expected `Result<isize, ()>`, found `isize`
|
||||||
|
|
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
|
fn test(shouldwe: Option<u32>, shouldwe2: Option<u32>) -> u32 {
|
||||||
|
//~^ NOTE expected `u32` because of return type
|
||||||
|
match shouldwe {
|
||||||
|
Some(val) => {
|
||||||
|
match shouldwe2 {
|
||||||
|
Some(val) => {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
None => (), //~ ERROR mismatched types
|
||||||
|
//~^ NOTE expected `u32`, found `()`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => return 12,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("returned {}", test(None, Some(5)));
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
error[E0308]: mismatched types
|
||||||
|
--> $DIR/non-first-arm-doesnt-match-expected-return-type.rs:11:25
|
||||||
|
|
|
||||||
|
LL | fn test(shouldwe: Option<u32>, shouldwe2: Option<u32>) -> u32 {
|
||||||
|
| --- expected `u32` because of return type
|
||||||
|
...
|
||||||
|
LL | None => (),
|
||||||
|
| ^^ expected `u32`, found `()`
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0308`.
|
|
@ -1,6 +1,9 @@
|
||||||
error[E0308]: `?` operator has incompatible types
|
error[E0308]: `?` operator has incompatible types
|
||||||
--> $DIR/remove-question-symbol-with-paren.rs:5:6
|
--> $DIR/remove-question-symbol-with-paren.rs:5:6
|
||||||
|
|
|
|
||||||
|
LL | fn foo() -> Option<()> {
|
||||||
|
| ---------- expected `Option<()>` because of return type
|
||||||
|
LL | let x = Some(());
|
||||||
LL | (x?)
|
LL | (x?)
|
||||||
| ^^ expected `Option<()>`, found `()`
|
| ^^ expected `Option<()>`, found `()`
|
||||||
|
|
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue