<commit_before># frozen_string_literal: true

module GraphQL
  module Execution
    class Interpreter
      # I think it would be even better if we could somehow make
      # `continue_field` not recursive. "Trampolining" it somehow.
      class Runtime
        # @return [GraphQL::Query]
        attr_reader :query

        # @return [Class]
        attr_reader :schema

        # @return [GraphQL::Query::Context]
        attr_reader :context

        def initialize(query:, lazies:, response:)
          @query = query
          @schema = query.schema
          @context = query.context
          @lazies = lazies
          @response = response
          @dead_paths = {}
          @types_at_paths = {}
        end

        def final_value
          @response.final_value
        end

        def inspect
          "#<#{self.class.name} response=#{@response.inspect}>"
        end

        # This _begins_ the execution. Some deferred work
        # might be stored up in {@lazies}.
        # @return [void]
        def run_eager
          root_operation = query.selected_operation
          root_op_type = root_operation.operation_type || "query"
          legacy_root_type = schema.root_type_for_operation(root_op_type)
          root_type = legacy_root_type.metadata[:type_class] || raise("Invariant: type must be class-based: #{legacy_root_type}")
          object_proxy = root_type.authorized_new(query.root_value, context)

          path = []
          evaluate_selections(path, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
        end

        private

        def gather_selections(owner_type, selections, selections_by_name)
          selections.each do |node|
            case node
            when GraphQL::Language::Nodes::Field
              if passes_skip_and_include?(node)
                response_key = node.alias || node.name
                s = selections_by_name[response_key] ||= []
                s << node
              end
            when GraphQL::Language::Nodes::InlineFragment
              if passes_skip_and_include?(node)
                include_fragmment = if node.type
                  type_defn = schema.types[node.type.name]
                  type_defn = type_defn.metadata[:type_class]
                  possible_types = query.warden.possible_types(type_defn).map { |t| t.metadata[:type_class] }
                  possible_types.include?(owner_type)
                else
                  true
                end
                if include_fragmment
                  gather_selections(owner_type, node.selections, selections_by_name)
                end
              end
            when GraphQL::Language::Nodes::FragmentSpread
              if passes_skip_and_include?(node)
                fragment_def = query.fragments[node.name]
                type_defn = schema.types[fragment_def.type.name]
                type_defn = type_defn.metadata[:type_class]
                possible_types = schema.possible_types(type_defn).map { |t| t.metadata[:type_class] }
                if possible_types.include?(owner_type)
                  gather_selections(owner_type, fragment_def.selections, selections_by_name)
                end
              end
            else
              raise "Invariant: unexpected selection class: #{node.class}"
            end
          end
        end

        def evaluate_selections(path, owner_object, owner_type, selections, root_operation_type: nil)
          selections_by_name = {}
          owner_type = resolve_if_late_bound_type(owner_type)
          gather_selections(owner_type, selections, selections_by_name)
          selections_by_name.each do |result_name, fields|
            ast_node = fields.first
            field_name = ast_node.name
            field_defn = owner_type.fields[field_name]
            is_introspection = false
            if field_defn.nil?
              field_defn = if owner_type == schema.query.metadata[:type_class] && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
                is_introspection = true
                entry_point_field.metadata[:type_class]
              elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
                is_introspection = true
                dynamic_field.metadata[:type_class]
              else
                raise "Invariant: no field for #{owner_type}.#{field_name}"
              end
            end

            return_type = resolve_if_late_bound_type(field_defn.type)

            next_path = [*path, result_name].freeze
            # This seems janky, but we need to know
            # the field's return type at this path in order
            # to propagate `null`
            set_type_at_path(next_path, return_type)

            object = owner_object

            if is_introspection
              object = field_defn.owner.authorized_new(object, context)
            end

            kwarg_arguments = arguments(object, field_defn, ast_node)
            # It might turn out that making arguments for every field is slow.
            # If we have to cache them, we'll need a more subtle approach here.
            if field_defn.extras.include?(:ast_node)
              kwarg_arguments[:ast_node] = ast_node
            end
            if field_defn.extras.include?(:execution_errors)
              kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
            end

            next_selections = fields.inject([]) { |memo, f| memo.concat(f.selections) }

            app_result = query.trace("execute_field", {field: field_defn, path: next_path}) do
              field_defn.resolve(object, kwarg_arguments, context)
            end

            after_lazy(app_result, field: field_defn, path: next_path, eager: root_operation_type == "mutation") do |inner_result|
              should_continue, continue_value = continue_value(next_path, inner_result, field_defn, return_type, ast_node)
              if should_continue
                continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections)
              end
            end
          end
        end

        def continue_value(path, value, field, as_type, ast_node)
          if value.nil? || value.is_a?(GraphQL::ExecutionError)
            if value.nil?
              if as_type.non_null?
                err = GraphQL::InvalidNullError.new(field.owner, field, value)
                write_in_response(path, err, propagating_nil: true)
              else
                write_in_response(path, nil)
              end
            else
              value.path ||= path
              value.ast_node ||= ast_node
              write_in_response(path, value, propagating_nil: as_type.non_null?)
            end
            false
          elsif value.is_a?(Array) && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
            value.each do |v|
              v.path ||= path
              v.ast_node ||= ast_node
            end
            write_in_response(path, value, propagating_nil: as_type.non_null?)
            false
          elsif value.is_a?(GraphQL::UnauthorizedError)
            # this hook might raise & crash, or it might return
            # a replacement value
            next_value = begin
              schema.unauthorized_object(value)
            rescue GraphQL::ExecutionError => err
              err
            end

            continue_value(path, next_value, field, as_type, ast_node)
          elsif GraphQL::Execution::Execute::SKIP == value
            false
          else
            return true, value
          end
        end

        def continue_field(path, value, field, type, ast_node, next_selections)
          type = resolve_if_late_bound_type(type)

          case type.kind
          when TypeKinds::SCALAR, TypeKinds::ENUM
            r = type.coerce_result(value, context)
            write_in_response(path, r)
          when TypeKinds::UNION, TypeKinds::INTERFACE
            resolved_type = query.resolve_type(type, value)
            possible_types = query.possible_types(type)

            if !possible_types.include?(resolved_type)
              parent_type = field.owner
              type_error = GraphQL::UnresolvedTypeError.new(value, field, parent_type, resolved_type, possible_types)
              schema.type_error(type_error, context)
              write_in_response(path, nil, propagating_nil: field.type.non_null?)
            else
              resolved_type = resolved_type.metadata[:type_class]
              continue_field(path, value, field, resolved_type, ast_node, next_selections)
            end
          when TypeKinds::OBJECT
            object_proxy = begin
              type.authorized_new(value, context)
            rescue GraphQL::ExecutionError => err
              err
            end
            after_lazy(object_proxy, path: path, field: field) do |inner_object|
              should_continue, continue_value = continue_value(path, inner_object, field, type, ast_node)
              if should_continue
                write_in_response(path, {})
                evaluate_selections(path, continue_value, type, next_selections)
              end
            end
          when TypeKinds::LIST
            write_in_response(path, [])
            inner_type = type.of_type
            value.each_with_index.each do |inner_value, idx|
              next_path = [*path, idx].freeze
              set_type_at_path(next_path, inner_type)
              after_lazy(inner_value, path: next_path, field: field) do |inner_inner_value|
                should_continue, continue_value = continue_value(next_path, inner_inner_value, field, inner_type, ast_node)
                if should_continue
                  continue_field(next_path, continue_value, field, inner_type, ast_node, next_selections)
                end
              end
            end
          when TypeKinds::NON_NULL
            inner_type = type.of_type
            # Don't `set_type_at_path` because we want the static type,
            # we're going to use that to determine whether a `nil` should be propagated or not.
            continue_field(path, value, field, inner_type, ast_node, next_selections)
          else
            raise "Invariant: Unhandled type kind #{type.kind} (#{type})"
          end
        end

        def passes_skip_and_include?(node)
          # Eventually this should actually call out to the directives
          # instead of having magical hard-coded behavior.
          node.directives.each do |dir|
            dir_defn = schema.directives.fetch(dir.name)
            if dir.name == "skip" && arguments(nil, dir_defn, dir)[:if] == true
              return false
            elsif dir.name == "include" && arguments(nil, dir_defn, dir)[:if] == false
              return false
            end
          end
          true
        end

        def resolve_if_late_bound_type(type)
          if type.is_a?(GraphQL::Schema::LateBoundType)
            query.warden.get_type(type.name).metadata[:type_class]
          else
            type
          end
        end

        # @param obj [Object] Some user-returned value that may want to be batched
        # @param path [Array<String>]
        # @param field [GraphQL::Schema::Field]
        # @param eager [Boolean] Set to `true` for mutation root fields only
        # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
        def after_lazy(obj, field:, path:, eager: false)
          if schema.lazy?(obj)
            lazy = GraphQL::Execution::Lazy.new do
              # Wrap the execution of _this_ method with tracing,
              # but don't wrap the continuation below
              inner_obj = query.trace("execute_field_lazy", {field: field, path: path}) do
                method_name = schema.lazy_method_name(obj)
                begin
                  obj.public_send(method_name)
                rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
                  yield(err)
                end
              end
              after_lazy(inner_obj, field: field, path: path, eager: eager) do |really_inner_obj|
                yield(really_inner_obj)
              end
            end

            if eager
              lazy.value
            else
              @lazies << lazy
            end
          else
            yield(obj)
          end
        end
        def arguments(graphql_object, arg_owner, ast_node)
          kwarg_arguments = {}
          ast_node.arguments.each do |arg|
            arg_defn = arg_owner.arguments[arg.name]
            # Need to distinguish between client-provided `nil`
            # and nothing-at-all
            is_present, value = arg_to_value(graphql_object, arg_defn.type, arg.value)
            if is_present
              # This doesn't apply to directives, which are legacy
              # Can remove this when Skip and Include use classes or something.
              if graphql_object
                value = arg_defn.prepare_value(graphql_object, value)
              end
              kwarg_arguments[arg_defn.keyword] = value
            end
          end
          arg_owner.arguments.each do |name, arg_defn|
            if arg_defn.default_value? && !kwarg_arguments.key?(arg_defn.keyword)
              kwarg_arguments[arg_defn.keyword] = arg_defn.default_value
            end
          end
          kwarg_arguments
        end

        # Get a Ruby-ready value from a client query.<commit_msg>fix lint error<commit_after># frozen_string_literal: true

