Skip to content

Commit 881655e

Browse files
authored
Merge pull request #5587 from rmosolgo/refactor-validation-visitor-stacks
Replace validation visitor stacks with single-slot variables
2 parents e38defe + 91de7fd commit 881655e

File tree

7 files changed

+98
-75
lines changed

7 files changed

+98
-75
lines changed

lib/graphql/static_validation/base_visitor.rb

Lines changed: 90 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,26 @@ module StaticValidation
44
class BaseVisitor < GraphQL::Language::StaticVisitor
55
def initialize(document, context)
66
@path = []
7-
@object_types = []
8-
@directives = []
9-
@field_definitions = []
10-
@argument_definitions = []
11-
@directive_definitions = []
7+
@path_depth = 0
8+
@current_object_type = nil
9+
@parent_object_type = nil
10+
@current_field_definition = nil
11+
@current_argument_definition = nil
12+
@parent_argument_definition = nil
13+
@current_directive_definition = nil
1214
@context = context
1315
@types = context.query.types
1416
@schema = context.schema
17+
@inline_fragment_paths = {}
18+
@field_unwrapped_types = {}.compare_by_identity
1519
super(document)
1620
end
1721

1822
attr_reader :context
1923

20-
# @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
21-
attr_reader :object_types
22-
2324
# @return [Array<String>] The nesting of the current position in the AST
2425
def path
25-
@path.dup
26+
@path[0, @path_depth]
2627
end
2728

2829
# Build a class to visit the AST and perform validation,
@@ -55,135 +56,158 @@ def self.including_rules(rules)
5556
module ContextMethods
5657
def on_operation_definition(node, parent)
5758
object_type = @schema.root_type_for_operation(node.operation_type)
58-
push_type(object_type)
59-
@path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
59+
prev_parent_ot = @parent_object_type
60+
@parent_object_type = @current_object_type
61+
@current_object_type = object_type
62+
@path[@path_depth] = "#{node.operation_type}#{node.name ? " #{node.name}" : ""}"
63+
@path_depth += 1
6064
super
61-
@object_types.pop
62-
@path.pop
65+
@current_object_type = @parent_object_type
66+
@parent_object_type = prev_parent_ot
67+
@path_depth -= 1
6368
end
6469

6570
def on_fragment_definition(node, parent)
66-
on_fragment_with_type(node) do
67-
@path.push("fragment #{node.name}")
68-
super
71+
object_type = if node.type
72+
@types.type(node.type.name)
73+
else
74+
@current_object_type
6975
end
76+
prev_parent_ot = @parent_object_type
77+
@parent_object_type = @current_object_type
78+
@current_object_type = object_type
79+
@path[@path_depth] = "fragment #{node.name}"
80+
@path_depth += 1
81+
super
82+
@current_object_type = @parent_object_type
83+
@parent_object_type = prev_parent_ot
84+
@path_depth -= 1
7085
end
7186

87+
INLINE_FRAGMENT_NO_TYPE = "..."
88+
7289
def on_inline_fragment(node, parent)
73-
on_fragment_with_type(node) do
74-
@path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}")
75-
super
90+
if node.type
91+
object_type = @types.type(node.type.name)
92+
@path[@path_depth] = @inline_fragment_paths[node.type.name] ||= -"... on #{node.type.to_query_string}"
93+
@path_depth += 1
94+
else
95+
object_type = @current_object_type
96+
@path[@path_depth] = INLINE_FRAGMENT_NO_TYPE
97+
@path_depth += 1
7698
end
99+
prev_parent_ot = @parent_object_type
100+
@parent_object_type = @current_object_type
101+
@current_object_type = object_type
102+
super
103+
@current_object_type = @parent_object_type
104+
@parent_object_type = prev_parent_ot
105+
@path_depth -= 1
77106
end
78107

79108
def on_field(node, parent)
80-
parent_type = @object_types.last
109+
parent_type = @current_object_type
81110
field_definition = @types.field(parent_type, node.name)
82-
@field_definitions.push(field_definition)
83-
if !field_definition.nil?
84-
next_object_type = field_definition.type.unwrap
85-
push_type(next_object_type)
111+
prev_field_definition = @current_field_definition
112+
@current_field_definition = field_definition
113+
prev_parent_ot = @parent_object_type
114+
@parent_object_type = @current_object_type
115+
if field_definition
116+
@current_object_type = @field_unwrapped_types[field_definition] ||= field_definition.type.unwrap
86117
else
87-
push_type(nil)
118+
@current_object_type = nil
88119
end
89-
@path.push(node.alias || node.name)
120+
@path[@path_depth] = node.alias || node.name
121+
@path_depth += 1
90122
super
91-
@field_definitions.pop
92-
@object_types.pop
93-
@path.pop
123+
@current_field_definition = prev_field_definition
124+
@current_object_type = @parent_object_type
125+
@parent_object_type = prev_parent_ot
126+
@path_depth -= 1
94127
end
95128

96129
def on_directive(node, parent)
97130
directive_defn = @context.schema_directives[node.name]
98-
@directive_definitions.push(directive_defn)
131+
prev_directive_definition = @current_directive_definition
132+
@current_directive_definition = directive_defn
99133
super
100-
@directive_definitions.pop
134+
@current_directive_definition = prev_directive_definition
101135
end
102136

