Repository: Shopify/bluejay (language: rust)

Issue:
Consistency in core directives across executable and definition
In `bluejay-core`, all of the traits in `definition` have the `directives` method returning an `Option<&Self::Directives>`. But in `executable`, all of the traits have the `directives` method returning `&Self::Directives`. We should settle on one approach and be consistent across `definition` and `executable`.


Discussion / hints:
I would say that maybe removing the `Option<>` in this case might be the most convenient way for developers, the absence of directives implies an empty list. I am aware that we can save memory by leveraging `None`, however it doesn't seem like there is a straight forward way of checking whether there are directives present, we could implement a `peek()` method, ... but at that point your parser performance will drop a bit for a minor optimization.

If we wanted the consistency with `schema` we could also leverage the `transpose()`, at the moment in `executable` we raise an `Err` due to the presence of `?`.

One of the issues with making everything a non-option would be `scalar` definitions would need to be able to return `directives` as well as now we just return `None` so maybe for convenience there it would be easier to transform `executable` to `Option`, this would entail a breaking change.

Fix patch:
diff --git a/bluejay-core/src/executable/field.rs b/bluejay-core/src/executable/field.rs
index 1f6f453..ca0faf3 100644
--- a/bluejay-core/src/executable/field.rs
+++ b/bluejay-core/src/executable/field.rs
@@ -9,7 +9,7 @@ pub trait Field {
     fn alias(&self) -> Option<&str>;
     fn name(&self) -> &str;
     fn arguments(&self) -> Option<&Self::Arguments>;
-    fn directives(&self) -> &Self::Directives;
+    fn directives(&self) -> Option<&Self::Directives>;
     fn selection_set(&self) -> Option<&Self::SelectionSet>;
 
     fn response_name(&self) -> &str {
diff --git a/bluejay-core/src/executable/fragment_definition.rs b/bluejay-core/src/executable/fragment_definition.rs
index c225478..eb3faa4 100644
--- a/bluejay-core/src/executable/fragment_definition.rs
+++ b/bluejay-core/src/executable/fragment_definition.rs
@@ -7,6 +7,6 @@ pub trait FragmentDefinition: Indexable {
 
     fn name(&self) -> &str;
     fn type_condition(&self) -> &str;
-    fn directives(&self) -> &Self::Directives;
+    fn directives(&self) -> Option<&Self::Directives>;
     fn selection_set(&self) -> &Self::SelectionSet;
 }
diff --git a/bluejay-core/src/executable/fragment_spread.rs b/bluejay-core/src/executable/fragment_spread.rs
index 4dc93d4..1a1b2e1 100644
--- a/bluejay-core/src/executable/fragment_spread.rs
+++ b/bluejay-core/src/executable/fragment_spread.rs
@@ -4,5 +4,5 @@ pub trait FragmentSpread {
     type Directives: VariableDirectives;
 
     fn name(&self) -> &str;
-    fn directives(&self) -> &Self::Directives;
+    fn directives(&self) -> Option<&Self::Directives>;
 }
diff --git a/bluejay-core/src/executable/inline_fragment.rs b/bluejay-core/src/executable/inline_fragment.rs
index 938ba80..8f30af5 100644
--- a/bluejay-core/src/executable/inline_fragment.rs
+++ b/bluejay-core/src/executable/inline_fragment.rs
@@ -6,6 +6,6 @@ pub trait InlineFragment {
     type SelectionSet: SelectionSet;
 
     fn type_condition(&self) -> Option<&str>;
-    fn directives(&self) -> &Self::Directives;
+    fn directives(&self) -> Option<&Self::Directives>;
     fn selection_set(&self) -> &Self::SelectionSet;
 }
diff --git a/bluejay-core/src/executable/operation_definition.rs b/bluejay-core/src/executable/operation_definition.rs
index 097a96e..6554066 100644
--- a/bluejay-core/src/executable/operation_definition.rs
+++ b/bluejay-core/src/executable/operation_definition.rs
@@ -38,7 +38,7 @@ impl<'a, O: OperationDefinition> OperationDefinitionReference<'a, O> {
 
     pub fn directives(&self) -> Option<&'a <<O as OperationDefinition>::ExplicitOperationDefinition as ExplicitOperationDefinition>::Directives>{
         match self {
-            Self::Explicit(eod) => Some(eod.directives()),
+            Self::Explicit(eod) => eod.directives(),
             Self::Implicit(_) => None,
         }
     }
@@ -59,7 +59,7 @@ pub trait ExplicitOperationDefinition {
     fn operation_type(&self) -> OperationType;
     fn name(&self) -> Option<&str>;
     fn variable_definitions(&self) -> Option<&Self::VariableDefinitions>;
-    fn directives(&self) -> &Self::Directives;
+    fn directives(&self) -> Option<&Self::Directives>;
     fn selection_set(&self) -> &Self::SelectionSet;
 }
 
diff --git a/bluejay-core/src/executable/selection.rs b/bluejay-core/src/executable/selection.rs
index 94d8d13..2149631 100644
--- a/bluejay-core/src/executable/selection.rs
+++ b/bluejay-core/src/executable/selection.rs
@@ -16,7 +16,7 @@ pub trait Selection: Sized {
 }
 
 impl<'a, S: Selection> SelectionReference<'a, S> {
-    pub fn directives(&self) -> &'a <S::Field as Field>::Directives {
+    pub fn directives(&self) -> Option<&'a <S::Field as Field>::Directives> {
         match self {
             Self::Field(f) => f.directives(),
             Self::FragmentSpread(fs) => fs.directives(),
diff --git a/bluejay-core/src/executable/variable_definition.rs b/bluejay-core/src/executable/variable_definition.rs
index 0d9f0e6..d9b244b 100644
--- a/bluejay-core/src/executable/variable_definition.rs
+++ b/bluejay-core/src/executable/variable_definition.rs
@@ -8,7 +8,7 @@ pub trait VariableDefinition {
 
     fn variable(&self) -> &str;
     fn r#type(&self) -> &Self::VariableType;
-    fn directives(&self) -> &Self::Directives;
+    fn directives(&self) -> Option<&Self::Directives>;
     fn default_value(&self) -> Option<&Self::Value>;
 
     fn is_required(&self) -> bool {
diff --git a/bluejay-parser/src/ast/executable/field.rs b/bluejay-parser/src/ast/executable/field.rs
index b56541b..5615d3a 100644
--- a/bluejay-parser/src/ast/executable/field.rs
+++ b/bluejay-parser/src/ast/executable/field.rs
@@ -12,7 +12,7 @@ pub struct Field<'a> {
     alias: Option<Name<'a>>,
     name: Name<'a>,
     arguments: Option<VariableArguments<'a>>,
-    directives: VariableDirectives<'a>,
+    directives: Option<VariableDirectives<'a>>,
     selection_set: Option<SelectionSet<'a>>,
     span: Span,
 }
@@ -29,12 +29,13 @@ impl<'a> FromTokens<'a> for Field<'a> {
             (None, tokens.expect_name()?)
         };
         let arguments = VariableArguments::try_from_tokens(tokens).transpose()?;
-        let directives = VariableDirectives::from_tokens(tokens)?;
+        let directives = VariableDirectives::try_from_tokens(tokens).transpose()?;
         let selection_set = SelectionSet::try_from_tokens(tokens).transpose()?;
         let start_span = alias.as_ref().unwrap_or(&name).span();
+        let directives_span = directives.as_ref().and_then(|directives| directives.span());
         let end_span = if let Some(selection_set) = &selection_set {
             selection_set.span()
-        } else if let Some(directive_span) = directives.span() {
+        } else if let Some(directive_span) = directives_span {
             directive_span
         } else if let Some(arguments) = &arguments {
             arguments.span()
@@ -98,8 +99,8 @@ impl<'a> bluejay_core::executable::Field for Field<'a> {
         self.arguments.as_ref()
     }
 
-    fn directives(&self) -> &Self::Directives {
-        &self.directives
+    fn directives(&self) -> Option<&Self::Directives> {
+        self.directives.as_ref()
     }
 
     fn selection_set(&self) -> Option<&Self::SelectionSet> {
diff --git a/bluejay-parser/src/ast/executable/fragment_definition.rs b/bluejay-parser/src/ast/executable/fragment_definition.rs
index 5233467..3331e54 100644
--- a/bluejay-parser/src/ast/executable/fragment_definition.rs
+++ b/bluejay-parser/src/ast/executable/fragment_definition.rs
@@ -1,4 +1,5 @@
 use crate::ast::executable::{SelectionSet, TypeCondition};
+use crate::ast::try_from_tokens::TryFromTokens;
 use crate::ast::{FromTokens, IsMatch, ParseError, Tokens, VariableDirectives};
 use crate::lexical_token::Name;
 use crate::{HasSpan, Span};
@@ -7,7 +8,7 @@ use crate::{HasSpan, Span};
 pub struct FragmentDefinition<'a> {
     name: Name<'a>,
     type_condition: TypeCondition<'a>,
-    directives: VariableDirectives<'a>,
+    directives: Option<VariableDirectives<'a>>,
     selection_set: SelectionSet<'a>,
     span: Span,
 }
@@ -27,7 +28,7 @@ impl<'a> FromTokens<'a> for FragmentDefinition<'a> {
             return Err(ParseError::UnexpectedToken { span: name.into() });
         }
         let type_condition = TypeCondition::from_tokens(tokens)?;
-        let directives = VariableDirectives::from_tokens(tokens)?;
+        let directives = VariableDirectives::try_from_tokens(tokens).transpose()?;
         let selection_set = SelectionSet::from_tokens(tokens)?;
         let span = fragment_identifier_span.merge(selection_set.span());
         Ok(Self {
@@ -74,8 +75,8 @@ impl<'a> bluejay_core::executable::FragmentDefinition for FragmentDefinition<'a>
         self.type_condition.named_type().as_ref()
     }
 
-    fn directives(&self) -> &Self::Directives {
-        &self.directives
+    fn directives(&self) -> Option<&Self::Directives> {
+        self.directives.as_ref()
     }
 
     fn selection_set(&self) -> &Self::SelectionSet {
diff --git a/bluejay-parser/src/ast/executable/fragment_spread.rs b/bluejay-parser/src/ast/executable/fragment_spread.rs
index 01cf301..a5e4c8c 100644
--- a/bluejay-parser/src/ast/executable/fragment_spread.rs
+++ b/bluejay-parser/src/ast/executable/fragment_spread.rs
@@ -1,4 +1,5 @@
 use crate::ast::executable::TypeCondition;
+use crate::ast::try_from_tokens::TryFromTokens;
 use crate::ast::{FromTokens, IsMatch, ParseError, Tokens, VariableDirectives};
 use crate::lexical_token::{Name, PunctuatorType};
 use crate::{HasSpan, Span};
@@ -6,7 +7,7 @@ use crate::{HasSpan, Span};
 #[derive(Debug)]
 pub struct FragmentSpread<'a> {
     name: Name<'a>,
-    directives: VariableDirectives<'a>,
+    directives: Option<VariableDirectives<'a>>,
     span: Span,
 }
 
@@ -15,7 +16,7 @@ impl<'a> FromTokens<'a> for FragmentSpread<'a> {
         let ellipse_span = tokens.expect_punctuator(PunctuatorType::Ellipse)?;
         let name = tokens.expect_name()?;
         assert_ne!(TypeCondition::ON, name.as_ref());
-        let directives = VariableDirectives::from_tokens(tokens)?;
+        let directives = VariableDirectives::try_from_tokens(tokens).transpose()?;
         let span = ellipse_span.merge(name.span());
         Ok(Self {
             name,
@@ -48,8 +49,8 @@ impl<'a> bluejay_core::executable::FragmentSpread for FragmentSpread<'a> {
         self.name.as_ref()
     }
 
-    fn directives(&self) -> &Self::Directives {
-        &self.directives
+    fn directives(&self) -> Option<&Self::Directives> {
+        self.directives.as_ref()
     }
 }
 
diff --git a/bluejay-parser/src/ast/executable/inline_fragment.rs b/bluejay-parser/src/ast/executable/inline_fragment.rs
index fe26beb..1726691 100644
--- a/bluejay-parser/src/ast/executable/inline_fragment.rs
+++ b/bluejay-parser/src/ast/executable/inline_fragment.rs
@@ -6,7 +6,7 @@ use crate::{HasSpan, Span};
 #[derive(Debug)]
 pub struct InlineFragment<'a> {
     type_condition: Option<TypeCondition<'a>>,
-    directives: VariableDirectives<'a>,
+    directives: Option<VariableDirectives<'a>>,
     selection_set: SelectionSet<'a>,
     span: Span,
 }
@@ -15,7 +15,7 @@ impl<'a> FromTokens<'a> for InlineFragment<'a> {
     fn from_tokens(tokens: &mut impl Tokens<'a>) -> Result<Self, ParseError> {
         let ellipse_span = tokens.expect_punctuator(PunctuatorType::Ellipse)?;
         let type_condition = TypeCondition::try_from_tokens(tokens).transpose()?;
-        let directives = VariableDirectives::from_tokens(tokens)?;
+        let directives = VariableDirectives::try_from_tokens(tokens).transpose()?;
         let selection_set = SelectionSet::from_tokens(tokens)?;
         let span = ellipse_span.merge(selection_set.span());
         Ok(Self {
@@ -57,8 +57,8 @@ impl<'a> bluejay_core::executable::InlineFragment for InlineFragment<'a> {
             .map(|tc| tc.named_type().as_ref())
     }
 
-    fn directives(&self) -> &Self::Directives {
-        &self.directives
+    fn directives(&self) -> Option<&Self::Directives> {
+        self.directives.as_ref()
     }
 
     fn selection_set(&self) -> &Self::SelectionSet {
diff --git a/bluejay-parser/src/ast/executable/operation_definition.rs b/bluejay-parser/src/ast/executable/operation_definition.rs
index 509190b..9624f0e 100644
--- a/bluejay-parser/src/ast/executable/operation_definition.rs
+++ b/bluejay-parser/src/ast/executable/operation_definition.rs
@@ -49,7 +49,7 @@ impl<'a> FromTokens<'a> for OperationDefinition<'a> {
         if let Some(operation_type) = OperationType::try_from_tokens(tokens).transpose()? {
             let name = tokens.next_if_name();
             let variable_definitions = VariableDefinitions::try_from_tokens(tokens).transpose()?;
-            let directives = VariableDirectives::from_tokens(tokens)?;
+            let directives = VariableDirectives::try_from_tokens(tokens).transpose()?;
             let selection_set = SelectionSet::from_tokens(tokens)?;
             let span = operation_type.span().merge(selection_set.span());
             Ok(Self::Explicit(ExplicitOperationDefinition {
@@ -90,7 +90,7 @@ pub struct ExplicitOperationDefinition<'a> {
     operation_type: OperationType,
     name: Option<Name<'a>>,
     variable_definitions: Option<VariableDefinitions<'a>>,
-    directives: VariableDirectives<'a>,
+    directives: Option<VariableDirectives<'a>>,
     selection_set: SelectionSet<'a>,
     span: Span,
 }
@@ -112,8 +112,8 @@ impl<'a> bluejay_core::executable::ExplicitOperationDefinition for ExplicitOpera
         self.variable_definitions.as_ref()
     }
 
-    fn directives(&self) -> &Self::Directives {
-        &self.directives
+    fn directives(&self) -> Option<&Self::Directives> {
+        self.directives.as_ref()
     }
 
     fn selection_set(&self) -> &Self::SelectionSet {
diff --git a/bluejay-parser/src/ast/executable/variable_definition.rs b/bluejay-parser/src/ast/executable/variable_definition.rs
index b307f98..4e47942 100644
--- a/bluejay-parser/src/ast/executable/variable_definition.rs
+++ b/bluejay-parser/src/ast/executable/variable_definition.rs
@@ -1,3 +1,4 @@
+use crate::ast::try_from_tokens::TryFromTokens;
 use crate::ast::{
     executable::VariableType, ConstDirectives, ConstValue, FromTokens, ParseError, Tokens, Variable,
 };
@@ -8,7 +9,7 @@ pub struct VariableDefinition<'a> {
     variable: Variable<'a>,
     r#type: VariableType<'a>,
     default_value: Option<ConstValue<'a>>,
-    directives: ConstDirectives<'a>,
+    directives: Option<ConstDirectives<'a>>,
 }
 
 impl<'a> FromTokens<'a> for VariableDefinition<'a> {
@@ -22,7 +23,7 @@ impl<'a> FromTokens<'a> for VariableDefinition<'a> {
             } else {
                 None
             };
-        let directives = ConstDirectives::from_tokens(tokens)?;
+        let directives = ConstDirectives::try_from_tokens(tokens).transpose()?;
         Ok(Self {
             variable,
             r#type,
@@ -59,8 +60,8 @@ impl<'a> bluejay_core::executable::VariableDefinition for VariableDefinition<'a>
         &self.r#type
     }
 
-    fn directives(&self) -> &Self::Directives {
-        &self.directives
+    fn directives(&self) -> Option<&Self::Directives> {
+        self.directives.as_ref()
     }
 
     fn default_value(&self) -> Option<&Self::Value> {
diff --git a/bluejay-printer/src/executable/field.rs b/bluejay-printer/src/executable/field.rs
index c59f6b4..1b9306a 100644
--- a/bluejay-printer/src/executable/field.rs
+++ b/bluejay-printer/src/executable/field.rs
@@ -27,7 +27,9 @@ impl<'a, F: Field> Display for FieldPrinter<'a, F> {
         if let Some(arguments) = field.arguments() {
             write!(f, "{}", ArgumentsPrinter::new(arguments))?;
         }
-        write!(f, "{}", DirectivesPrinter::new(field.directives()))?;
+        if let Some(directives) = field.directives() {
+            write!(f, "{}", DirectivesPrinter::new(directives))?;
+        }
         if let Some(selection_set) = field.selection_set() {
             write!(
                 f,
diff --git a/bluejay-printer/src/executable/fragment_spread.rs b/bluejay-printer/src/executable/fragment_spread.rs
index 2b29d28..86b3b42 100644
--- a/bluejay-printer/src/executable/fragment_spread.rs
+++ b/bluejay-printer/src/executable/fragment_spread.rs
@@ -24,11 +24,10 @@ impl<'a, T: FragmentSpread> Display for FragmentSpreadPrinter<'a, T> {
             indentation,
         } = *self;
         write_indent(f, indentation)?;
-        write!(
-            f,
-            "...{}{}",
-            fragment_spread.name(),
-            DirectivesPrinter::new(fragment_spread.directives())
-        )
+        write!(f, "...{}", fragment_spread.name())?;
+        if let Some(directives) = fragment_spread.directives() {
+            write!(f, "{}", DirectivesPrinter::new(directives))?;
+        };
+        Ok(())
     }
 }
diff --git a/bluejay-printer/src/executable/inline_fragment.rs b/bluejay-printer/src/executable/inline_fragment.rs
index 475f0c8..cbd120a 100644
--- a/bluejay-printer/src/executable/inline_fragment.rs
+++ b/bluejay-printer/src/executable/inline_fragment.rs
@@ -1,5 +1,5 @@
 use crate::{directive::DirectivesPrinter, executable::SelectionSetPrinter, write_indent};
-use bluejay_core::{executable::InlineFragment, AsIter};
+use bluejay_core::executable::InlineFragment;
 use std::fmt::{Display, Formatter, Result};
 
 pub(crate) struct InlineFragmentPrinter<'a, I: InlineFragment> {
@@ -27,12 +27,8 @@ impl<'a, I: InlineFragment> Display for InlineFragmentPrinter<'a, I> {
         if let Some(type_condition) = inline_fragment.type_condition() {
             write!(f, "on {}", type_condition)?;
         }
-        if !inline_fragment.directives().is_empty() {
-            write!(
-                f,
-                "{}",
-                DirectivesPrinter::new(inline_fragment.directives())
-            )?;
+        if let Some(directives) = inline_fragment.directives() {
+            write!(f, "{}", DirectivesPrinter::new(directives))?;
         }
 
         write!(
diff --git a/bluejay-printer/src/executable/variable_definition.rs b/bluejay-printer/src/executable/variable_definition.rs
index 8d387bc..cb6f15e 100644
--- a/bluejay-printer/src/executable/variable_definition.rs
+++ b/bluejay-printer/src/executable/variable_definition.rs
@@ -29,11 +29,11 @@ impl<'a, T: VariableDefinition> Display for VariableDefinitionPrinter<'a, T> {
         if let Some(default_value) = variable_definition.default_value() {
             write!(f, " = {}", ValuePrinter::new(default_value))?;
         }
-        write!(
-            f,
-            "{}",
-            DirectivesPrinter::new(variable_definition.directives())
-        )
+
+        if let Some(directives) = variable_definition.directives() {
+            write!(f, "{}", DirectivesPrinter::new(directives))?;
+        };
+        Ok(())
     }
 }
 
diff --git a/bluejay-validator/src/executable/document/orchestrator.rs b/bluejay-validator/src/executable/document/orchestrator.rs
index b3fd08e..c0dad7a 100644
--- a/bluejay-validator/src/executable/document/orchestrator.rs
+++ b/bluejay-validator/src/executable/document/orchestrator.rs
@@ -103,11 +103,14 @@ impl<'a, E: ExecutableDocument, S: SchemaDefinition, V: Visitor<'a, E, S>>
         if let Some(type_condition) = type_condition {
             self.visit_selection_set(fragment_definition.selection_set(), type_condition, &path);
         }
-        self.visit_variable_directives(
-            fragment_definition.directives(),
-            DirectiveLocation::FragmentDefinition,
-            &path,
-        );
+        if let Some(directives) = fragment_definition.directives() {
+            self.visit_variable_directives(
+                directives,
+                DirectiveLocation::FragmentDefinition,
+                &path,
+            );
+        }
+
         self.visitor.visit_fragment_definition(fragment_definition);
     }
 
@@ -148,7 +151,9 @@ impl<'a, E: ExecutableDocument, S: SchemaDefinition, V: Visitor<'a, E, S>>
         path: &Path<'a, E>,
     ) {
         self.visitor.visit_field(field, field_definition, path);
-        self.visit_variable_directives(field.directives(), DirectiveLocation::Field, path);
+        if let Some(directives) = field.directives() {
+            self.visit_variable_directives(directives, DirectiveLocation::Field, path);
+        }
 
         if let Some((arguments, arguments_definition)) = field
             .arguments()
@@ -231,11 +236,9 @@ impl<'a, E: ExecutableDocument, S: SchemaDefinition, V: Visitor<'a, E, S>>
         scoped_type: TypeDefinitionReference<'a, S::TypeDefinition>,
         path: &Path<'a, E>,
     ) {
-        self.visit_variable_directives(
-            inline_fragment.directives(),
-            DirectiveLocation::InlineFragment,
-            path,
-        );
+        if let Some(directives) = inline_fragment.directives() {
+            self.visit_variable_directives(directives, DirectiveLocation::InlineFragment, path);
+        }
 
         let fragment_type = if let Some(type_condition) = inline_fragment.type_condition() {
             self.schema_definition.get_type_definition(type_condition)
@@ -257,11 +260,10 @@ impl<'a, E: ExecutableDocument, S: SchemaDefinition, V: Visitor<'a, E, S>>
         scoped_type: TypeDefinitionReference<'a, S::TypeDefinition>,
         path: &Path<'a, E>,
     ) {
-        self.visit_variable_directives(
-            fragment_spread.directives(),
-            DirectiveLocation::FragmentSpread,
-            path,
-        );
+        if let Some(directives) = fragment_spread.directives() {
+            self.visit_variable_directives(directives, DirectiveLocation::FragmentSpread, path);
+        }
+
         self.visitor
             .visit_fragment_spread(fragment_spread, scoped_type, path);
         // fragment will get checked when definition is visited
@@ -271,10 +273,9 @@ impl<'a, E: ExecutableDocument, S: SchemaDefinition, V: Visitor<'a, E, S>>
         self.visitor
             .visit_variable_definitions(variable_definitions);
         variable_definitions.iter().for_each(|variable_definition| {
-            self.visit_const_directives(
-                variable_definition.directives(),
-                DirectiveLocation::VariableDefinition,
-            );
+            if let Some(directives) = variable_definition.directives() {
+                self.visit_const_directives(directives, DirectiveLocation::VariableDefinition);
+            }
             self.visitor.visit_variable_definition(variable_definition);
         });
     }
diff --git a/bluejay-validator/src/executable/operation/orchestrator.rs b/bluejay-validator/src/executable/operation/orchestrator.rs
index 5b22649..5b22bff 100644
--- a/bluejay-validator/src/executable/operation/orchestrator.rs
+++ b/bluejay-validator/src/executable/operation/orchestrator.rs
@@ -138,7 +138,10 @@ impl<
         owner_type: TypeDefinitionReference<'a, S::TypeDefinition>,
         included: bool,
     ) {
-        let included = included && self.evaluate_selection_inclusion(field.directives());
+        let included = included
+            && field.directives().map_or(true, |directives| {
+                self.evaluate_selection_inclusion(directives)
+            });
 
         self.visitor
             .visit_field(field, field_definition, owner_type, included);
@@ -162,7 +165,10 @@ impl<
         scoped_type: TypeDefinitionReference<'a, S::TypeDefinition>,
         included: bool,
     ) {
-        let included = included && self.evaluate_selection_inclusion(inline_fragment.directives());
+        let included = included
+            && inline_fragment.directives().map_or(true, |directives| {
+                self.evaluate_selection_inclusion(directives)
+            });
 
         let fragment_type = if let Some(type_condition) = inline_fragment.type_condition() {
             self.schema_definition.get_type_definition(type_condition)
@@ -176,7 +182,10 @@ impl<
     }
 
     fn visit_fragment_spread(&mut self, fragment_spread: &'a E::FragmentSpread, included: bool) {
-        let included = included && self.evaluate_selection_inclusion(fragment_spread.directives());
+        let included = included
+            && fragment_spread.directives().map_or(true, |directives| {
+                self.evaluate_selection_inclusion(directives)
+            });
         if self
             .currently_spread_fragments
             .insert(fragment_spread.name())


Test patch: