feat(execution-engine)!: this patch prohibits error code = 0 (#702)

This commit is contained in:
raftedproc 2023-09-21 17:33:57 +03:00 committed by GitHub
parent b713e447fc
commit 45035ccff5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 591 additions and 284 deletions

View File

@ -17,7 +17,7 @@
use super::ErrorAffectable; use super::ErrorAffectable;
use super::Joinable; use super::Joinable;
use crate::execution_step::execution_context::errors::StreamMapError; use crate::execution_step::execution_context::errors::StreamMapError;
use crate::execution_step::execution_context::LastErrorObjectError; use crate::execution_step::execution_context::ErrorObjectError;
use crate::execution_step::lambda_applier::LambdaError; use crate::execution_step::lambda_applier::LambdaError;
use crate::JValue; use crate::JValue;
use crate::ToErrorCode; use crate::ToErrorCode;
@ -77,7 +77,7 @@ pub enum CatchableError {
/// This error type is produced by a fail instruction that tries to throw a scalar that have inappropriate type. /// This error type is produced by a fail instruction that tries to throw a scalar that have inappropriate type.
#[error(transparent)] #[error(transparent)]
InvalidLastErrorObjectError(#[from] LastErrorObjectError), InvalidErrorObjectError(#[from] ErrorObjectError),
/// A new with this variable name was met and right after that it was accessed /// A new with this variable name was met and right after that it was accessed
/// that is prohibited. /// that is prohibited.

View File

@ -20,7 +20,7 @@ use thiserror::Error as ThisError;
/// Describes errors related to converting a scalar into error object. /// Describes errors related to converting a scalar into error object.
#[derive(Debug, Clone, ThisError)] #[derive(Debug, Clone, ThisError)]
pub enum LastErrorObjectError { pub enum ErrorObjectError {
#[error("scalar should have an object type to be converted into error object, but '{0}' doesn't have")] #[error("scalar should have an object type to be converted into error object, but '{0}' doesn't have")]
ScalarMustBeObject(JValue), ScalarMustBeObject(JValue),
@ -33,4 +33,7 @@ pub enum LastErrorObjectError {
field_name: &'static str, field_name: &'static str,
expected_type: &'static str, expected_type: &'static str,
}, },
#[error("error code must be non-zero, but it is zero")]
ErrorCodeMustBeNonZero,
} }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use super::LastErrorObjectError; use super::ErrorObjectError;
use crate::execution_step::RcSecurityTetraplet; use crate::execution_step::RcSecurityTetraplet;
use crate::JValue; use crate::JValue;
@ -72,25 +72,25 @@ pub(crate) fn error_from_raw_fields(error_code: i64, error_message: &str, instru
} }
/// Checks that a scalar is a value of an object types that contains at least two fields: /// Checks that a scalar is a value of an object types that contains at least two fields:
/// - error_code /// - error_code != 0
/// - message /// - message
pub(crate) fn check_error_object(scalar: &JValue) -> Result<(), LastErrorObjectError> { pub(crate) fn check_error_object(scalar: &JValue) -> Result<(), ErrorObjectError> {
let fields = match scalar { let fields = match scalar {
JValue::Object(fields) => fields, JValue::Object(fields) => fields,
_ => return Err(LastErrorObjectError::ScalarMustBeObject(scalar.clone())), _ => return Err(ErrorObjectError::ScalarMustBeObject(scalar.clone())),
}; };
let check_field = |field_name| { let check_field = |field_name| {
fields fields
.get(field_name) .get(field_name)
.ok_or_else(|| LastErrorObjectError::ScalarMustContainField { .ok_or_else(|| ErrorObjectError::ScalarMustContainField {
scalar: scalar.clone(), scalar: scalar.clone(),
field_name, field_name,
}) })
}; };
let error_code = check_field(ERROR_CODE_FIELD_NAME)?; let error_code = check_field(ERROR_CODE_FIELD_NAME)?;
ensure_jvalue_is_integer(scalar, error_code, ERROR_CODE_FIELD_NAME)?; ensure_error_code_correct(scalar, error_code, ERROR_CODE_FIELD_NAME)?;
let message = check_field(MESSAGE_FIELD_NAME)?; let message = check_field(MESSAGE_FIELD_NAME)?;
ensure_jvalue_is_string(scalar, message, MESSAGE_FIELD_NAME)?; ensure_jvalue_is_string(scalar, message, MESSAGE_FIELD_NAME)?;
@ -98,14 +98,16 @@ pub(crate) fn check_error_object(scalar: &JValue) -> Result<(), LastErrorObjectE
Ok(()) Ok(())
} }
fn ensure_jvalue_is_integer( fn ensure_error_code_correct(
scalar: &JValue, scalar: &JValue,
value: &JValue, value: &JValue,
field_name: &'static str, field_name: &'static str,
) -> Result<(), LastErrorObjectError> { ) -> Result<(), ErrorObjectError> {
match value { match value {
JValue::Number(number) if number.is_i64() || number.is_u64() => Ok(()), JValue::Number(number) if number.is_i64() | number.is_u64() => {
_ => Err(LastErrorObjectError::ScalarFieldIsWrongType { ensure_error_code_is_error(number.as_i64().unwrap())
}
_ => Err(ErrorObjectError::ScalarFieldIsWrongType {
scalar: scalar.clone(), scalar: scalar.clone(),
field_name, field_name,
expected_type: "integer", expected_type: "integer",
@ -113,14 +115,18 @@ fn ensure_jvalue_is_integer(
} }
} }
fn ensure_jvalue_is_string( fn ensure_error_code_is_error(number: i64) -> Result<(), ErrorObjectError> {
scalar: &JValue, if number == NO_ERROR_ERROR_CODE {
value: &JValue, Err(ErrorObjectError::ErrorCodeMustBeNonZero)
field_name: &'static str, } else {
) -> Result<(), LastErrorObjectError> { Ok(())
}
}
fn ensure_jvalue_is_string(scalar: &JValue, value: &JValue, field_name: &'static str) -> Result<(), ErrorObjectError> {
match value { match value {
JValue::String(_) => Ok(()), JValue::String(_) => Ok(()),
_ => Err(LastErrorObjectError::ScalarFieldIsWrongType { _ => Err(ErrorObjectError::ScalarFieldIsWrongType {
scalar: scalar.clone(), scalar: scalar.clone(),
field_name, field_name,
expected_type: "string", expected_type: "string",

View File

@ -20,7 +20,7 @@ mod errors_utils;
mod instruction_error_definition; mod instruction_error_definition;
mod last_error_descriptor; mod last_error_descriptor;
pub use errors::LastErrorObjectError; pub use errors::ErrorObjectError;
pub use instruction_error_definition::no_error; pub use instruction_error_definition::no_error;
pub use instruction_error_definition::no_error_object; pub use instruction_error_definition::no_error_object;
pub use instruction_error_definition::InstructionError; pub use instruction_error_definition::InstructionError;

View File

@ -55,7 +55,7 @@ fn fail_with_scalar<'i>(scalar: &ast::Scalar<'i>, exec_ctx: &mut ExecutionCtx<'i
let (value, mut tetraplet, provenance) = scalar.resolve(exec_ctx)?; let (value, mut tetraplet, provenance) = scalar.resolve(exec_ctx)?;
// tetraplets always have one element here and it'll be refactored after boxed value // tetraplets always have one element here and it'll be refactored after boxed value
let tetraplet = tetraplet.remove(0); let tetraplet = tetraplet.remove(0);
check_error_object(&value).map_err(CatchableError::InvalidLastErrorObjectError)?; check_error_object(&value).map_err(CatchableError::InvalidErrorObjectError)?;
fail_with_error_object(exec_ctx, Rc::new(value), Some(tetraplet), provenance) fail_with_error_object(exec_ctx, Rc::new(value), Some(tetraplet), provenance)
} }
@ -64,7 +64,7 @@ fn fail_with_scalar_wl<'i>(scalar: &ast::ScalarWithLambda<'i>, exec_ctx: &mut Ex
let (value, mut tetraplet, provenance) = scalar.resolve(exec_ctx)?; let (value, mut tetraplet, provenance) = scalar.resolve(exec_ctx)?;
// tetraplets always have one element here and it'll be refactored after boxed value // tetraplets always have one element here and it'll be refactored after boxed value
let tetraplet = tetraplet.remove(0); let tetraplet = tetraplet.remove(0);
check_error_object(&value).map_err(CatchableError::InvalidLastErrorObjectError)?; check_error_object(&value).map_err(CatchableError::InvalidErrorObjectError)?;
fail_with_error_object(exec_ctx, Rc::new(value), Some(tetraplet), provenance) fail_with_error_object(exec_ctx, Rc::new(value), Some(tetraplet), provenance)
} }
@ -97,7 +97,7 @@ fn fail_with_canon_stream(
let (value, mut tetraplets, provenance) = ast_canon.resolve(exec_ctx)?; let (value, mut tetraplets, provenance) = ast_canon.resolve(exec_ctx)?;
// tetraplets always have one element here and it'll be refactored after boxed value // tetraplets always have one element here and it'll be refactored after boxed value
check_error_object(&value).map_err(CatchableError::InvalidLastErrorObjectError)?; check_error_object(&value).map_err(CatchableError::InvalidErrorObjectError)?;
fail_with_error_object(exec_ctx, Rc::new(value), Some(tetraplets.remove(0)), provenance) fail_with_error_object(exec_ctx, Rc::new(value), Some(tetraplets.remove(0)), provenance)
} }
@ -111,6 +111,8 @@ fn fail_with_last_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()>
provenance, provenance,
} = exec_ctx.last_error_descriptor.error(); } = exec_ctx.last_error_descriptor.error();
check_error_object(error).map_err(CatchableError::InvalidErrorObjectError)?;
// to avoid warnings from https://github.com/rust-lang/rust/issues/59159 // to avoid warnings from https://github.com/rust-lang/rust/issues/59159
let error = error.clone(); let error = error.clone();
let tetraplet = tetraplet.clone(); let tetraplet = tetraplet.clone();
@ -127,6 +129,8 @@ fn fail_with_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> {
provenance, provenance,
} = exec_ctx.error_descriptor.error(); } = exec_ctx.error_descriptor.error();
check_error_object(error).map_err(CatchableError::InvalidErrorObjectError)?;
fail_with_error_object(exec_ctx, error.clone(), tetraplet.clone(), provenance.clone()) fail_with_error_object(exec_ctx, error.clone(), tetraplet.clone(), provenance.clone())
} }

View File

@ -29,7 +29,7 @@ const TETRAPLET_IDX_CORRECT: &str = "selects always return a correct index insid
pub use errors::CatchableError; pub use errors::CatchableError;
pub use errors::ExecutionError; pub use errors::ExecutionError;
pub use errors::UncatchableError; pub use errors::UncatchableError;
pub use execution_context::LastErrorObjectError; pub use execution_context::ErrorObjectError;
pub use lambda_applier::LambdaError; pub use lambda_applier::LambdaError;
pub mod errors_prelude { pub mod errors_prelude {

View File

@ -46,9 +46,9 @@ pub use execution_step::execution_context::InstructionError;
pub use execution_step::execution_context::NO_ERROR_ERROR_CODE; pub use execution_step::execution_context::NO_ERROR_ERROR_CODE;
pub use execution_step::execution_context::NO_ERROR_MESSAGE; pub use execution_step::execution_context::NO_ERROR_MESSAGE;
pub use execution_step::CatchableError; pub use execution_step::CatchableError;
pub use execution_step::ErrorObjectError;
pub use execution_step::ExecutionError; pub use execution_step::ExecutionError;
pub use execution_step::LambdaError; pub use execution_step::LambdaError;
pub use execution_step::LastErrorObjectError;
pub use execution_step::UncatchableError; pub use execution_step::UncatchableError;
pub use farewell_step::FarewellError; pub use farewell_step::FarewellError;
pub use polyplets::ResolvedTriplet; pub use polyplets::ResolvedTriplet;

View File

@ -0,0 +1,73 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use air::no_error_object;
use air::ExecutionCidState;
use air_test_framework::AirScriptExecutor;
use air_test_utils::prelude::*;
#[test]
fn fail_with_rebubble_error() {
let peer_id = "peer_id";
let script = r#"
(seq
(xor
(xor
(match 1 2 (null) )
(fail :error:)
)
(call "peer_id" ("m" "f1") [:error:] scalar1) ; behaviour = echo
)
(call "peer_id" ("m" "f2") [:error:] scalar2) ; behaviour = echo
)
"#
.to_string();
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(peer_id), &script)
.expect("invalid test AIR script");
let result = executor.execute_all(peer_id).unwrap();
let actual_trace = trace_from_result(&result.last().unwrap());
let mut cid_tracker: ExecutionCidState = ExecutionCidState::new();
let expected_error_json = {
json!({
"error_code": 10006,
"instruction": "xor",
"message": "fail with '{\"error_code\":10001,\"instruction\":\"match 1 2\",\"message\":\"compared values do not match\"}' is used without corresponding xor"
})
};
let expected_trace: Vec<ExecutedState> = vec![
scalar_tracked!(
expected_error_json.clone(),
cid_tracker,
peer = peer_id,
service = "m..0",
function = "f1",
args = [expected_error_json]
),
scalar_tracked!(
no_error_object(),
cid_tracker,
peer = peer_id,
service = "m..1",
function = "f2",
args = [no_error_object()]
),
];
assert_eq!(actual_trace, expected_trace,);
}

View File

@ -16,10 +16,10 @@
use air::no_error_object; use air::no_error_object;
use air::CatchableError; use air::CatchableError;
use air::ErrorObjectError;
use air::ExecutionCidState; use air::ExecutionCidState;
use air::ExecutionError; use air::ExecutionError;
use air::LambdaError; use air::LambdaError;
use air::LastErrorObjectError;
use air::SecurityTetraplet; use air::SecurityTetraplet;
use air::NO_ERROR_ERROR_CODE; use air::NO_ERROR_ERROR_CODE;
use air::NO_ERROR_MESSAGE; use air::NO_ERROR_MESSAGE;
@ -422,7 +422,7 @@ fn fail_with_scalar_from_call_not_enough_fields() {
let result = call_vm!(vm, <_>::default(), &script, "", ""); let result = call_vm!(vm, <_>::default(), &script, "", "");
let expected_error = CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarMustContainField { let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarMustContainField {
scalar: service_result, scalar: service_result,
field_name: "message", field_name: "message",
}); });
@ -446,8 +446,7 @@ fn fail_with_scalar_from_call_not_right_type() {
let result = call_vm!(vm, <_>::default(), &script, "", ""); let result = call_vm!(vm, <_>::default(), &script, "", "");
let expected_error = let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarMustBeObject(service_result));
CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarMustBeObject(service_result));
assert!(check_error(&result, expected_error)); assert!(check_error(&result, expected_error));
} }
@ -468,7 +467,7 @@ fn fail_with_scalar_from_call_field_not_right_type() {
let result = call_vm!(vm, <_>::default(), &script, "", ""); let result = call_vm!(vm, <_>::default(), &script, "", "");
let expected_error = CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarFieldIsWrongType { let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarFieldIsWrongType {
scalar: service_result, scalar: service_result,
field_name: "error_code", field_name: "error_code",
expected_type: "integer", expected_type: "integer",

View File

@ -14,5 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
mod error;
mod invalid_air; mod invalid_air;
mod last_error; mod last_error;

View File

@ -15,6 +15,9 @@
*/ */
use air::CatchableError; use air::CatchableError;
use air::ErrorObjectError;
use air::ExecutionError;
use air_test_framework::AirScriptExecutor;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
#[test] #[test]
@ -209,3 +212,58 @@ fn fail_with_canon_stream() {
}; };
assert!(check_error(&result, expected_error)); assert!(check_error(&result, expected_error));
} }
fn fail_to_fail_with_unsupported_errorcode(script: &str) {
let local_peer_id = "local_peer_id";
let script = script.to_string();
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(local_peer_id), &script)
.expect("invalid test AIR script");
let results = executor.execute_all(local_peer_id).unwrap();
let expected_error = ExecutionError::Catchable(rc!(CatchableError::InvalidErrorObjectError(
ErrorObjectError::ErrorCodeMustBeNonZero
)));
assert!(check_error(&results.last().unwrap(), expected_error));
}
#[test]
fn fail_to_fail_with_unsupported_errorcode_in_scalar() {
let script = r#"
(seq
(call "local_peer_id" ("m" "f1") [] scalar) ; ok = {"error_code": 0, "message": "some message"}
(fail scalar)
)
"#;
fail_to_fail_with_unsupported_errorcode(script);
}
#[test]
fn fail_to_fail_with_unsupported_errorcode_in_scalar_wl() {
let script = r#"
(seq
(call "local_peer_id" ("m" "f1") [] scalar) ; ok = {"key": {"error_code": 0, "message": "some message"} }
(fail scalar.$.key)
)
"#;
fail_to_fail_with_unsupported_errorcode(script);
}
#[test]
fn fail_to_fail_with_unsupported_errorcode_in_canon() {
let script = r#"
(seq
(call "local_peer_id" ("m" "f1") [] scalar) ; ok = [{"error_code": 0, "message": "some message"}]
(fail scalar.$.[0])
)
"#;
fail_to_fail_with_unsupported_errorcode(script);
}
#[test]
fn fail_to_fail_with_unsupported_errorcode_in_error() {
let script = r#"
(fail :error:)
"#;
fail_to_fail_with_unsupported_errorcode(script);
}

View File

@ -86,7 +86,10 @@ Instr: Box<Instruction<'input>> = {
Box::new(Instruction::New(new)) Box::new(Instruction::New(new))
}, },
"(" fail <fail_body: FailBody> ")" => { <left: @L> "(" fail <fail_body: FailBody> ")" <right: @R> => {
let span = Span::new(left, right);
validator.met_fail_literal(&fail_body, span);
Box::new(Instruction::Fail(fail_body)) Box::new(Instruction::Fail(fail_body))
}, },

File diff suppressed because it is too large Load Diff

View File

@ -53,6 +53,9 @@ pub enum ParserError {
ap_key_type: String, ap_key_type: String,
ap_result_name: String, ap_result_name: String,
}, },
#[error("error code 0 with fail is unsupported")]
UnsupportedLiteralErrCodes { span: Span },
} }
impl ParserError { impl ParserError {
@ -67,6 +70,7 @@ impl ParserError {
Self::MultipleIterableValuesForOneIterator { span, .. } => *span, Self::MultipleIterableValuesForOneIterator { span, .. } => *span,
Self::MultipleNextInFold { span, .. } => *span, Self::MultipleNextInFold { span, .. } => *span,
Self::UnsupportedMapKeyType { span, .. } => *span, Self::UnsupportedMapKeyType { span, .. } => *span,
Self::UnsupportedLiteralErrCodes { span } => *span,
} }
} }
@ -116,6 +120,10 @@ impl ParserError {
ap_result_name: ap_result_name.into(), ap_result_name: ap_result_name.into(),
} }
} }
pub fn unsupported_literal_errcodes(span: Span) -> Self {
Self::UnsupportedLiteralErrCodes { span }
}
} }
impl From<std::convert::Infallible> for ParserError { impl From<std::convert::Infallible> for ParserError {

View File

@ -92,6 +92,10 @@ pub(super) fn fail_last_error() -> Instruction<'static> {
Instruction::Fail(Fail::LastError) Instruction::Fail(Fail::LastError)
} }
pub(super) fn fail_error() -> Instruction<'static> {
Instruction::Fail(Fail::Error)
}
pub(super) fn fold_scalar_variable<'i>( pub(super) fn fold_scalar_variable<'i>(
scalar: Scalar<'i>, scalar: Scalar<'i>,
iterator: Scalar<'i>, iterator: Scalar<'i>,

View File

@ -68,3 +68,47 @@ fn parse_fail_scalar_with_lambda() {
)); ));
assert_eq!(instruction, expected) assert_eq!(instruction, expected)
} }
#[test]
fn parse_fail_scalar_with_error() {
let source_code = r#"
(fail :error:)
"#;
let instruction = parse(source_code);
let expected = fail_error();
assert_eq!(instruction, expected)
}
#[test]
fn parse_fail_literal_0() {
use crate::parser::errors::ParserError;
use lalrpop_util::ParseError;
let source_code = r#"
(fail 0 "some error")
"#;
let lexer = crate::AIRLexer::new(source_code);
let parser = crate::AIRParser::new();
let mut errors = Vec::new();
let mut validator = crate::parser::VariableValidator::new();
parser
.parse(source_code, &mut errors, &mut validator, lexer)
.expect("parser shouldn't fail");
let errors = validator.finalize();
assert_eq!(errors.len(), 1);
let error = &errors[0].error;
let parser_error = match error {
ParseError::User { error } => error,
_ => panic!("unexpected error type"),
};
assert!(matches!(
parser_error,
ParserError::UnsupportedLiteralErrCodes { .. }
));
}

