1
Fork 0

Introduce TypeErrCtxt

TypeErrCtxt optionally has a TypeckResults so that InferCtxt doesn't
need to.
This commit is contained in:
Cameron Steffen 2022-09-09 15:08:06 -05:00
parent 5854680388
commit 4a68373217
42 changed files with 655 additions and 589 deletions

View file

@ -75,7 +75,7 @@ use rustc_middle::ty::{
};
use rustc_span::{sym, symbol::kw, BytePos, DesugaringKind, Pos, Span};
use rustc_target::spec::abi;
use std::ops::ControlFlow;
use std::ops::{ControlFlow, Deref};
use std::{cmp, fmt, iter};
mod note;
@ -85,6 +85,31 @@ pub use need_type_info::TypeAnnotationNeeded;
pub mod nice_region_error;
/// A helper for building type related errors. The `typeck_results`
/// field is only populated during an in-progress typeck.
/// Get an instance by calling `InferCtxt::err` or `FnCtxt::infer_err`.
pub struct TypeErrCtxt<'a, 'tcx> {
pub infcx: &'a InferCtxt<'a, 'tcx>,
pub typeck_results: Option<std::cell::Ref<'a, ty::TypeckResults<'tcx>>>,
}
impl TypeErrCtxt<'_, '_> {
/// This is just to avoid a potential footgun of accidentally
/// dropping `typeck_results` by calling `InferCtxt::err_ctxt`
#[deprecated(note = "you already have a `TypeErrCtxt`")]
#[allow(unused)]
pub fn err_ctxt(&self) -> ! {
bug!("called `err_ctxt` on `TypeErrCtxt`. Try removing the call");
}
}
impl<'a, 'tcx> Deref for TypeErrCtxt<'a, 'tcx> {
type Target = InferCtxt<'a, 'tcx>;
fn deref(&self) -> &InferCtxt<'a, 'tcx> {
&self.infcx
}
}
pub(super) fn note_and_explain_region<'tcx>(
tcx: TyCtxt<'tcx>,
err: &mut Diagnostic,
@ -304,7 +329,39 @@ pub fn unexpected_hidden_region_diagnostic<'tcx>(
err
}
impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
impl<'tcx> InferCtxt<'_, 'tcx> {
pub fn get_impl_future_output_ty(&self, ty: Ty<'tcx>) -> Option<Binder<'tcx, Ty<'tcx>>> {
if let ty::Opaque(def_id, substs) = ty.kind() {
let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
// Future::Output
let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0];
let bounds = self.tcx.bound_explicit_item_bounds(*def_id);
for predicate in bounds.transpose_iter().map(|e| e.map_bound(|(p, _)| *p)) {
let predicate = predicate.subst(self.tcx, substs);
let output = predicate
.kind()
.map_bound(|kind| match kind {
ty::PredicateKind::Projection(projection_predicate)
if projection_predicate.projection_ty.item_def_id == item_def_id =>
{
projection_predicate.term.ty()
}
_ => None,
})
.transpose();
if output.is_some() {
// We don't account for multiple `Future::Output = Ty` constraints.
return output;
}
}
}
None
}
}
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
pub fn report_region_errors(
&self,
generic_param_scope: LocalDefId,
@ -578,13 +635,13 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
{
// don't show type `_`
if span.desugaring_kind() == Some(DesugaringKind::ForLoop)
&& let ty::Adt(def, substs) = ty.kind()
&& Some(def.did()) == self.tcx.get_diagnostic_item(sym::Option)
&& let ty::Adt(def, substs) = ty.kind()
&& Some(def.did()) == self.tcx.get_diagnostic_item(sym::Option)
{
err.span_label(span, format!("this is an iterator with items of type `{}`", substs.type_at(0)));
} else {
err.span_label(span, format!("this expression has type `{}`", ty));
}
err.span_label(span, format!("this expression has type `{}`", ty));
}
}
if let Some(ty::error::ExpectedFound { found, .. }) = exp_found
&& ty.is_box() && ty.boxed_ty() == found
@ -620,8 +677,8 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
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.in_progress_typeck_results.and_then(|typeck_results| {
typeck_results.borrow().expr_ty_opt(arg_expr)
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");
@ -727,10 +784,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
_ => {
if let ObligationCauseCode::BindingObligation(_, span)
| ObligationCauseCode::ExprBindingObligation(_, span, ..)
= cause.code().peel_derives()
= cause.code().peel_derives()
&& let TypeError::RegionsPlaceholderMismatch = terr
{
err.span_note(*span, "the lifetime requirement is introduced here");
err.span_note( * span,
"the lifetime requirement is introduced here");
}
}
}
@ -1954,36 +2012,6 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
}
pub fn get_impl_future_output_ty(&self, ty: Ty<'tcx>) -> Option<Binder<'tcx, Ty<'tcx>>> {
if let ty::Opaque(def_id, substs) = ty.kind() {
let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
// Future::Output
let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0];
let bounds = self.tcx.bound_explicit_item_bounds(*def_id);
for predicate in bounds.transpose_iter().map(|e| e.map_bound(|(p, _)| *p)) {
let predicate = predicate.subst(self.tcx, substs);
let output = predicate
.kind()
.map_bound(|kind| match kind {
ty::PredicateKind::Projection(projection_predicate)
if projection_predicate.projection_ty.item_def_id == item_def_id =>
{
projection_predicate.term.ty()
}
_ => None,
})
.transpose();
if output.is_some() {
// We don't account for multiple `Future::Output = Ty` constraints.
return output;
}
}
}
None
}
/// A possible error is to forget to add `.await` when using futures:
///
/// ```compile_fail,E0308
@ -2431,7 +2459,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
origin: Option<SubregionOrigin<'tcx>>,
bound_kind: GenericKind<'tcx>,
sub: Region<'tcx>,
) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
// Attempt to obtain the span of the parameter so we can
// suggest adding an explicit lifetime bound to it.
let generics = self.tcx.generics_of(generic_param_scope);
@ -3111,7 +3139,9 @@ impl<'tcx> InferCtxt<'_, 'tcx> {
_ => rustc_span::DUMMY_SP,
}
}
}
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
/// Be helpful when the user wrote `{... expr; }` and taking the `;` off
/// is enough to fix the error.
pub fn could_remove_semicolon(
@ -3128,7 +3158,7 @@ impl<'tcx> InferCtxt<'_, 'tcx> {
let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else {
return None;
};
let last_expr_ty = self.in_progress_typeck_results?.borrow().expr_ty_opt(*last_expr)?;
let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?;
let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) {
_ if last_expr_ty.references_error() => return None,
_ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => {
@ -3211,8 +3241,9 @@ impl<'tcx> InferCtxt<'_, 'tcx> {
let mut find_compatible_candidates = |pat: &hir::Pat<'_>| {
if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind
&& let Some(pat_ty) = self
.in_progress_typeck_results
.and_then(|typeck_results| typeck_results.borrow().node_type_opt(*hir_id))
.typeck_results
.as_ref()
.and_then(|typeck_results| typeck_results.node_type_opt(*hir_id))
{
let pat_ty = self.resolve_vars_if_possible(pat_ty);
if self.same_type_modulo_infer(pat_ty, expected_ty)

View file

@ -2,6 +2,7 @@ use crate::errors::{
AmbigousImpl, AmbigousReturn, AnnotationRequired, InferenceBadError, NeedTypeInfoInGenerator,
SourceKindMultiSuggestion, SourceKindSubdiag,
};
use crate::infer::error_reporting::TypeErrCtxt;
use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use crate::infer::InferCtxt;
use rustc_errors::IntoDiagnostic;
@ -317,7 +318,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
}
/// Used as a fallback in [InferCtxt::emit_inference_failure_err]
/// Used as a fallback in [TypeErrCtxt::emit_inference_failure_err]
/// in case we weren't able to get a better error.
fn bad_inference_failure_err(
&self,
@ -364,7 +365,9 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
.into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic),
}
}
}
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
pub fn emit_inference_failure_err(
&self,
body_id: Option<hir::BodyId>,
@ -376,14 +379,12 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
let arg = self.resolve_vars_if_possible(arg);
let arg_data = self.extract_inference_diagnostics_data(arg, None);
let Some(typeck_results) = self.in_progress_typeck_results else {
let Some(typeck_results) = &self.typeck_results else {
// If we don't have any typeck results we're outside
// of a body, so we won't be able to get better info
// here.
return self.bad_inference_failure_err(failure_span, arg_data, error_code);
};
let typeck_results = typeck_results.borrow();
let typeck_results = &typeck_results;
let mut local_visitor = FindInferSourceVisitor::new(&self, typeck_results, arg);
if let Some(body_id) = body_id {
@ -563,7 +564,9 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
.into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic),
}
}
}
impl<'tcx> InferCtxt<'_, 'tcx> {
pub fn need_type_info_err_in_generator(
&self,
kind: hir::GeneratorKind,

View file

@ -1,6 +1,6 @@
use crate::infer::error_reporting::TypeErrCtxt;
use crate::infer::lexical_region_resolve::RegionResolutionError;
use crate::infer::lexical_region_resolve::RegionResolutionError::*;
use crate::infer::InferCtxt;
use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed};
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::source_map::Span;
@ -19,34 +19,34 @@ pub use find_anon_type::find_anon_type;
pub use static_impl_trait::{suggest_new_region_bound, HirTraitObjectVisitor, TraitObjectVisitor};
pub use util::find_param_with_region;
impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
pub fn try_report_nice_region_error(&self, error: &RegionResolutionError<'tcx>) -> bool {
impl<'cx, 'tcx> TypeErrCtxt<'cx, 'tcx> {
pub fn try_report_nice_region_error(&'cx self, error: &RegionResolutionError<'tcx>) -> bool {
NiceRegionError::new(self, error.clone()).try_report().is_some()
}
}
pub struct NiceRegionError<'cx, 'tcx> {
infcx: &'cx InferCtxt<'cx, 'tcx>,
cx: &'cx TypeErrCtxt<'cx, 'tcx>,
error: Option<RegionResolutionError<'tcx>>,
regions: Option<(Span, ty::Region<'tcx>, ty::Region<'tcx>)>,
}
impl<'cx, 'tcx> NiceRegionError<'cx, 'tcx> {
pub fn new(infcx: &'cx InferCtxt<'cx, 'tcx>, error: RegionResolutionError<'tcx>) -> Self {
Self { infcx, error: Some(error), regions: None }
pub fn new(cx: &'cx TypeErrCtxt<'cx, 'tcx>, error: RegionResolutionError<'tcx>) -> Self {
Self { cx, error: Some(error), regions: None }
}
pub fn new_from_span(
infcx: &'cx InferCtxt<'cx, 'tcx>,
cx: &'cx TypeErrCtxt<'cx, 'tcx>,
span: Span,
sub: ty::Region<'tcx>,
sup: ty::Region<'tcx>,
) -> Self {
Self { infcx, error: None, regions: Some((span, sub, sup)) }
Self { cx, error: None, regions: Some((span, sub, sup)) }
}
fn tcx(&self) -> TyCtxt<'tcx> {
self.infcx.tcx
self.cx.tcx
}
pub fn try_report_from_nll(&self) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> {

View file

@ -226,12 +226,12 @@ impl<'tcx> NiceRegionError<'_, 'tcx> {
false
};
let expected_trait_ref = self.infcx.resolve_vars_if_possible(ty::TraitRef {
let expected_trait_ref = self.cx.resolve_vars_if_possible(ty::TraitRef {
def_id: trait_def_id,
substs: expected_substs,
});
let actual_trait_ref = self
.infcx
.cx
.resolve_vars_if_possible(ty::TraitRef { def_id: trait_def_id, substs: actual_substs });
// Search the expected and actual trait references to see (a)

View file

@ -486,7 +486,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
tcx,
ctxt.param_env,
ctxt.assoc_item.def_id,
self.infcx.resolve_vars_if_possible(ctxt.substs),
self.cx.resolve_vars_if_possible(ctxt.substs),
) else {
return false;
};

View file

@ -84,12 +84,12 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
let expected_highlight = HighlightBuilder::build(self.tcx(), expected);
let expected = self
.infcx
.cx
.extract_inference_diagnostics_data(expected.into(), Some(expected_highlight))
.name;
let found_highlight = HighlightBuilder::build(self.tcx(), found);
let found =
self.infcx.extract_inference_diagnostics_data(found.into(), Some(found_highlight)).name;
self.cx.extract_inference_diagnostics_data(found.into(), Some(found_highlight)).name;
err.span_label(sp, &format!("found `{}`", found));
err.span_label(trait_sp, &format!("expected `{}`", expected));

View file

@ -130,7 +130,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
let ret_ty = fn_ty.fn_sig(self.tcx()).output();
let span = hir_sig.decl.output.span();
let future_output = if hir_sig.header.is_async() {
ret_ty.map_bound(|ty| self.infcx.get_impl_future_output_ty(ty)).transpose()
ret_ty.map_bound(|ty| self.cx.get_impl_future_output_ty(ty)).transpose()
} else {
None
};

View file

@ -1,6 +1,6 @@
use crate::errors::RegionOriginNote;
use crate::infer::error_reporting::note_and_explain_region;
use crate::infer::{self, InferCtxt, SubregionOrigin};
use crate::infer::error_reporting::{note_and_explain_region, TypeErrCtxt};
use crate::infer::{self, SubregionOrigin};
use rustc_errors::{
fluent, struct_span_err, AddToDiagnostic, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
};
@ -10,7 +10,7 @@ use rustc_middle::ty::{self, Region};
use super::ObligationCauseAsDiagArg;
impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
pub(super) fn note_region_origin(&self, err: &mut Diagnostic, origin: &SubregionOrigin<'tcx>) {
match *origin {
infer::Subtype(ref trace) => RegionOriginNote::WithRequirement {

View file

@ -40,6 +40,7 @@ use std::cell::{Cell, Ref, RefCell};
use std::fmt;
use self::combine::CombineFields;
use self::error_reporting::TypeErrCtxt;
use self::free_regions::RegionRelations;
use self::lexical_region_resolve::LexicalRegionResolutions;
use self::outlives::env::OutlivesEnvironment;
@ -701,6 +702,12 @@ pub struct CombinedSnapshot<'a, 'tcx> {
}
impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
/// Creates a `TypeErrCtxt` for emitting various inference errors.
/// During typeck, use `FnCtxt::infer_err` instead.
pub fn err_ctxt(&'a self) -> TypeErrCtxt<'a, 'tcx> {
TypeErrCtxt { infcx: self, typeck_results: None }
}
/// calls `tcx.try_unify_abstract_consts` after
/// canonicalizing the consts.
#[instrument(skip(self), level = "debug")]
@ -1343,32 +1350,6 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
errors
}
/// Process the region constraints and report any errors that
/// result. After this, no more unification operations should be
/// done -- or the compiler will panic -- but it is legal to use
/// `resolve_vars_if_possible` as well as `fully_resolve`.
///
/// Make sure to call [`InferCtxt::process_registered_region_obligations`]
/// first, or preferably use [`InferCtxt::check_region_obligations_and_report_errors`]
/// to do both of these operations together.
pub fn resolve_regions_and_report_errors(
&self,
generic_param_scope: LocalDefId,
outlives_env: &OutlivesEnvironment<'tcx>,
) {
let errors = self.resolve_regions(outlives_env);
if !self.is_tainted_by_errors() {
// As a heuristic, just skip reporting region errors
// altogether if other errors have been reported while
// this infcx was in use. This is totally hokey but
// otherwise we have a hard time separating legit region
// errors from silly ones.
self.report_region_errors(generic_param_scope, &errors);
}
}
/// Obtains (and clears) the current set of region
/// constraints. The inference context is still usable: further
/// unifications will simply add new constraints.
@ -1524,59 +1505,6 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
resolve::fully_resolve(self, value)
}
// [Note-Type-error-reporting]
// An invariant is that anytime the expected or actual type is Error (the special
// error type, meaning that an error occurred when typechecking this expression),
// this is a derived error. The error cascaded from another error (that was already
// reported), so it's not useful to display it to the user.
// The following methods implement this logic.
// They check if either the actual or expected type is Error, and don't print the error
// in this case. The typechecker should only ever report type errors involving mismatched
// types using one of these methods, and should not call span_err directly for such
// errors.
pub fn type_error_struct_with_diag<M>(
&self,
sp: Span,
mk_diag: M,
actual_ty: Ty<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>
where
M: FnOnce(String) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>,
{
let actual_ty = self.resolve_vars_if_possible(actual_ty);
debug!("type_error_struct_with_diag({:?}, {:?})", sp, actual_ty);
let mut err = mk_diag(self.ty_to_string(actual_ty));
// Don't report an error if actual type is `Error`.
if actual_ty.references_error() {
err.downgrade_to_delayed_bug();
}
err
}
pub fn report_mismatched_types(
&self,
cause: &ObligationCause<'tcx>,
expected: Ty<'tcx>,
actual: Ty<'tcx>,
err: TypeError<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
self.report_and_explain_type_error(TypeTrace::types(cause, true, expected, actual), err)
}
pub fn report_mismatched_consts(
&self,
cause: &ObligationCause<'tcx>,
expected: ty::Const<'tcx>,
actual: ty::Const<'tcx>,
err: TypeError<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
self.report_and_explain_type_error(TypeTrace::consts(cause, true, expected, actual), err)
}
pub fn replace_bound_vars_with_fresh_vars<T>(
&self,
span: Span,
@ -1816,6 +1744,86 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
}
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
/// Process the region constraints and report any errors that
/// result. After this, no more unification operations should be
/// done -- or the compiler will panic -- but it is legal to use
/// `resolve_vars_if_possible` as well as `fully_resolve`.
///
/// Make sure to call [`InferCtxt::process_registered_region_obligations`]
/// first, or preferably use [`InferCtxt::check_region_obligations_and_report_errors`]
/// to do both of these operations together.
pub fn resolve_regions_and_report_errors(
&self,
generic_param_scope: LocalDefId,
outlives_env: &OutlivesEnvironment<'tcx>,
) {
let errors = self.resolve_regions(outlives_env);
if !self.is_tainted_by_errors() {
// As a heuristic, just skip reporting region errors
// altogether if other errors have been reported while
// this infcx was in use. This is totally hokey but
// otherwise we have a hard time separating legit region
// errors from silly ones.
self.report_region_errors(generic_param_scope, &errors);
}
}
// [Note-Type-error-reporting]
// An invariant is that anytime the expected or actual type is Error (the special
// error type, meaning that an error occurred when typechecking this expression),
// this is a derived error. The error cascaded from another error (that was already
// reported), so it's not useful to display it to the user.
// The following methods implement this logic.
// They check if either the actual or expected type is Error, and don't print the error
// in this case. The typechecker should only ever report type errors involving mismatched
// types using one of these methods, and should not call span_err directly for such
// errors.
pub fn type_error_struct_with_diag<M>(
&self,
sp: Span,
mk_diag: M,
actual_ty: Ty<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>
where
M: FnOnce(String) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>,
{
let actual_ty = self.resolve_vars_if_possible(actual_ty);
debug!("type_error_struct_with_diag({:?}, {:?})", sp, actual_ty);
let mut err = mk_diag(self.ty_to_string(actual_ty));
// Don't report an error if actual type is `Error`.
if actual_ty.references_error() {
err.downgrade_to_delayed_bug();
}
err
}
pub fn report_mismatched_types(
&self,
cause: &ObligationCause<'tcx>,
expected: Ty<'tcx>,
actual: Ty<'tcx>,
err: TypeError<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
self.report_and_explain_type_error(TypeTrace::types(cause, true, expected, actual), err)
}
pub fn report_mismatched_consts(
&self,
cause: &ObligationCause<'tcx>,
expected: ty::Const<'tcx>,
actual: ty::Const<'tcx>,
err: TypeError<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
self.report_and_explain_type_error(TypeTrace::consts(cause, true, expected, actual), err)
}
}
/// Helper for `ty_or_const_infer_var_changed` (see comment on that), currently
/// used only for `traits::fulfill`'s list of `stalled_on` inference variables.
#[derive(Copy, Clone, Debug)]

View file

@ -183,7 +183,7 @@ impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
outlives_env.param_env,
);
self.resolve_regions_and_report_errors(generic_param_scope, outlives_env)
self.err_ctxt().resolve_regions_and_report_errors(generic_param_scope, outlives_env)
}
}

View file

@ -12,8 +12,8 @@ use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt};
use std::mem;
/// Ensures `a` is made a subtype of `b`. Returns `a` on success.
pub struct Sub<'combine, 'infcx, 'tcx> {
fields: &'combine mut CombineFields<'infcx, 'tcx>,
pub struct Sub<'combine, 'a, 'tcx> {
fields: &'combine mut CombineFields<'a, 'tcx>,
a_is_expected: bool,
}