diff --git a/trustfall_core/src/frontend/error.rs b/trustfall_core/src/frontend/error.rs index a4f78d97..94e4e333 100644 --- a/trustfall_core/src/frontend/error.rs +++ b/trustfall_core/src/frontend/error.rs @@ -1,9 +1,12 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, fmt::Write}; use serde::{Deserialize, Serialize}; use crate::{ - ir::{FieldValue, Type}, + ir::{ + Argument, FieldValue, FoldSpecificField, OperationSubject, TransformBase, TransformedField, + Type, + }, util::DisplayVec, }; @@ -16,11 +19,11 @@ pub enum FrontendError { #[error("{0}")] ParseError(#[from] crate::graphql_query::error::ParseError), - #[error("Filter on property name \"{0}\" uses undefined tag: %{1}")] + #[error("Filter on {0} uses undefined tag: %{1}")] UndefinedTagInFilter(String, String), #[error( - "Filter on property name \"{0}\" uses tag \"{1}\" which is not yet defined at that point \ + "Filter on {0} uses tag \"{1}\" which is not yet defined at that point \ in the query. Please reorder the query components so that the @tag directive \ comes before all uses of its tagged value." )] @@ -28,7 +31,7 @@ pub enum FrontendError { #[error( "Tag \"{1}\" is defined within a @fold but is used outside that @fold in a filter on \ - property name \"{0}\". This is not supported; if possible, please consider reorganizing \ + {0}. This is not supported; if possible, please consider reorganizing \ the query so that the tagged values are captured outside the @fold and \ their use in @filter moves inside the @fold." )] @@ -48,7 +51,7 @@ pub enum FrontendError { #[error( "Tagged fields with an applied @transform must explicitly specify the tag name, like this: \ - @tag(name: \"some_name\"). Affected field: {0}" + @tag(name: \"some_name\"). Affected location: {0}" )] ExplicitTagNameRequired(String), @@ -119,6 +122,52 @@ pub enum FrontendError { OtherError(String), } +impl FrontendError { + #[inline] + fn represent_subject(subject: &OperationSubject) -> String { + match subject { + OperationSubject::LocalField(field) => { + let property_name = &field.field_name; + format!("property \"{property_name}\"") + } + OperationSubject::TransformedField(field) => { + let mut buf = String::with_capacity(32); + write_name_of_transformed_field(&mut buf, field); + buf + } + OperationSubject::FoldSpecificField(field) => { + let field_name = field.kind.field_name(); + format!("transformed field \"{field_name}\"") + } + } + } + + pub(super) fn undefined_tag_in_filter( + subject: &OperationSubject, + tag_name: impl Into, + ) -> Self { + Self::UndefinedTagInFilter(Self::represent_subject(subject), tag_name.into()) + } + + pub(super) fn tag_used_before_definition( + subject: &OperationSubject, + tag_name: impl Into, + ) -> Self { + Self::TagUsedBeforeDefinition(Self::represent_subject(subject), tag_name.into()) + } + + pub(super) fn tag_used_outside_its_folded_subquery( + subject: &OperationSubject, + tag_name: impl Into, + ) -> Self { + Self::TagUsedOutsideItsFoldedSubquery(Self::represent_subject(subject), tag_name.into()) + } + + pub(super) fn explicit_tag_name_required(subject: &OperationSubject) -> Self { + Self::ExplicitTagNameRequired(Self::represent_subject(subject)) + } +} + #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, thiserror::Error)] pub enum FilterTypeError { @@ -178,108 +227,169 @@ pub enum FilterTypeError { } impl FilterTypeError { + #[inline] fn represent_property_and_type(property_name: &str, property_type: &Type) -> String { format!("property \"{property_name}\" of type \"{property_type}\"") } + #[inline] fn represent_tag_name_and_type(tag_name: &str, tag_type: &Type) -> String { format!("tag \"{tag_name}\" of type \"{tag_type}\"") } - pub(crate) fn non_nullable_property_with_nullability_filter( + #[inline] + fn represent_variable_name_and_type(var_name: &str, var_type: &Type) -> String { + format!("variable \"{var_name}\" of type \"{var_type}\"") + } + + #[inline] + fn represent_fold_specific_field(field: &FoldSpecificField) -> String { + let field_name = field.kind.field_name(); + let field_type = field.kind.field_type(); + format!("transformed field \"{field_name}\" of type \"{field_type}\"") + } + + #[inline] + fn represent_transformed_field(field: &TransformedField) -> String { + let mut buf = String::with_capacity(64); + + write_name_of_transformed_field(&mut buf, field); + + let field_type = &field.field_type; + write!(buf, " of type \"{field_type}\"").expect("write failed"); + buf + } + + fn represent_subject(subject: &OperationSubject) -> String { + match subject { + OperationSubject::LocalField(field) => { + Self::represent_property_and_type(&field.field_name, &field.field_type) + } + OperationSubject::TransformedField(field) => Self::represent_transformed_field(field), + OperationSubject::FoldSpecificField(field) => { + Self::represent_fold_specific_field(field) + } + } + } + + /// Represent a filter argument as a human-readable string suitable for use in an error message. + /// Tag arguments don't carry a name inside the [`Argument`] type, so we look up and supply + /// the tag name separately if needed. + fn represent_argument(argument: &Argument, tag_name: Option<&str>) -> String { + match argument { + Argument::Tag(tag) => Self::represent_tag_name_and_type( + tag_name.expect("tag argument without a name"), + tag.field_type(), + ), + Argument::Variable(var) => { + Self::represent_variable_name_and_type(&var.variable_name, &var.variable_type) + } + } + } + + pub(crate) fn non_nullable_subject_with_nullability_filter( filter_operator: &str, - property_name: &str, - property_type: &Type, + subject: &OperationSubject, filter_outcome: bool, ) -> Self { Self::NonNullableTypeFilteredForNullability( filter_operator.to_string(), - Self::represent_property_and_type(property_name, property_type), + Self::represent_subject(subject), filter_outcome, ) } - pub(crate) fn type_mismatch_between_property_and_tag( + pub(crate) fn type_mismatch_between_subject_and_argument( filter_operator: &str, - property_name: &str, - property_type: &Type, - tag_name: &str, - tag_type: &Type, + subject: &OperationSubject, + argument: &Argument, + tag_name: Option<&str>, ) -> Self { Self::TypeMismatchBetweenFilterSubjectAndArgument( filter_operator.to_string(), - Self::represent_property_and_type(property_name, property_type), - Self::represent_tag_name_and_type(tag_name, tag_type), + Self::represent_subject(subject), + Self::represent_argument(argument, tag_name), ) } - pub(crate) fn non_orderable_property_with_ordering_filter( + pub(crate) fn non_orderable_subject_with_ordering_filter( filter_operator: &str, - property_name: &str, - property_type: &Type, + subject: &OperationSubject, ) -> Self { Self::OrderingFilterOperationOnNonOrderableSubject( filter_operator.to_string(), - Self::represent_property_and_type(property_name, property_type), + Self::represent_subject(subject), ) } - pub(crate) fn non_orderable_tag_argument_to_ordering_filter( + pub(crate) fn non_orderable_argument_to_ordering_filter( filter_operator: &str, - tag_name: &str, - tag_type: &Type, + argument: &Argument, + tag_name: Option<&str>, ) -> Self { Self::OrderingFilterOperationWithNonOrderableArgument( filter_operator.to_string(), - Self::represent_tag_name_and_type(tag_name, tag_type), + Self::represent_argument(argument, tag_name), ) } - pub(crate) fn non_string_property_with_string_filter( + pub(crate) fn non_string_subject_with_string_filter( filter_operator: &str, - property_name: &str, - property_type: &Type, + subject: &OperationSubject, ) -> Self { Self::StringFilterOperationOnNonStringSubject( filter_operator.to_string(), - Self::represent_property_and_type(property_name, property_type), + Self::represent_subject(subject), ) } - pub(crate) fn non_string_tag_argument_to_string_filter( + pub(crate) fn non_string_argument_to_string_filter( filter_operator: &str, - tag_name: &str, - tag_type: &Type, + argument: &Argument, + tag_name: Option<&str>, ) -> Self { Self::StringFilterOperationOnNonStringArgument( filter_operator.to_string(), - Self::represent_tag_name_and_type(tag_name, tag_type), + Self::represent_argument(argument, tag_name), ) } - pub(crate) fn non_list_property_with_list_filter( + pub(crate) fn non_list_subject_with_list_filter( filter_operator: &str, - property_name: &str, - property_type: &Type, + subject: &OperationSubject, ) -> Self { Self::ListFilterOperationOnNonListSubject( filter_operator.to_string(), - Self::represent_property_and_type(property_name, property_type), + Self::represent_subject(subject), ) } - pub(crate) fn non_list_tag_argument_to_list_filter( + pub(crate) fn non_list_argument_to_list_filter( filter_operator: &str, - tag_name: &str, - tag_type: &Type, + argument: &Argument, + tag_name: Option<&str>, ) -> Self { Self::ListFilterOperationOnNonListArgument( filter_operator.to_string(), - Self::represent_tag_name_and_type(tag_name, tag_type), + Self::represent_argument(argument, tag_name), ) } } +fn write_name_of_transformed_field(buf: &mut String, field: &TransformedField) { + buf.push_str("transformed field \""); + + buf.push_str(match &field.value.base { + TransformBase::ContextField(c) => &c.field_name, + TransformBase::FoldSpecificField(f) => f.kind.field_name(), + }); + for transform in &field.value.transforms { + buf.push('.'); + buf.push_str(transform.operation_name()); + } + buf.push('"'); +} + #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct DuplicatedNamesConflict { // duplicate output name -> vec (type name, field name) being output under that name diff --git a/trustfall_core/src/frontend/filters.rs b/trustfall_core/src/frontend/filters.rs index ec84f325..ffd7f0cb 100644 --- a/trustfall_core/src/frontend/filters.rs +++ b/trustfall_core/src/frontend/filters.rs @@ -1,6 +1,6 @@ use crate::{ graphql_query::directives::{FilterDirective, OperatorArgument}, - ir::{Argument, NamedTypedValue, Operation, Type, VariableRef, Vid}, + ir::{Argument, Operation, OperationSubject, Type, VariableRef, Vid}, schema::Schema, }; @@ -11,14 +11,14 @@ use super::{ }; #[allow(clippy::too_many_arguments)] -pub(super) fn make_filter_expr( +pub(super) fn make_filter_expr( schema: &Schema, component_path: &ComponentPath, tags: &mut TagHandler<'_>, current_vertex_vid: Vid, - left_operand: LeftT, + left_operand: OperationSubject, filter_directive: &FilterDirective, -) -> Result, Vec> { +) -> Result, Vec> { let filter_operation = filter_directive .operation .try_map( @@ -28,8 +28,7 @@ pub(super) fn make_filter_expr( OperatorArgument::VariableRef(var_name) => Argument::Variable(VariableRef { variable_name: var_name.clone(), variable_type: infer_variable_type( - left_operand.named(), - left_operand.typed().clone(), + &left_operand, &filter_directive.operation, ) .map_err(|e| *e)?, @@ -42,20 +41,20 @@ pub(super) fn make_filter_expr( ) { Ok(defined_tag) => defined_tag, Err(TagLookupError::UndefinedTag(tag_name)) => { - return Err(FrontendError::UndefinedTagInFilter( - left_operand.named().to_string(), + return Err(FrontendError::undefined_tag_in_filter( + &left_operand, tag_name, )); } Err(TagLookupError::TagDefinedInsideFold(tag_name)) => { - return Err(FrontendError::TagUsedOutsideItsFoldedSubquery( - left_operand.named().to_string(), + return Err(FrontendError::tag_used_outside_its_folded_subquery( + &left_operand, tag_name, )); } Err(TagLookupError::TagUsedBeforeDefinition(tag_name)) => { - return Err(FrontendError::TagUsedBeforeDefinition( - left_operand.named().to_string(), + return Err(FrontendError::tag_used_before_definition( + &left_operand, tag_name, )) } @@ -83,16 +82,16 @@ pub(super) fn make_filter_expr( } fn infer_variable_type( - property_name: &str, - property_type: Type, + subject: &OperationSubject, operation: &Operation<(), OperatorArgument>, ) -> Result> { + let left_type = subject.field_type(); match operation { Operation::Equals(..) | Operation::NotEquals(..) => { // Direct equality comparison. // If the field is nullable, then the input should be nullable too // so that the null valued fields can be matched. - Ok(property_type) + Ok(left_type.to_owned()) } Operation::LessThan(..) | Operation::LessThanOrEqual(..) @@ -105,18 +104,17 @@ fn infer_variable_type( // Using a "null" valued variable doesn't make sense as a comparison. // However, [[1], [2], null] is a valid value to use in the comparison, since // there are definitely values that it is smaller than or bigger than. - Ok(property_type.with_nullability(false)) + Ok(left_type.with_nullability(false)) } Operation::Contains(..) | Operation::NotContains(..) => { // To be able to check whether the property's value contains the operand, // the property needs to be a list. If it's not a list, this is a bad filter. - let inner_type = if let Some(list) = property_type.as_list() { + let inner_type = if let Some(list) = left_type.as_list() { list } else { - return Err(Box::new(FilterTypeError::non_list_property_with_list_filter( + return Err(Box::new(FilterTypeError::non_list_subject_with_list_filter( operation.operation_name(), - property_name, - &property_type, + subject, ))); }; @@ -128,7 +126,7 @@ fn infer_variable_type( // Whatever the property's type is, the argument must be a non-nullable list of // the same type, so that the elements of that list may be checked for equality // against that property's value. - Ok(Type::new_list_type(property_type, false)) + Ok(Type::new_list_type(left_type.to_owned(), false)) } Operation::HasPrefix(..) | Operation::NotHasPrefix(..) @@ -150,14 +148,13 @@ fn infer_variable_type( } } -fn operand_types_valid( - operation: &Operation, +fn operand_types_valid( + operation: &Operation, tag_name: Option<&str>, ) -> Result<(), Vec> { let left = operation.left(); let right = operation.right(); - let left_type = left.typed(); - let right_type = right.map(|x| x.typed()); + let left_type = left.field_type(); // Check the left and right operands match the operator's needs individually. // For example: @@ -204,93 +201,92 @@ fn operand_types_valid( mod validity { use crate::{ frontend::error::FilterTypeError, - ir::{Argument, NamedTypedValue, Operation}, + ir::{Argument, Operation, OperationSubject}, }; - pub(super) fn nullability_types_valid( - operation: &Operation, + pub(super) fn nullability_types_valid( + operation: &Operation, tag_name: Option<&str>, ) -> Result<(), Vec> { let left = operation.left(); - let left_type = left.typed(); + let left_type = left.field_type(); // Checking non-nullable types for null or non-null is pointless. if left_type.nullable() { Ok(()) } else { - Err(vec![FilterTypeError::non_nullable_property_with_nullability_filter( + Err(vec![FilterTypeError::non_nullable_subject_with_nullability_filter( operation.operation_name(), - left.named(), - left_type, + left, matches!(operation, Operation::IsNotNull(..)), )]) } } - pub(super) fn equality_types_valid( - operation: &Operation, + pub(super) fn equality_types_valid( + operation: &Operation, tag_name: Option<&str>, ) -> Result<(), Vec> { let left = operation.left(); let right = operation.right(); - let left_type = left.typed(); - let right_type = right.map(|x| x.typed()); + let argument = right.unwrap(); + let left_type = left.field_type(); + let right_type = argument.field_type(); // Individually, any operands are valid for equality operations. // // For the operands relative to each other, nullability doesn't matter, // but the rest of the type must be the same. - let right_type = right_type.unwrap(); if left_type.equal_ignoring_nullability(right_type) { Ok(()) } else { - // The right argument must be a tag at this point. If it is not a tag - // and the second .unwrap() below panics, then our type inference - // has inferred an incorrect type for the variable in the argument. - let tag = right.unwrap().as_tag().unwrap(); - - Err(vec![FilterTypeError::type_mismatch_between_property_and_tag( + let argument = right.unwrap(); + assert!( + argument.as_variable().is_none(), + "type inference for variable {argument:?} has failed to produce a valid type; \ + this is a bug since the issue should have been caught in an earlier stage" + ); + + Err(vec![FilterTypeError::type_mismatch_between_subject_and_argument( operation.operation_name(), - left.named(), - left_type, - tag_name.unwrap(), - tag.field_type(), + left, + argument, + tag_name, )]) } } - pub(super) fn ordering_types_valid( - operation: &Operation, + pub(super) fn ordering_types_valid( + operation: &Operation, tag_name: Option<&str>, ) -> Result<(), Vec> { let left = operation.left(); let right = operation.right(); - let left_type = left.typed(); - let right_type = right.map(|x| x.typed()); + let argument = right.unwrap(); + let left_type = left.field_type(); + let right_type = argument.field_type(); // Individually, the operands' types must be non-nullable or list, recursively, // versions of orderable types. - let right_type = right_type.unwrap(); - let mut errors = vec![]; if !left_type.is_orderable() { - errors.push(FilterTypeError::non_orderable_property_with_ordering_filter( + errors.push(FilterTypeError::non_orderable_subject_with_ordering_filter( operation.operation_name(), - left.named(), - left_type, + left, )); } if !right_type.is_orderable() { - // The right argument must be a tag at this point. If it is not a tag - // and the second .unwrap() below panics, then our type inference - // has inferred an incorrect type for the variable in the argument. - let tag = right.unwrap().as_tag().unwrap(); + assert!( + argument.as_variable().is_none(), + "type inference for variable {argument:?} has failed to produce a valid type; \ + this is a bug since the issue should have been caught in an earlier stage" + ); - errors.push(FilterTypeError::non_orderable_tag_argument_to_ordering_filter( + errors.push(FilterTypeError::non_orderable_argument_to_ordering_filter( operation.operation_name(), - tag_name.unwrap(), - tag.field_type(), + argument, + tag_name, )); } @@ -302,12 +298,11 @@ mod validity { // has inferred an incorrect type for the variable in the argument. let tag = right.unwrap().as_tag().unwrap(); - errors.push(FilterTypeError::type_mismatch_between_property_and_tag( + errors.push(FilterTypeError::type_mismatch_between_subject_and_argument( operation.operation_name(), - left.named(), - left_type, - tag_name.unwrap(), - tag.field_type(), + left, + argument, + tag_name, )); } @@ -318,71 +313,70 @@ mod validity { } } - pub(super) fn list_containment_types_valid( - operation: &Operation, + pub(super) fn list_containment_types_valid( + operation: &Operation, tag_name: Option<&str>, ) -> Result<(), Vec> { let left = operation.left(); let right = operation.right(); - let left_type = left.typed(); - let right_type = right.map(|x| x.typed()); + let argument = right.unwrap(); + let left_type = left.field_type(); + let right_type = argument.field_type(); // The left-hand operand needs to be a list, ignoring nullability. // The right-hand operand may be anything, if considered individually. let inner_type = left_type.as_list().ok_or_else(|| { - vec![FilterTypeError::non_list_property_with_list_filter( + vec![FilterTypeError::non_list_subject_with_list_filter( operation.operation_name(), - left.named(), - left_type, + left, )] })?; - let right_type = right_type.unwrap(); - // However, the type inside the left-hand list must be equal, // ignoring nullability, to the type of the right-hand operand. if inner_type.equal_ignoring_nullability(right_type) { Ok(()) } else { - // The right argument must be a tag at this point. If it is not a tag - // and the second .unwrap() below panics, then our type inference - // has inferred an incorrect type for the variable in the argument. - let tag = right.unwrap().as_tag().unwrap(); + assert!( + argument.as_variable().is_none(), + "type inference for variable {argument:?} has failed to produce a valid type; \ + this is a bug since the issue should have been caught in an earlier stage" + ); - Err(vec![FilterTypeError::type_mismatch_between_property_and_tag( + Err(vec![FilterTypeError::type_mismatch_between_subject_and_argument( operation.operation_name(), - left.named(), - left_type, - tag_name.unwrap(), - tag.field_type(), + left, + argument, + tag_name, )]) } } - pub(super) fn bulk_equality_types_valid( - operation: &Operation, + pub(super) fn bulk_equality_types_valid( + operation: &Operation, tag_name: Option<&str>, ) -> Result<(), Vec> { let left = operation.left(); let right = operation.right(); - let left_type = left.typed(); - let right_type = right.map(|x| x.typed()); + let argument = right.unwrap(); + let left_type = left.field_type(); + let right_type = argument.field_type(); // The right-hand operand needs to be a list, ignoring nullability. // The left-hand operand may be anything, if considered individually. - let right_type = right_type.unwrap(); let inner_type = if let Some(list) = right_type.as_list() { Ok(list) } else { - // The right argument must be a tag at this point. If it is not a tag - // and the second .unwrap() below panics, then our type inference - // has inferred an incorrect type for the variable in the argument. - let tag = right.unwrap().as_tag().unwrap(); + assert!( + argument.as_variable().is_none(), + "type inference for variable {argument:?} has failed to produce a valid type; \ + this is a bug since the issue should have been caught in an earlier stage" + ); - Err(vec![FilterTypeError::non_list_tag_argument_to_list_filter( + Err(vec![FilterTypeError::non_list_argument_to_list_filter( operation.operation_name(), - tag_name.unwrap(), - tag.field_type(), + argument, + tag_name, )]) }?; @@ -391,51 +385,52 @@ mod validity { if left_type.equal_ignoring_nullability(&inner_type) { Ok(()) } else { - // The right argument must be a tag at this point. If it is not a tag - // and the second .unwrap() below panics, then our type inference - // has inferred an incorrect type for the variable in the argument. - let tag = right.unwrap().as_tag().unwrap(); + assert!( + argument.as_variable().is_none(), + "type inference for variable {argument:?} has failed to produce a valid type; \ + this is a bug since the issue should have been caught in an earlier stage" + ); - Err(vec![FilterTypeError::type_mismatch_between_property_and_tag( + Err(vec![FilterTypeError::type_mismatch_between_subject_and_argument( operation.operation_name(), - left.named(), - left_type, - tag_name.unwrap(), - tag.field_type(), + left, + argument, + tag_name, )]) } } - pub(super) fn string_operation_types_valid( - operation: &Operation, + pub(super) fn string_operation_types_valid( + operation: &Operation, tag_name: Option<&str>, ) -> Result<(), Vec> { let left = operation.left(); let right = operation.right(); - let left_type = left.typed(); - let right_type = right.map(|x| x.typed()); + let argument = right.unwrap(); + let left_type = left.field_type(); + let right_type = argument.field_type(); let mut errors = vec![]; // Both operands need to be strings, ignoring nullability. if left_type.is_list() || left_type.base_type() != "String" { - errors.push(FilterTypeError::non_string_property_with_string_filter( + errors.push(FilterTypeError::non_string_subject_with_string_filter( operation.operation_name(), - left.named(), - left_type, + left, )); } - // The right argument must be a tag at this point. If it is not a tag - // and the second .unwrap() below panics, then our type inference - // has inferred an incorrect type for the variable in the argument. - let right_type = right_type.unwrap(); if right_type.is_list() || right_type.base_type() != "String" { - let tag = right.unwrap().as_tag().unwrap(); - errors.push(FilterTypeError::non_string_tag_argument_to_string_filter( + assert!( + argument.as_variable().is_none(), + "type inference for variable {argument:?} has failed to produce a valid type; \ + this is a bug since the issue should have been caught in an earlier stage" + ); + + errors.push(FilterTypeError::non_string_argument_to_string_filter( operation.operation_name(), - tag_name.unwrap(), - tag.field_type(), + argument, + tag_name, )); } diff --git a/trustfall_core/src/frontend/mod.rs b/trustfall_core/src/frontend/mod.rs index 481cb44a..b01326f9 100644 --- a/trustfall_core/src/frontend/mod.rs +++ b/trustfall_core/src/frontend/mod.rs @@ -1134,6 +1134,7 @@ where }, }; let field_ref = FieldRef::FoldSpecificField(fold_specific_field.clone()); + let subject = OperationSubject::FoldSpecificField(fold_specific_field.clone()); for filter_directive in &transform_group.filter { match make_filter_expr( @@ -1141,7 +1142,7 @@ where component_path, tags, starting_vid, - field_ref.clone(), + subject.clone(), filter_directive, ) { Ok(filter) => post_filters.push(filter), @@ -1193,9 +1194,7 @@ where errors.push(FrontendError::MultipleTagsWithSameName(tag_name.to_string())); } } else { - errors.push(FrontendError::ExplicitTagNameRequired( - starting_field.name.as_ref().to_owned(), - )) + errors.push(FrontendError::explicit_tag_name_required(&subject)) } } } diff --git a/trustfall_core/src/interpreter/execution.rs b/trustfall_core/src/interpreter/execution.rs index fffc97f9..2c1978b2 100644 --- a/trustfall_core/src/interpreter/execution.rs +++ b/trustfall_core/src/interpreter/execution.rs @@ -105,7 +105,7 @@ fn compute_component<'query, AdapterT: Adapter<'query> + 'query>( iterator = coerce_if_needed(adapter.as_ref(), carrier, root_vertex, iterator); for filter_expr in &root_vertex.filters { - iterator = apply_filter_with_arbitrary_subject( + iterator = apply_filter_with_non_folded_field_subject( adapter.as_ref(), carrier, component, @@ -287,7 +287,7 @@ fn get_max_fold_count_limit(carrier: &mut QueryCarrier, fold: &IRFold) -> Option continue; } - if !matches!(left, FieldRef::FoldSpecificField(f) if f.kind == FoldSpecificFieldKind::Count) + if !matches!(left, OperationSubject::FoldSpecificField(f) if f.kind == FoldSpecificFieldKind::Count) { // The filter expression is doing something more complex than we can currently analyze. // Conservatively return `None` to disable optimizations here. @@ -358,7 +358,7 @@ fn get_min_fold_count_limit(carrier: &mut QueryCarrier, fold: &IRFold) -> Option continue; } - if !matches!(left, FieldRef::FoldSpecificField(f) if f.kind == FoldSpecificFieldKind::Count) + if !matches!(left, OperationSubject::FoldSpecificField(f) if f.kind == FoldSpecificFieldKind::Count) { // The filter expression is doing something more complex than we can currently analyze. // Conservatively return `None` to disable optimizations here. @@ -621,10 +621,7 @@ fn compute_fold<'query, AdapterT: Adapter<'query> + 'query>( for post_fold_filter in fold.post_filters.iter() { let left = post_fold_filter.left(); match left { - FieldRef::ContextField(_) => { - unreachable!("unexpectedly found a fold post-filtering step that references a ContextField: {fold:#?}"); - } - FieldRef::FoldSpecificField(fold_specific_field) => { + OperationSubject::FoldSpecificField(fold_specific_field) => { let remapped_operation = post_fold_filter.map(|_| fold_specific_field.kind, |x| x); post_filtered_iterator = apply_fold_specific_filter( adapter.as_ref(), @@ -636,6 +633,10 @@ fn compute_fold<'query, AdapterT: Adapter<'query> + 'query>( post_filtered_iterator, ); } + OperationSubject::TransformedField(_) => todo!(), + OperationSubject::LocalField(_) => { + unreachable!("unexpectedly found a fold post-filtering step that references a LocalField: {fold:#?}"); + } } } @@ -785,7 +786,7 @@ mismatch on whether the fold below {expanding_from_vid:?} was inside an `@option Box::new(final_iterator) } -fn apply_filter_with_arbitrary_subject<'query, AdapterT: Adapter<'query>>( +fn apply_filter_with_non_folded_field_subject<'query, AdapterT: Adapter<'query>>( adapter: &AdapterT, carrier: &mut QueryCarrier, component: &IRQueryComponent, @@ -805,6 +806,9 @@ fn apply_filter_with_arbitrary_subject<'query, AdapterT: Adapter<'query>>( iterator, ), OperationSubject::TransformedField(_) => todo!(), + OperationSubject::FoldSpecificField(..) => unreachable!( + "illegal filter over fold-specific field passed to this function: {filter:?}" + ), } } @@ -1146,7 +1150,7 @@ fn perform_entry_into_new_vertex<'query, AdapterT: Adapter<'query>>( let vertex_id = vertex.vid; let mut iterator = coerce_if_needed(adapter, carrier, vertex, iterator); for filter_expr in vertex.filters.iter() { - iterator = apply_filter_with_arbitrary_subject( + iterator = apply_filter_with_non_folded_field_subject( adapter, carrier, component, diff --git a/trustfall_core/src/interpreter/hints/filters.rs b/trustfall_core/src/interpreter/hints/filters.rs index ae7a028e..7b8efd32 100644 --- a/trustfall_core/src/interpreter/hints/filters.rs +++ b/trustfall_core/src/interpreter/hints/filters.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, collections::BTreeMap, fmt::Debug, ops::Bound, sync::Arc} use itertools::Itertools; -use crate::ir::{Argument, FieldRef, FieldValue, FoldSpecificFieldKind, IRFold, Operation}; +use crate::ir::{Argument, FieldValue, FoldSpecificFieldKind, IRFold, Operation, OperationSubject}; use super::{candidates::NullableValue, CandidateValue, Range}; @@ -117,7 +117,7 @@ pub(super) fn fold_requires_at_least_one_element( // TODO: When we support applying `@transform` to property-like values, we can update this logic // to be smarter and less conservative. let relevant_filters = fold.post_filters.iter().filter(|op| { - matches!(op.left(), FieldRef::FoldSpecificField(f) if f.kind == FoldSpecificFieldKind::Count) + matches!(op.left(), OperationSubject::FoldSpecificField(f) if f.kind == FoldSpecificFieldKind::Count) }); let is_subject_field_nullable = false; // the "count" value can't be null candidate_from_statically_evaluated_filters( diff --git a/trustfall_core/src/interpreter/hints/vertex_info.rs b/trustfall_core/src/interpreter/hints/vertex_info.rs index b05657bd..06773569 100644 --- a/trustfall_core/src/interpreter/hints/vertex_info.rs +++ b/trustfall_core/src/interpreter/hints/vertex_info.rs @@ -6,6 +6,7 @@ use std::{ sync::Arc, }; +use crate::ir::TransformBase; use crate::{ interpreter::InterpretedQuery, ir::{ @@ -169,10 +170,16 @@ impl VertexInfo for T { .filter(|c| c.defined_at() == current_vertex.vid) .map(|c| RequiredProperty::new(c.field_name_arc())); - let properties = properties.chain(current_vertex.filters.iter().map(|f| { + let properties = properties.chain(current_vertex.filters.iter().map(move |f| { RequiredProperty::new(match f.left() { OperationSubject::LocalField(field) => field.field_name.clone(), - OperationSubject::TransformedField(_) => todo!(), + OperationSubject::TransformedField(transformed) => { + match &transformed.value.base { + TransformBase::ContextField(field) => field.field_name.clone(), + TransformBase::FoldSpecificField(_) => unreachable!("illegal transformed vertex in filter of current_vertex: {current_vertex:#?}"), + } + } + OperationSubject::FoldSpecificField(..) => unreachable!("found fold-specific field in vertex filters: {current_vertex:#?}"), }) })); @@ -238,6 +245,9 @@ impl VertexInfo for T { let local_field = match first_filter.left() { OperationSubject::LocalField(field) => field, OperationSubject::TransformedField(_) => return None, + OperationSubject::FoldSpecificField(..) => { + unreachable!("found fold-specific field in vertex filters: {vertex:#?}") + } }; let candidate = @@ -268,6 +278,8 @@ impl VertexInfo for T { return None; } + let current_vertex = self.current_vertex(); + // We only care about filtering operations that are all of the following: // - on the requested property of this vertex; // - dynamically-resolvable, i.e. depend on tagged arguments, @@ -275,7 +287,7 @@ impl VertexInfo for T { // at the time this call was made, and // - use a supported filtering operation using those tagged arguments. let resolved_range = (Bound::Unbounded, self.execution_frontier()); - let relevant_filters: Vec<_> = filters_on_local_property(self.current_vertex(), property) + let relevant_filters: Vec<_> = filters_on_local_property(current_vertex, property) .filter(|op| { matches!( op, @@ -315,6 +327,9 @@ impl VertexInfo for T { let local_field = match first_filter.left() { OperationSubject::LocalField(field) => field, OperationSubject::TransformedField(_) => return None, + OperationSubject::FoldSpecificField(..) => { + unreachable!("found fold-specific field in vertex filters: {current_vertex:#?}") + } }; let initial_candidate = self.statically_required_property(property).unwrap_or_else(|| { @@ -433,6 +448,9 @@ fn filters_on_local_property<'a: 'b, 'b>( // TODO: This is an opportunity for further optimization. false } + OperationSubject::FoldSpecificField(..) => { + unreachable!("found fold-specific field in vertex filters: {vertex:#?}") + } } }) } diff --git a/trustfall_core/src/ir/mod.rs b/trustfall_core/src/ir/mod.rs index a920b276..f3c39433 100644 --- a/trustfall_core/src/ir/mod.rs +++ b/trustfall_core/src/ir/mod.rs @@ -13,7 +13,7 @@ use std::{ use serde::{Deserialize, Serialize}; pub use self::indexed::{EdgeKind, IndexedQuery, InvalidIRQueryError, Output}; -pub use self::types::{NamedTypedValue, Type}; +pub use self::types::Type; pub use self::value::{FieldValue, TransparentValue}; mod indexed; @@ -235,7 +235,7 @@ pub struct IRFold { /// All [`FieldRef`] values inside each [`Operation`] within the `Vec` are guaranteed to have /// `FieldRef.refers_to_fold_specific_field().is_some() == true`. #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub post_filters: Vec>, + pub post_filters: Vec>, } #[non_exhaustive] @@ -380,6 +380,13 @@ pub enum Argument { } impl Argument { + pub(crate) fn as_variable(&self) -> Option<&VariableRef> { + match self { + Argument::Variable(var) => Some(var), + _ => None, + } + } + pub(crate) fn as_tag(&self) -> Option<&FieldRef> { match self { Argument::Tag(t) => Some(t), @@ -398,6 +405,13 @@ impl Argument { } } } + + pub fn field_type(&self) -> &Type { + match self { + Argument::Tag(tag) => tag.field_type(), + Argument::Variable(var) => &var.variable_type, + } + } } /// The left-hand side of a Trustfall operation. @@ -414,6 +428,7 @@ impl Argument { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum OperationSubject { LocalField(LocalField), + FoldSpecificField(FoldSpecificField), TransformedField(TransformedField), } @@ -429,6 +444,23 @@ impl From for OperationSubject { } } +impl OperationSubject { + pub fn refers_to_fold_specific_field(&self) -> Option<&FoldSpecificField> { + match self { + OperationSubject::FoldSpecificField(fold_specific) => Some(fold_specific), + _ => None, + } + } + + pub fn field_type(&self) -> &Type { + match self { + OperationSubject::LocalField(inner) => &inner.field_type, + OperationSubject::TransformedField(inner) => &inner.field_type, + OperationSubject::FoldSpecificField(inner) => inner.kind.field_type(), + } + } +} + /// Operations that can be made in the graph. /// /// In a Trustfall query, the `@filter` directive produces [`Operation`] values: @@ -719,20 +751,52 @@ pub struct LocalField { pub field_type: Type, } +#[non_exhaustive] /// The outcome of a `@transform` operation applied to a vertex property or property-like value /// such as the element count of a fold. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct TransformedField { - /// Which vertex's field is this a transformation of. - pub vertex_id: Vid, + pub value: Arc, - /// The unique identifier of the transformation this represents. + /// The unique identifier of the transformed value this struct represents. pub tid: Tid, /// The resulting type of the value produced by this transformation. pub field_type: Type, } +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TransformedValue { + pub base: TransformBase, + pub transforms: Vec, +} + +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum TransformBase { + ContextField(ContextField), + FoldSpecificField(FoldSpecificField), +} + +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Transform { + Len, + Abs, + Add(FieldRef), +} + +impl Transform { + pub(crate) fn operation_name(&self) -> &str { + match self { + Self::Len => "len", + Self::Abs => "abs", + Self::Add(..) => "add", + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct VariableRef { pub variable_name: Arc, diff --git a/trustfall_core/src/ir/types/mod.rs b/trustfall_core/src/ir/types/mod.rs index e6cbc830..ed1611d4 100644 --- a/trustfall_core/src/ir/types/mod.rs +++ b/trustfall_core/src/ir/types/mod.rs @@ -1,5 +1,3 @@ mod base; -mod named_typed; pub use base::Type; -pub use named_typed::NamedTypedValue; diff --git a/trustfall_core/src/ir/types/named_typed.rs b/trustfall_core/src/ir/types/named_typed.rs deleted file mode 100644 index f309871e..00000000 --- a/trustfall_core/src/ir/types/named_typed.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::fmt::Debug; - -use super::{ - super::{ - Argument, ContextField, FieldRef, FoldSpecificField, FoldSpecificFieldKind, LocalField, - OperationSubject, VariableRef, - }, - Type, -}; - -pub trait NamedTypedValue: Debug + Clone + PartialEq + Eq { - fn typed(&self) -> &Type; - - fn named(&self) -> &str; -} - -impl NamedTypedValue for OperationSubject { - fn typed(&self) -> &Type { - match self { - OperationSubject::LocalField(inner) => inner.typed(), - OperationSubject::TransformedField(_) => todo!(), - } - } - - fn named(&self) -> &str { - match self { - OperationSubject::LocalField(inner) => inner.named(), - OperationSubject::TransformedField(_) => todo!(), - } - } -} - -impl NamedTypedValue for LocalField { - fn typed(&self) -> &Type { - &self.field_type - } - - fn named(&self) -> &str { - self.field_name.as_ref() - } -} - -impl NamedTypedValue for ContextField { - fn typed(&self) -> &Type { - &self.field_type - } - - fn named(&self) -> &str { - self.field_name.as_ref() - } -} - -impl NamedTypedValue for FoldSpecificField { - fn typed(&self) -> &Type { - self.kind.field_type() - } - - fn named(&self) -> &str { - self.kind.field_name() - } -} - -impl NamedTypedValue for FoldSpecificFieldKind { - fn typed(&self) -> &Type { - self.field_type() - } - - fn named(&self) -> &str { - self.field_name() - } -} - -impl NamedTypedValue for VariableRef { - fn typed(&self) -> &Type { - &self.variable_type - } - - fn named(&self) -> &str { - &self.variable_name - } -} - -impl NamedTypedValue for FieldRef { - fn typed(&self) -> &Type { - match self { - FieldRef::ContextField(c) => c.typed(), - FieldRef::FoldSpecificField(f) => f.kind.typed(), - } - } - - fn named(&self) -> &str { - match self { - FieldRef::ContextField(c) => c.named(), - FieldRef::FoldSpecificField(f) => f.kind.named(), - } - } -} - -impl NamedTypedValue for Argument { - fn typed(&self) -> &Type { - match self { - Argument::Tag(t) => t.typed(), - Argument::Variable(v) => v.typed(), - } - } - - fn named(&self) -> &str { - match self { - Argument::Tag(t) => t.named(), - Argument::Variable(v) => v.named(), - } - } -} diff --git a/trustfall_core/test_data/tests/frontend_errors/duplicate_tag_in_separate_folds.frontend-error.ron b/trustfall_core/test_data/tests/frontend_errors/duplicate_tag_in_separate_folds.frontend-error.ron index fe2f9372..64fab002 100644 --- a/trustfall_core/test_data/tests/frontend_errors/duplicate_tag_in_separate_folds.frontend-error.ron +++ b/trustfall_core/test_data/tests/frontend_errors/duplicate_tag_in_separate_folds.frontend-error.ron @@ -1,4 +1,4 @@ Err(MultipleErrors(DisplayVec([ MultipleTagsWithSameName("tagged"), - TagUsedOutsideItsFoldedSubquery("value", "tagged"), + TagUsedOutsideItsFoldedSubquery("property \"value\"", "tagged"), ]))) diff --git a/trustfall_core/test_data/tests/frontend_errors/duplicate_tag_with_parent_after_fold.frontend-error.ron b/trustfall_core/test_data/tests/frontend_errors/duplicate_tag_with_parent_after_fold.frontend-error.ron index fe2f9372..64fab002 100644 --- a/trustfall_core/test_data/tests/frontend_errors/duplicate_tag_with_parent_after_fold.frontend-error.ron +++ b/trustfall_core/test_data/tests/frontend_errors/duplicate_tag_with_parent_after_fold.frontend-error.ron @@ -1,4 +1,4 @@ Err(MultipleErrors(DisplayVec([ MultipleTagsWithSameName("tagged"), - TagUsedOutsideItsFoldedSubquery("value", "tagged"), + TagUsedOutsideItsFoldedSubquery("property \"value\"", "tagged"), ]))) diff --git a/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_fold_count_tag_and_string_subject.frontend-error.ron b/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_fold_count_tag_and_string_subject.frontend-error.ron new file mode 100644 index 00000000..3f0ff72f --- /dev/null +++ b/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_fold_count_tag_and_string_subject.frontend-error.ron @@ -0,0 +1 @@ +Err(FilterTypeError(StringFilterOperationOnNonStringArgument("regex", "tag \"factors\" of type \"Int!\""))) diff --git a/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_fold_count_tag_and_string_subject.graphql-parsed.ron b/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_fold_count_tag_and_string_subject.graphql-parsed.ron new file mode 100644 index 00000000..f2f79788 --- /dev/null +++ b/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_fold_count_tag_and_string_subject.graphql-parsed.ron @@ -0,0 +1,106 @@ +Ok(TestParsedGraphQLQuery( + schema_name: "numbers", + query: Query( + root_connection: FieldConnection( + position: Pos( + line: 3, + column: 5, + ), + name: "Four", + ), + root_field: FieldNode( + position: Pos( + line: 3, + column: 5, + ), + name: "Four", + connections: [ + (FieldConnection( + position: Pos( + line: 4, + column: 9, + ), + name: "primeFactor", + fold: Some(FoldGroup( + fold: FoldDirective(), + transform: Some(TransformGroup( + transform: TransformDirective( + kind: Count, + ), + tag: [ + TagDirective( + name: Some("factors"), + ), + ], + )), + )), + ), FieldNode( + position: Pos( + line: 4, + column: 9, + ), + name: "primeFactor", + transform_group: Some(TransformGroup( + transform: TransformDirective( + kind: Count, + ), + tag: [ + TagDirective( + name: Some("factors"), + ), + ], + )), + )), + (FieldConnection( + position: Pos( + line: 6, + column: 9, + ), + name: "successor", + ), FieldNode( + position: Pos( + line: 6, + column: 9, + ), + name: "successor", + connections: [ + (FieldConnection( + position: Pos( + line: 9, + column: 13, + ), + name: "name", + ), FieldNode( + position: Pos( + line: 9, + column: 13, + ), + name: "name", + filter: [ + FilterDirective( + operation: RegexMatches((), TagRef("factors")), + ), + ], + )), + (FieldConnection( + position: Pos( + line: 11, + column: 13, + ), + name: "value", + ), FieldNode( + position: Pos( + line: 11, + column: 13, + ), + name: "value", + output: [ + OutputDirective(), + ], + )), + ], + )), + ], + ), + ), +)) diff --git a/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_fold_count_tag_and_string_subject.graphql.ron b/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_fold_count_tag_and_string_subject.graphql.ron new file mode 100644 index 00000000..baae3460 --- /dev/null +++ b/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_fold_count_tag_and_string_subject.graphql.ron @@ -0,0 +1,18 @@ +TestGraphQLQuery ( + schema_name: "numbers", + query: r#" +{ + Four { + primeFactor @fold @transform(op: "count") @tag(name: "factors") + + successor { + # This filter has nonsensical types: we're attempting to use + # a tag of type `Int!` as an argument to a string filter operator. + name @filter(op: "regex", value: ["%factors"]) + + value @output + } + } +}"#, + arguments: {}, +) diff --git a/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_string_tag_and_fold_count_subject.frontend-error.ron b/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_string_tag_and_fold_count_subject.frontend-error.ron new file mode 100644 index 00000000..77a2b0aa --- /dev/null +++ b/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_string_tag_and_fold_count_subject.frontend-error.ron @@ -0,0 +1 @@ +Err(FilterTypeError(StringFilterOperationOnNonStringSubject("regex", "transformed field \"@fold.count\" of type \"Int!\""))) diff --git a/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_string_tag_and_fold_count_subject.graphql-parsed.ron b/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_string_tag_and_fold_count_subject.graphql-parsed.ron new file mode 100644 index 00000000..7edff169 --- /dev/null +++ b/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_string_tag_and_fold_count_subject.graphql-parsed.ron @@ -0,0 +1,76 @@ +Ok(TestParsedGraphQLQuery( + schema_name: "numbers", + query: Query( + root_connection: FieldConnection( + position: Pos( + line: 3, + column: 5, + ), + name: "Four", + ), + root_field: FieldNode( + position: Pos( + line: 3, + column: 5, + ), + name: "Four", + connections: [ + (FieldConnection( + position: Pos( + line: 4, + column: 9, + ), + name: "name", + ), FieldNode( + position: Pos( + line: 4, + column: 9, + ), + name: "name", + output: [ + OutputDirective(), + ], + tag: [ + TagDirective(), + ], + )), + (FieldConnection( + position: Pos( + line: 6, + column: 9, + ), + name: "primeFactor", + fold: Some(FoldGroup( + fold: FoldDirective(), + transform: Some(TransformGroup( + transform: TransformDirective( + kind: Count, + ), + filter: [ + FilterDirective( + operation: RegexMatches((), TagRef("name")), + ), + ], + )), + )), + ), FieldNode( + position: Pos( + line: 6, + column: 9, + ), + name: "primeFactor", + transform_group: Some(TransformGroup( + transform: TransformDirective( + kind: Count, + ), + filter: [ + FilterDirective( + operation: RegexMatches((), TagRef("name")), + ), + ], + )), + )), + ], + ), + ), +)) diff --git a/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_string_tag_and_fold_count_subject.graphql.ron b/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_string_tag_and_fold_count_subject.graphql.ron new file mode 100644 index 00000000..4739d259 --- /dev/null +++ b/trustfall_core/test_data/tests/frontend_errors/filter_tag_type_mismatch_string_tag_and_fold_count_subject.graphql.ron @@ -0,0 +1,12 @@ +TestGraphQLQuery ( + schema_name: "numbers", + query: r#" +{ + Four { + name @tag @output + + primeFactor @fold @transform(op: "count") @filter(op: "regex", value: ["%name"]) + } +}"#, + arguments: {}, +) diff --git a/trustfall_core/test_data/tests/frontend_errors/fold_count_tag_value_used_inside_own_fold.frontend-error.ron b/trustfall_core/test_data/tests/frontend_errors/fold_count_tag_value_used_inside_own_fold.frontend-error.ron index 2f2e1e83..c383c92b 100644 --- a/trustfall_core/test_data/tests/frontend_errors/fold_count_tag_value_used_inside_own_fold.frontend-error.ron +++ b/trustfall_core/test_data/tests/frontend_errors/fold_count_tag_value_used_inside_own_fold.frontend-error.ron @@ -1 +1 @@ -Err(UndefinedTagInFilter("value", "tagged_count")) +Err(UndefinedTagInFilter("property \"value\"", "tagged_count")) diff --git a/trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_fold_count_value.frontend-error.ron b/trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_fold_count_value.frontend-error.ron new file mode 100644 index 00000000..9a289eb1 --- /dev/null +++ b/trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_fold_count_value.frontend-error.ron @@ -0,0 +1,4 @@ +Err(MultipleErrors(DisplayVec([ + ExplicitTagNameRequired("transformed field \"@fold.count\""), + UndefinedTagInFilter("property \"value\"", "successorcount"), +]))) diff --git a/trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_value.graphql-parsed.ron b/trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_fold_count_value.graphql-parsed.ron similarity index 100% rename from trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_value.graphql-parsed.ron rename to trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_fold_count_value.graphql-parsed.ron diff --git a/trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_value.graphql.ron b/trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_fold_count_value.graphql.ron similarity index 100% rename from trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_value.graphql.ron rename to trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_fold_count_value.graphql.ron diff --git a/trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_value.frontend-error.ron b/trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_value.frontend-error.ron deleted file mode 100644 index d52bed85..00000000 --- a/trustfall_core/test_data/tests/frontend_errors/implicit_tag_name_on_transformed_value.frontend-error.ron +++ /dev/null @@ -1,4 +0,0 @@ -Err(MultipleErrors(DisplayVec([ - ExplicitTagNameRequired("successor"), - UndefinedTagInFilter("value", "successorcount"), -]))) diff --git a/trustfall_core/test_data/tests/frontend_errors/tag_and_filter_in_unrelated_folds.frontend-error.ron b/trustfall_core/test_data/tests/frontend_errors/tag_and_filter_in_unrelated_folds.frontend-error.ron index f9bf2aa5..8a1f8d9a 100644 --- a/trustfall_core/test_data/tests/frontend_errors/tag_and_filter_in_unrelated_folds.frontend-error.ron +++ b/trustfall_core/test_data/tests/frontend_errors/tag_and_filter_in_unrelated_folds.frontend-error.ron @@ -1 +1 @@ -Err(TagUsedOutsideItsFoldedSubquery("value", "folded")) +Err(TagUsedOutsideItsFoldedSubquery("property \"value\"", "folded")) diff --git a/trustfall_core/test_data/tests/frontend_errors/tag_before_filter_in_different_scopes.frontend-error.ron b/trustfall_core/test_data/tests/frontend_errors/tag_before_filter_in_different_scopes.frontend-error.ron index b9748c0a..1cd17482 100644 --- a/trustfall_core/test_data/tests/frontend_errors/tag_before_filter_in_different_scopes.frontend-error.ron +++ b/trustfall_core/test_data/tests/frontend_errors/tag_before_filter_in_different_scopes.frontend-error.ron @@ -1 +1 @@ -Err(TagUsedBeforeDefinition("name", "my_tag")) +Err(TagUsedBeforeDefinition("property \"name\"", "my_tag")) diff --git a/trustfall_core/test_data/tests/frontend_errors/tag_from_inside_fold.frontend-error.ron b/trustfall_core/test_data/tests/frontend_errors/tag_from_inside_fold.frontend-error.ron index f9bf2aa5..8a1f8d9a 100644 --- a/trustfall_core/test_data/tests/frontend_errors/tag_from_inside_fold.frontend-error.ron +++ b/trustfall_core/test_data/tests/frontend_errors/tag_from_inside_fold.frontend-error.ron @@ -1 +1 @@ -Err(TagUsedOutsideItsFoldedSubquery("value", "folded")) +Err(TagUsedOutsideItsFoldedSubquery("property \"value\"", "folded")) diff --git a/trustfall_core/test_data/tests/frontend_errors/tag_used_in_fold_but_defined_after.frontend-error.ron b/trustfall_core/test_data/tests/frontend_errors/tag_used_in_fold_but_defined_after.frontend-error.ron index 6edd5b65..ea06ba12 100644 --- a/trustfall_core/test_data/tests/frontend_errors/tag_used_in_fold_but_defined_after.frontend-error.ron +++ b/trustfall_core/test_data/tests/frontend_errors/tag_used_in_fold_but_defined_after.frontend-error.ron @@ -1 +1 @@ -Err(UndefinedTagInFilter("value", "my_tag")) +Err(UndefinedTagInFilter("property \"value\"", "my_tag")) diff --git a/trustfall_core/test_data/tests/frontend_errors/undefined_tag.frontend-error.ron b/trustfall_core/test_data/tests/frontend_errors/undefined_tag.frontend-error.ron index 498fb53c..694c4d8e 100644 --- a/trustfall_core/test_data/tests/frontend_errors/undefined_tag.frontend-error.ron +++ b/trustfall_core/test_data/tests/frontend_errors/undefined_tag.frontend-error.ron @@ -1 +1 @@ -Err(UndefinedTagInFilter("name", "undefined_tag_name")) +Err(UndefinedTagInFilter("property \"name\"", "undefined_tag_name"))