View File

@ -59,9 +59,12 @@ pub struct VariableValidator<'i> {
/// Contains all names that should be checked that they are not iterators. /// Contains all names that should be checked that they are not iterators.
not_iterators_candidates: Vec<(&'i str, Span)>, not_iterators_candidates: Vec<(&'i str, Span)>,
// This contains info about unssuported map key arguments used with ap instruction, /// This contains info about unssuported map key arguments used with ap instruction,
// namely (key map ApArgument) /// namely (key map ApArgument).
unsupported_map_keys: Vec<(String, &'i str, Span)>, unsupported_map_keys: Vec<(String, &'i str, Span)>,
/// This vector contains all literal error codes used with fail.
unsupported_literal_errcodes: Vec<(i64, Span)>,
} }
impl<'i> VariableValidator<'i> { impl<'i> VariableValidator<'i> {
@ -197,6 +200,15 @@ impl<'i> VariableValidator<'i> {
} }
} }
pub(super) fn met_fail_literal(&mut self, fail: &Fail<'i>, span: Span) {
match fail {
Fail::Literal { ret_code, .. } if *ret_code == 0 => {
self.unsupported_literal_errcodes.push((*ret_code, span))
}
_ => {}
}
}
pub(super) fn finalize(self) -> Vec<ErrorRecovery<AirPos, Token<'i>, ParserError>> { pub(super) fn finalize(self) -> Vec<ErrorRecovery<AirPos, Token<'i>, ParserError>> {
ValidatorErrorBuilder::new(self) ValidatorErrorBuilder::new(self)
.check_undefined_variables() .check_undefined_variables()
@ -205,6 +217,7 @@ impl<'i> VariableValidator<'i> {
.check_new_on_iterators() .check_new_on_iterators()
.check_iterator_for_multiple_definitions() .check_iterator_for_multiple_definitions()
.check_for_unsupported_map_keys() .check_for_unsupported_map_keys()
.check_for_unsupported_literal_errcodes()
.build() .build()
} }
@ -496,6 +509,14 @@ impl<'i> ValidatorErrorBuilder<'i> {
self self
} }
fn check_for_unsupported_literal_errcodes(mut self) -> Self {
for (_, span) in self.validator.unsupported_literal_errcodes.iter_mut() {
let error = ParserError::unsupported_literal_errcodes(*span);
add_to_errors(&mut self.errors, *span, Token::New, error);
}
self
}
fn build(self) -> Vec<ErrorRecovery<AirPos, Token<'i>, ParserError>> { fn build(self) -> Vec<ErrorRecovery<AirPos, Token<'i>, ParserError>> {
self.errors self.errors
} }