mirror of
https://github.com/fluencelabs/aquavm
synced 2025-03-15 04:30:48 +00:00
feat(execution-engine)!: this patch prohibits error code = 0 (#702)
This commit is contained in:
parent
b713e447fc
commit
45035ccff5
@ -17,7 +17,7 @@
|
||||
use super::ErrorAffectable;
|
||||
use super::Joinable;
|
||||
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::JValue;
|
||||
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.
|
||||
#[error(transparent)]
|
||||
InvalidLastErrorObjectError(#[from] LastErrorObjectError),
|
||||
InvalidErrorObjectError(#[from] ErrorObjectError),
|
||||
|
||||
/// A new with this variable name was met and right after that it was accessed
|
||||
/// that is prohibited.
|
||||
|
@ -20,7 +20,7 @@ use thiserror::Error as ThisError;
|
||||
|
||||
/// Describes errors related to converting a scalar into error object.
|
||||
#[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")]
|
||||
ScalarMustBeObject(JValue),
|
||||
|
||||
@ -33,4 +33,7 @@ pub enum LastErrorObjectError {
|
||||
field_name: &'static str,
|
||||
expected_type: &'static str,
|
||||
},
|
||||
|
||||
#[error("error code must be non-zero, but it is zero")]
|
||||
ErrorCodeMustBeNonZero,
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use super::LastErrorObjectError;
|
||||
use super::ErrorObjectError;
|
||||
use crate::execution_step::RcSecurityTetraplet;
|
||||
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:
|
||||
/// - error_code
|
||||
/// - error_code != 0
|
||||
/// - 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 {
|
||||
JValue::Object(fields) => fields,
|
||||
_ => return Err(LastErrorObjectError::ScalarMustBeObject(scalar.clone())),
|
||||
_ => return Err(ErrorObjectError::ScalarMustBeObject(scalar.clone())),
|
||||
};
|
||||
|
||||
let check_field = |field_name| {
|
||||
fields
|
||||
.get(field_name)
|
||||
.ok_or_else(|| LastErrorObjectError::ScalarMustContainField {
|
||||
.ok_or_else(|| ErrorObjectError::ScalarMustContainField {
|
||||
scalar: scalar.clone(),
|
||||
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)?;
|
||||
ensure_jvalue_is_string(scalar, message, MESSAGE_FIELD_NAME)?;
|
||||
@ -98,14 +98,16 @@ pub(crate) fn check_error_object(scalar: &JValue) -> Result<(), LastErrorObjectE
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_jvalue_is_integer(
|
||||
fn ensure_error_code_correct(
|
||||
scalar: &JValue,
|
||||
value: &JValue,
|
||||
field_name: &'static str,
|
||||
) -> Result<(), LastErrorObjectError> {
|
||||
) -> Result<(), ErrorObjectError> {
|
||||
match value {
|
||||
JValue::Number(number) if number.is_i64() || number.is_u64() => Ok(()),
|
||||
_ => Err(LastErrorObjectError::ScalarFieldIsWrongType {
|
||||
JValue::Number(number) if number.is_i64() | number.is_u64() => {
|
||||
ensure_error_code_is_error(number.as_i64().unwrap())
|
||||
}
|
||||
_ => Err(ErrorObjectError::ScalarFieldIsWrongType {
|
||||
scalar: scalar.clone(),
|
||||
field_name,
|
||||
expected_type: "integer",
|
||||
@ -113,14 +115,18 @@ fn ensure_jvalue_is_integer(
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_jvalue_is_string(
|
||||
scalar: &JValue,
|
||||
value: &JValue,
|
||||
field_name: &'static str,
|
||||
) -> Result<(), LastErrorObjectError> {
|
||||
fn ensure_error_code_is_error(number: i64) -> Result<(), ErrorObjectError> {
|
||||
if number == NO_ERROR_ERROR_CODE {
|
||||
Err(ErrorObjectError::ErrorCodeMustBeNonZero)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_jvalue_is_string(scalar: &JValue, value: &JValue, field_name: &'static str) -> Result<(), ErrorObjectError> {
|
||||
match value {
|
||||
JValue::String(_) => Ok(()),
|
||||
_ => Err(LastErrorObjectError::ScalarFieldIsWrongType {
|
||||
_ => Err(ErrorObjectError::ScalarFieldIsWrongType {
|
||||
scalar: scalar.clone(),
|
||||
field_name,
|
||||
expected_type: "string",
|
||||
|
@ -20,7 +20,7 @@ mod errors_utils;
|
||||
mod instruction_error_definition;
|
||||
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_object;
|
||||
pub use instruction_error_definition::InstructionError;
|
||||
|
@ -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)?;
|
||||
// tetraplets always have one element here and it'll be refactored after boxed value
|
||||
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)
|
||||
}
|
||||
@ -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)?;
|
||||
// tetraplets always have one element here and it'll be refactored after boxed value
|
||||
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)
|
||||
}
|
||||
@ -97,7 +97,7 @@ fn fail_with_canon_stream(
|
||||
let (value, mut tetraplets, provenance) = ast_canon.resolve(exec_ctx)?;
|
||||
|
||||
// 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)
|
||||
}
|
||||
@ -111,6 +111,8 @@ fn fail_with_last_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()>
|
||||
provenance,
|
||||
} = 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
|
||||
let error = error.clone();
|
||||
let tetraplet = tetraplet.clone();
|
||||
@ -127,6 +129,8 @@ fn fail_with_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> {
|
||||
provenance,
|
||||
} = 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())
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ const TETRAPLET_IDX_CORRECT: &str = "selects always return a correct index insid
|
||||
pub use errors::CatchableError;
|
||||
pub use errors::ExecutionError;
|
||||
pub use errors::UncatchableError;
|
||||
pub use execution_context::LastErrorObjectError;
|
||||
pub use execution_context::ErrorObjectError;
|
||||
pub use lambda_applier::LambdaError;
|
||||
|
||||
pub mod errors_prelude {
|
||||
|
@ -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_MESSAGE;
|
||||
pub use execution_step::CatchableError;
|
||||
pub use execution_step::ErrorObjectError;
|
||||
pub use execution_step::ExecutionError;
|
||||
pub use execution_step::LambdaError;
|
||||
pub use execution_step::LastErrorObjectError;
|
||||
pub use execution_step::UncatchableError;
|
||||
pub use farewell_step::FarewellError;
|
||||
pub use polyplets::ResolvedTriplet;
|
||||
|
73
air/tests/test_module/features/errors/error.rs
Normal file
73
air/tests/test_module/features/errors/error.rs
Normal 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,);
|
||||
}
|
@ -16,10 +16,10 @@
|
||||
|
||||
use air::no_error_object;
|
||||
use air::CatchableError;
|
||||
use air::ErrorObjectError;
|
||||
use air::ExecutionCidState;
|
||||
use air::ExecutionError;
|
||||
use air::LambdaError;
|
||||
use air::LastErrorObjectError;
|
||||
use air::SecurityTetraplet;
|
||||
use air::NO_ERROR_ERROR_CODE;
|
||||
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 expected_error = CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarMustContainField {
|
||||
let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarMustContainField {
|
||||
scalar: service_result,
|
||||
field_name: "message",
|
||||
});
|
||||
@ -446,8 +446,7 @@ fn fail_with_scalar_from_call_not_right_type() {
|
||||
|
||||
let result = call_vm!(vm, <_>::default(), &script, "", "");
|
||||
|
||||
let expected_error =
|
||||
CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarMustBeObject(service_result));
|
||||
let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarMustBeObject(service_result));
|
||||
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 expected_error = CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarFieldIsWrongType {
|
||||
let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarFieldIsWrongType {
|
||||
scalar: service_result,
|
||||
field_name: "error_code",
|
||||
expected_type: "integer",
|
||||
|
@ -14,5 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
mod error;
|
||||
mod invalid_air;
|
||||
mod last_error;
|
||||
|
@ -15,6 +15,9 @@
|
||||
*/
|
||||
|
||||
use air::CatchableError;
|
||||
use air::ErrorObjectError;
|
||||
use air::ExecutionError;
|
||||
use air_test_framework::AirScriptExecutor;
|
||||
use air_test_utils::prelude::*;
|
||||
|
||||
#[test]
|
||||
@ -209,3 +212,58 @@ fn fail_with_canon_stream() {
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
@ -86,7 +86,10 @@ Instr: Box<Instruction<'input>> = {
|
||||
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))
|
||||
},
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -53,6 +53,9 @@ pub enum ParserError {
|
||||
ap_key_type: String,
|
||||
ap_result_name: String,
|
||||
},
|
||||
|
||||
#[error("error code 0 with fail is unsupported")]
|
||||
UnsupportedLiteralErrCodes { span: Span },
|
||||
}
|
||||
|
||||
impl ParserError {
|
||||
@ -67,6 +70,7 @@ impl ParserError {
|
||||
Self::MultipleIterableValuesForOneIterator { span, .. } => *span,
|
||||
Self::MultipleNextInFold { span, .. } => *span,
|
||||
Self::UnsupportedMapKeyType { span, .. } => *span,
|
||||
Self::UnsupportedLiteralErrCodes { span } => *span,
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,6 +120,10 @@ impl ParserError {
|
||||
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 {
|
||||
|
@ -92,6 +92,10 @@ pub(super) fn fail_last_error() -> Instruction<'static> {
|
||||
Instruction::Fail(Fail::LastError)
|
||||
}
|
||||
|
||||
pub(super) fn fail_error() -> Instruction<'static> {
|
||||
Instruction::Fail(Fail::Error)
|
||||
}
|
||||
|
||||
pub(super) fn fold_scalar_variable<'i>(
|
||||
scalar: Scalar<'i>,
|
||||
iterator: Scalar<'i>,
|
||||
|
@ -68,3 +68,47 @@ fn parse_fail_scalar_with_lambda() {
|
||||
));
|
||||
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 { .. }
|
||||
));
|
||||
}
|
||||
|
@ -59,9 +59,12 @@ pub struct VariableValidator<'i> {
|
||||
/// Contains all names that should be checked that they are not iterators.
|
||||
not_iterators_candidates: Vec<(&'i str, Span)>,
|
||||
|
||||
// This contains info about unssuported map key arguments used with ap instruction,
|
||||
// namely (key map ApArgument)
|
||||
/// This contains info about unssuported map key arguments used with ap instruction,
|
||||
/// namely (key map ApArgument).
|
||||
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> {
|
||||
@ -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>> {
|
||||
ValidatorErrorBuilder::new(self)
|
||||
.check_undefined_variables()
|
||||
@ -205,6 +217,7 @@ impl<'i> VariableValidator<'i> {
|
||||
.check_new_on_iterators()
|
||||
.check_iterator_for_multiple_definitions()
|
||||
.check_for_unsupported_map_keys()
|
||||
.check_for_unsupported_literal_errcodes()
|
||||
.build()
|
||||
}
|
||||
|
||||
@ -496,6 +509,14 @@ impl<'i> ValidatorErrorBuilder<'i> {
|
||||
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>> {
|
||||
self.errors
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user