103137
def on_argument(node, parent)
104-
argument_defn = if (arg = @argument_definitions.last)
138+
argument_defn = if (arg = @current_argument_definition)
105139
arg_type = arg.type.unwrap
106140
if arg_type.kind.input_object?
107141
@types.argument(arg_type, node.name)
108142
else
109143
nil
110144
end
111-
elsif (directive_defn = @directive_definitions.last)
145+
elsif (directive_defn = @current_directive_definition)
112146
@types.argument(directive_defn, node.name)
113-
elsif (field_defn = @field_definitions.last)
147+
elsif (field_defn = @current_field_definition)
114148
@types.argument(field_defn, node.name)
115149
else
116150
nil
117151
end
118152

119-
@argument_definitions.push(argument_defn)
120-
@path.push(node.name)
153+
prev_parent = @parent_argument_definition
154+
@parent_argument_definition = @current_argument_definition
155+
@current_argument_definition = argument_defn
156+
@path[@path_depth] = node.name
157+
@path_depth += 1
121158
super
122-
@argument_definitions.pop
123-
@path.pop
159+
@current_argument_definition = @parent_argument_definition
160+
@parent_argument_definition = prev_parent
161+
@path_depth -= 1
124162
end
125163

126164
def on_fragment_spread(node, parent)
127-
@path.push("... #{node.name}")
165+
@path[@path_depth] = "... #{node.name}"
166+
@path_depth += 1
128167
super
129-
@path.pop
168+
@path_depth -= 1
130169
end
131170

132171
def on_input_object(node, parent)
133-
arg_defn = @argument_definitions.last
172+
arg_defn = @current_argument_definition
134173
if arg_defn && arg_defn.type.list?
135-
@path.push(parent.children.index(node))
174+
@path[@path_depth] = parent.children.index(node)
175+
@path_depth += 1
136176
super
137-
@path.pop
177+
@path_depth -= 1
138178
else
139179
super
140180
end
141181
end
142182

143183
# @return [GraphQL::BaseType] The current object type
144184
def type_definition
145-
@object_types.last
185+
@current_object_type
146186
end
147187

148188
# @return [GraphQL::BaseType] The type which the current type came from
149189
def parent_type_definition
150-
@object_types[-2]
190+
@parent_object_type
151191
end
152192

153193
# @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
154194
def field_definition
155-
@field_definitions.last
195+
@current_field_definition
156196
end
157197

158198
# @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
159199
def directive_definition
160-
@directive_definitions.last
200+
@current_directive_definition
161201
end
162202

163203
# @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
164204
def argument_definition
165-
# Don't get the _last_ one because that's the current one.
166-
# Get the second-to-last one, which is the parent of the current one.
167-
@argument_definitions[-2]
205+
# Return the parent argument definition (not the current one).
206+
@parent_argument_definition
168207
end
169208

170209
private
171210

172-
def on_fragment_with_type(node)
173-
object_type = if node.type
174-
@types.type(node.type.name)
175-
else
176-
@object_types.last
177-
end
178-
push_type(object_type)
179-
yield(node)
180-
@object_types.pop
181-
@path.pop
182-
end
183-
184-
def push_type(t)
185-
@object_types.push(t)
186-
end
187211
end
188212

189213
private
@@ -192,7 +216,7 @@ def add_error(error, path: nil)
192216
if @context.too_many_errors?
193217
throw :too_many_validation_errors
194218
end
195-
error.path ||= (path || @path.dup)
219+
error.path ||= (path || @path[0, @path_depth])
196220
context.errors << error
197221
end
198222

lib/graphql/static_validation/rules/argument_literals_are_compatible.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def on_argument(node, parent)
1212
return
1313
end
1414

15-
if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path.size) == @path }
15+
if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path_depth) == @path[0, @path_depth] }
1616
parent_defn = parent_definition(parent)
1717

1818
if parent_defn && (arg_defn = @types.argument(parent_defn, node.name))

lib/graphql/static_validation/rules/fields_are_defined_on_type.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module GraphQL
33
module StaticValidation
44
module FieldsAreDefinedOnType
55
def on_field(node, parent)
6-
parent_type = @object_types[-2]
6+
parent_type = @parent_object_type
77
field = context.query.types.field(parent_type, node.name)
88

99
if field.nil?

lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ module FieldsHaveAppropriateSelections
77
include GraphQL::StaticValidation::Error::ErrorHelper
88

99
def on_field(node, parent)
10-
field_defn = field_definition
11-
if validate_field_selections(node, field_defn.type.unwrap)
10+
if validate_field_selections(node, @current_object_type)
1211
super
1312
end
1413
end

lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ def initialize(*)
88
end
99

1010
def on_inline_fragment(node, parent)
11-
fragment_parent = context.object_types[-2]
12-
fragment_child = context.object_types.last
11+
fragment_parent = @parent_object_type
12+
fragment_child = @current_object_type
1313
if fragment_child
1414
validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path)
1515
end
1616
super
1717
end
1818

1919
def on_fragment_spread(node, parent)
20-
fragment_parent = context.object_types.last
20+
fragment_parent = @current_object_type
2121
@spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path)
2222
super
2323
end

lib/graphql/static_validation/rules/required_arguments_are_present.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def initialize(*)
88
end
99

1010
def on_field(node, _parent)
11-
assert_required_args(node, field_definition)
11+
assert_required_args(node, @current_field_definition)
1212
super
1313
end
1414

lib/graphql/static_validation/validation_context.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def initialize(query, visitor_class, max_errors)
3232
# TODO stop using def_delegators because of Array allocations
3333
def_delegators :@visitor,
3434
:path, :type_definition, :field_definition, :argument_definition,
35-
:parent_type_definition, :directive_definition, :object_types, :dependencies
35+
:parent_type_definition, :directive_definition, :dependencies
3636

3737
def on_dependency_resolve(&handler)
3838
@on_dependency_resolve_handlers << handler

0 commit comments

Comments
 (0)