module GraphQL
  module Execution
    class Interpreter
      # I think it would be even better if we could somehow make
      # `continue_field` not recursive. "Trampolining" it somehow.
      class Runtime
        # @return [GraphQL::Query]
        attr_reader :query

        # @return [Class]
        attr_reader :schema

        # @return [GraphQL::Query::Context]
        attr_reader :context

        def initialize(query:, lazies:, response:)
          @query = query
          @schema = query.schema
          @context = query.context
          @lazies = lazies
          @response = response
          @dead_paths = {}
          @types_at_paths = {}
        end

        def final_value
          @response.final_value
        end

        def inspect
          "#<#{self.class.name} response=#{@response.inspect}>"
        end

        # This _begins_ the execution. Some deferred work
        # might be stored up in {@lazies}.
        # @return [void]
        def run_eager
          root_operation = query.selected_operation
          root_op_type = root_operation.operation_type || "query"
          legacy_root_type = schema.root_type_for_operation(root_op_type)
          root_type = legacy_root_type.metadata[:type_class] || raise("Invariant: type must be class-based: #{legacy_root_type}")
          object_proxy = root_type.authorized_new(query.root_value, context)

          path = []
          evaluate_selections(path, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
        end

        private

        def gather_selections(owner_type, selections, selections_by_name)
          selections.each do |node|
            case node
            when GraphQL::Language::Nodes::Field
              if passes_skip_and_include?(node)
                response_key = node.alias || node.name
                s = selections_by_name[response_key] ||= []
                s << node
              end
            when GraphQL::Language::Nodes::InlineFragment
              if passes_skip_and_include?(node)
                include_fragmment = if node.type
                  type_defn = schema.types[node.type.name]
                  type_defn = type_defn.metadata[:type_class]
                  possible_types = query.warden.possible_types(type_defn).map { |t| t.metadata[:type_class] }
                  possible_types.include?(owner_type)
                else
                  true
                end
                if include_fragmment
                  gather_selections(owner_type, node.selections, selections_by_name)
                end
              end
            when GraphQL::Language::Nodes::FragmentSpread
              if passes_skip_and_include?(node)
                fragment_def = query.fragments[node.name]
                type_defn = schema.types[fragment_def.type.name]
                type_defn = type_defn.metadata[:type_class]
                possible_types = schema.possible_types(type_defn).map { |t| t.metadata[:type_class] }
                if possible_types.include?(owner_type)
                  gather_selections(owner_type, fragment_def.selections, selections_by_name)
                end
              end
            else
              raise "Invariant: unexpected selection class: #{node.class}"
            end
          end
        end

        def evaluate_selections(path, owner_object, owner_type, selections, root_operation_type: nil)
          selections_by_name = {}
          owner_type = resolve_if_late_bound_type(owner_type)
          gather_selections(owner_type, selections, selections_by_name)
          selections_by_name.each do |result_name, fields|
            ast_node = fields.first
            field_name = ast_node.name
            field_defn = owner_type.fields[field_name]
            is_introspection = false
            if field_defn.nil?
              field_defn = if owner_type == schema.query.metadata[:type_class] && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
                is_introspection = true
                entry_point_field.metadata[:type_class]
              elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
                is_introspection = true
                dynamic_field.metadata[:type_class]
              else
                raise "Invariant: no field for #{owner_type}.#{field_name}"
              end
            end

            return_type = resolve_if_late_bound_type(field_defn.type)

            next_path = [*path, result_name].freeze
            # This seems janky, but we need to know
            # the field's return type at this path in order
            # to propagate `null`
            set_type_at_path(next_path, return_type)

            object = owner_object

            if is_introspection
              object = field_defn.owner.authorized_new(object, context)
            end

            kwarg_arguments = arguments(object, field_defn, ast_node)
            # It might turn out that making arguments for every field is slow.
            # If we have to cache them, we'll need a more subtle approach here.
            if field_defn.extras.include?(:ast_node)
              kwarg_arguments[:ast_node] = ast_node
            end
            if field_defn.extras.include?(:execution_errors)
              kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
            end

            next_selections = fields.inject([]) { |memo, f| memo.concat(f.selections) }

            app_result = query.trace("execute_field", {field: field_defn, path: next_path}) do
              field_defn.resolve(object, kwarg_arguments, context)
            end

            after_lazy(app_result, field: field_defn, path: next_path, eager: root_operation_type == "mutation") do |inner_result|
              should_continue, continue_value = continue_value(next_path, inner_result, field_defn, return_type, ast_node)
              if should_continue
                continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections)
              end
            end
          end
        end

        def continue_value(path, value, field, as_type, ast_node)
          if value.nil? || value.is_a?(GraphQL::ExecutionError)
            if value.nil?
              if as_type.non_null?
                err = GraphQL::InvalidNullError.new(field.owner, field, value)
                write_in_response(path, err, propagating_nil: true)
              else
                write_in_response(path, nil)
              end
            else
              value.path ||= path
              value.ast_node ||= ast_node
              write_in_response(path, value, propagating_nil: as_type.non_null?)
            end
            false
          elsif value.is_a?(Array) && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
            value.each do |v|
              v.path ||= path
              v.ast_node ||= ast_node
            end
            write_in_response(path, value, propagating_nil: as_type.non_null?)
            false
          elsif value.is_a?(GraphQL::UnauthorizedError)
            # this hook might raise & crash, or it might return
            # a replacement value
            next_value = begin
              schema.unauthorized_object(value)
            rescue GraphQL::ExecutionError => err
              err
            end

            continue_value(path, next_value, field, as_type, ast_node)
          elsif GraphQL::Execution::Execute::SKIP == value
            false
          else
            return true, value
          end
        end

        def continue_field(path, value, field, type, ast_node, next_selections)
          type = resolve_if_late_bound_type(type)

          case type.kind
          when TypeKinds::SCALAR, TypeKinds::ENUM
            r = type.coerce_result(value, context)
            write_in_response(path, r)
          when TypeKinds::UNION, TypeKinds::INTERFACE
            resolved_type = query.resolve_type(type, value)
            possible_types = query.possible_types(type)

            if !possible_types.include?(resolved_type)
              parent_type = field.owner
              type_error = GraphQL::UnresolvedTypeError.new(value, field, parent_type, resolved_type, possible_types)
              schema.type_error(type_error, context)
              write_in_response(path, nil, propagating_nil: field.type.non_null?)
            else
              resolved_type = resolved_type.metadata[:type_class]
              continue_field(path, value, field, resolved_type, ast_node, next_selections)
            end
          when TypeKinds::OBJECT
            object_proxy = begin
              type.authorized_new(value, context)
            rescue GraphQL::ExecutionError => err
              err
            end
            after_lazy(object_proxy, path: path, field: field) do |inner_object|
              should_continue, continue_value = continue_value(path, inner_object, field, type, ast_node)
              if should_continue
                write_in_response(path, {})
                evaluate_selections(path, continue_value, type, next_selections)
              end
            end
          when TypeKinds::LIST
            write_in_response(path, [])
            inner_type = type.of_type
            value.each_with_index.each do |inner_value, idx|
              next_path = [*path, idx].freeze
              set_type_at_path(next_path, inner_type)
              after_lazy(inner_value, path: next_path, field: field) do |inner_inner_value|
                should_continue, continue_value = continue_value(next_path, inner_inner_value, field, inner_type, ast_node)
                if should_continue
                  continue_field(next_path, continue_value, field, inner_type, ast_node, next_selections)
                end
              end
            end
          when TypeKinds::NON_NULL
            inner_type = type.of_type
            # Don't `set_type_at_path` because we want the static type,
            # we're going to use that to determine whether a `nil` should be propagated or not.
            continue_field(path, value, field, inner_type, ast_node, next_selections)
          else
            raise "Invariant: Unhandled type kind #{type.kind} (#{type})"
          end
        end

        def passes_skip_and_include?(node)
          # Eventually this should actually call out to the directives
          # instead of having magical hard-coded behavior.
          node.directives.each do |dir|
            dir_defn = schema.directives.fetch(dir.name)
            if dir.name == "skip" && arguments(nil, dir_defn, dir)[:if] == true
              return false
            elsif dir.name == "include" && arguments(nil, dir_defn, dir)[:if] == false
              return false
            end
          end
          true
        end

        def resolve_if_late_bound_type(type)
          if type.is_a?(GraphQL::Schema::LateBoundType)
            query.warden.get_type(type.name).metadata[:type_class]
          else
            type
          end
        end

        # @param obj [Object] Some user-returned value that may want to be batched
        # @param path [Array<String>]
        # @param field [GraphQL::Schema::Field]
        # @param eager [Boolean] Set to `true` for mutation root fields only
        # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
        def after_lazy(obj, field:, path:, eager: false)
          if schema.lazy?(obj)
            lazy = GraphQL::Execution::Lazy.new do
              # Wrap the execution of _this_ method with tracing,
              # but don't wrap the continuation below
              inner_obj = query.trace("execute_field_lazy", {field: field, path: path}) do
                method_name = schema.lazy_method_name(obj)
                begin
                  obj.public_send(method_name)
                rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
                  yield(err)
                end
              end
              after_lazy(inner_obj, field: field, path: path, eager: eager) do |really_inner_obj|
                yield(really_inner_obj)
              end
            end

            if eager
              lazy.value
            else
              @lazies << lazy
            end
          else
            yield(obj)
          end
        end

        def arguments(graphql_object, arg_owner, ast_node)
          kwarg_arguments = {}
          ast_node.arguments.each do |arg|
            arg_defn = arg_owner.arguments[arg.name]
            # Need to distinguish between client-provided `nil`
            # and nothing-at-all
            is_present, value = arg_to_value(graphql_object, arg_defn.type, arg.value)
            if is_present
              # This doesn't apply to directives, which are legacy
              # Can remove this when Skip and Include use classes or something.
              if graphql_object
                value = arg_defn.prepare_value(graphql_object, value)
              end
              kwarg_arguments[arg_defn.keyword] = value
            end
          end
          arg_owner.arguments.each do |name, arg_defn|
            if arg_defn.default_value? && !kwarg_arguments.key?(arg_defn.keyword)
              kwarg_arguments[arg_defn.keyword] = arg_defn.default_value
            end
          end
          kwarg_arguments
        end

        # Get a Ruby-ready value from a client query.
{'fraction_non_alphanumeric': 0.07564942232688204, 'fraction_numerical': 0.0, 'mean_word_length': 1.8846317130120993, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '403823', 'n_tokens_mistral': 7448, 'n_tokens_neox': 7280, 'n_words': 1721}