From b967e7167e9cc7bd8f43476ca66aa66a18de5491 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Tue, 11 May 2021 15:06:20 -0700 Subject: [PATCH] Preserve sources of variable values Depends on #3074 By way of introducing type `VariableValues`, allows `getVariableValues` to return both the coerced values as well as the original sources, which are then made available in `ExecutionContext`. While variable sources are not used directly here, they're used directly in #3065. This PR is pulled out as a pre-req to aid review --- src/execution/execute.d.ts | 4 +- src/execution/execute.js | 13 +-- src/execution/values.d.ts | 25 ++++-- src/execution/values.js | 62 ++++++++------ src/type/definition.d.ts | 8 +- src/type/definition.js | 9 +- .../__tests__/coerceInputValue-test.js | 84 +++++++++++++++---- src/utilities/coerceInputValue.d.ts | 5 +- src/utilities/coerceInputValue.js | 35 ++++---- src/utilities/valueFromASTUntyped.d.ts | 4 +- src/utilities/valueFromASTUntyped.js | 4 +- 11 files changed, 170 insertions(+), 83 deletions(-) diff --git a/src/execution/execute.d.ts b/src/execution/execute.d.ts index 1db6f20b06c..7386a31b364 100644 --- a/src/execution/execute.d.ts +++ b/src/execution/execute.d.ts @@ -23,6 +23,8 @@ import { GraphQLObjectType, } from '../type/definition'; +import { VariableValues } from './values'; + /** * Terminology * @@ -55,7 +57,7 @@ export interface ExecutionContext { contextValue: unknown; fragments: ObjMap; operation: OperationDefinitionNode; - variableValues: { [key: string]: unknown }; + variableValues: VariableValues; fieldResolver: GraphQLFieldResolver; typeResolver: GraphQLTypeResolver; errors: Array; diff --git a/src/execution/execute.js b/src/execution/execute.js index f3a88c8c0fd..11096b404a9 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -60,6 +60,7 @@ import { import { typeFromAST } from '../utilities/typeFromAST'; import { getOperationRootType } from '../utilities/getOperationRootType'; +import type { VariableValues } from './values'; import { getVariableValues, getArgumentValues, @@ -98,7 +99,7 @@ export type ExecutionContext = {| rootValue: mixed, contextValue: mixed, operation: OperationDefinitionNode, - variableValues: { [variable: string]: mixed, ... }, + variableValues: VariableValues, fieldResolver: GraphQLFieldResolver, typeResolver: GraphQLTypeResolver, errors: Array, @@ -295,15 +296,15 @@ export function buildExecutionContext( // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203') const variableDefinitions = operation.variableDefinitions ?? []; - const coercedVariableValues = getVariableValues( + const variableValuesOrErrors = getVariableValues( schema, variableDefinitions, rawVariableValues ?? {}, { maxErrors: 50 }, ); - if (coercedVariableValues.errors) { - return coercedVariableValues.errors; + if (variableValuesOrErrors.errors) { + return variableValuesOrErrors.errors; } // $FlowFixMe[incompatible-return] @@ -313,7 +314,7 @@ export function buildExecutionContext( rootValue, contextValue, operation, - variableValues: coercedVariableValues.coerced, + variableValues: variableValuesOrErrors.variableValues, fieldResolver: fieldResolver ?? defaultFieldResolver, typeResolver: typeResolver ?? defaultTypeResolver, errors: [], @@ -679,7 +680,7 @@ export function buildResolveInfo( fragments: exeContext.fragments, rootValue: exeContext.rootValue, operation: exeContext.operation, - variableValues: exeContext.variableValues, + variableValues: exeContext.variableValues.coerced, }; } diff --git a/src/execution/values.d.ts b/src/execution/values.d.ts index b88943a04d0..89c0018e017 100644 --- a/src/execution/values.d.ts +++ b/src/execution/values.d.ts @@ -1,5 +1,5 @@ import { Maybe } from '../jsutils/Maybe'; -import { ObjMap } from '../jsutils/ObjMap'; +import { ReadOnlyObjMap } from '../jsutils/ObjMap'; import { GraphQLError } from '../error/GraphQLError'; import { @@ -10,11 +10,20 @@ import { import { GraphQLDirective } from '../type/directives'; import { GraphQLSchema } from '../type/schema'; -import { GraphQLField } from '../type/definition'; +import { GraphQLField, GraphQLInputType } from '../type/definition'; -type CoercedVariableValues = - | { errors: ReadonlyArray; coerced?: never } - | { errors?: never; coerced: { [key: string]: unknown } }; +export type VariableValues = { + readonly sources: ReadOnlyObjMap<{ + readonly variable: VariableDefinitionNode; + readonly type: GraphQLInputType; + readonly value: unknown; + }>; + readonly coerced: ReadOnlyObjMap; +}; + +type VariableValuesOrErrors = + | { variableValues: VariableValues; errors?: never } + | { errors: ReadonlyArray; variableValues?: never }; /** * Prepares an object map of variableValues of the correct type based on the @@ -30,7 +39,7 @@ export function getVariableValues( varDefNodes: ReadonlyArray, inputs: { [key: string]: unknown }, options?: { maxErrors?: number }, -): CoercedVariableValues; +): VariableValuesOrErrors; /** * Prepares an object map of argument values given a list of argument @@ -43,7 +52,7 @@ export function getVariableValues( export function getArgumentValues( def: GraphQLField | GraphQLDirective, node: FieldNode | DirectiveNode, - variableValues?: Maybe>, + variableValues?: Maybe, ): { [key: string]: unknown }; /** @@ -62,5 +71,5 @@ export function getDirectiveValues( node: { readonly directives?: ReadonlyArray; }, - variableValues?: Maybe>, + variableValues?: Maybe, ): undefined | { [key: string]: unknown }; diff --git a/src/execution/values.js b/src/execution/values.js index 78cb4176e0c..5af0a871c6d 100644 --- a/src/execution/values.js +++ b/src/execution/values.js @@ -1,6 +1,6 @@ -import type { ObjMap } from '../jsutils/ObjMap'; -import { keyMap } from '../jsutils/keyMap'; +import type { ReadOnlyObjMap, ReadOnlyObjMapLike } from '../jsutils/ObjMap'; import { inspect } from '../jsutils/inspect'; +import { keyMap } from '../jsutils/keyMap'; import { printPathArray } from '../jsutils/printPathArray'; import { GraphQLError } from '../error/GraphQLError'; @@ -14,7 +14,7 @@ import { Kind } from '../language/kinds'; import { print } from '../language/printer'; import type { GraphQLSchema } from '../type/schema'; -import type { GraphQLField } from '../type/definition'; +import type { GraphQLInputType, GraphQLField } from '../type/definition'; import type { GraphQLDirective } from '../type/directives'; import { isInputType, isNonNullType } from '../type/definition'; @@ -25,9 +25,18 @@ import { coerceDefaultValue, } from '../utilities/coerceInputValue'; -type CoercedVariableValues = - | {| errors: $ReadOnlyArray |} - | {| coerced: { [variable: string]: mixed, ... } |}; +export type VariableValues = {| + +sources: ReadOnlyObjMap<{| + +variable: VariableDefinitionNode, + +type: GraphQLInputType, + +value: mixed, + |}>, + +coerced: ReadOnlyObjMap, +|}; + +type VariableValuesOrErrors = + | {| variableValues: VariableValues |} + | {| errors: $ReadOnlyArray |}; /** * Prepares an object map of variableValues of the correct type based on the @@ -43,13 +52,13 @@ type CoercedVariableValues = export function getVariableValues( schema: GraphQLSchema, varDefNodes: $ReadOnlyArray, - inputs: { +[variable: string]: mixed, ... }, + inputs: ReadOnlyObjMapLike, options?: {| maxErrors?: number |}, -): CoercedVariableValues { +): VariableValuesOrErrors { const errors = []; const maxErrors = options?.maxErrors; try { - const coerced = coerceVariableValues( + const variableValues = coerceVariableValues( schema, varDefNodes, inputs, @@ -64,7 +73,7 @@ export function getVariableValues( ); if (errors.length === 0) { - return { coerced }; + return { variableValues }; } } catch (error) { errors.push(error); @@ -76,10 +85,11 @@ export function getVariableValues( function coerceVariableValues( schema: GraphQLSchema, varDefNodes: $ReadOnlyArray, - inputs: { +[variable: string]: mixed, ... }, + inputs: ReadOnlyObjMapLike, onError: (error: GraphQLError) => void, -): { [variable: string]: mixed, ... } { - const coercedValues = {}; +): VariableValues { + const sources = Object.create(null); + const coerced = Object.create(null); for (const varDefNode of varDefNodes) { const varName = varDefNode.variable.name.value; const varType = typeFromAST(schema, varDefNode.type); @@ -97,11 +107,14 @@ function coerceVariableValues( } if (!hasOwnProperty(inputs, varName)) { - if (varDefNode.defaultValue) { - coercedValues[varName] = coerceInputLiteral( - varDefNode.defaultValue, - varType, - ); + const defaultValue = varDefNode.defaultValue; + if (defaultValue) { + sources[varName] = { + variable: varDefNode, + type: varType, + value: undefined, + }; + coerced[varName] = coerceInputLiteral(defaultValue, varType); } else if (isNonNullType(varType)) { const varTypeStr = inspect(varType); onError( @@ -126,7 +139,8 @@ function coerceVariableValues( continue; } - coercedValues[varName] = coerceInputValue( + sources[varName] = { variable: varDefNode, type: varType, value }; + coerced[varName] = coerceInputValue( value, varType, (path, invalidValue, error) => { @@ -149,7 +163,7 @@ function coerceVariableValues( ); } - return coercedValues; + return { sources, coerced }; } /** @@ -165,7 +179,7 @@ function coerceVariableValues( export function getArgumentValues( def: GraphQLField | GraphQLDirective, node: FieldNode | DirectiveNode, - variableValues?: ?ObjMap, + variableValues?: ?VariableValues, ): { [argument: string]: mixed, ... } { const coercedValues = {}; @@ -201,7 +215,7 @@ export function getArgumentValues( const variableName = valueNode.name.value; if ( variableValues == null || - !hasOwnProperty(variableValues, variableName) + variableValues.coerced[variableName] === undefined ) { if (argDef.defaultValue) { coercedValues[name] = coerceDefaultValue( @@ -217,7 +231,7 @@ export function getArgumentValues( } continue; } - isNull = variableValues[variableName] == null; + isNull = variableValues.coerced[variableName] == null; } if (isNull && isNonNullType(argType)) { @@ -257,7 +271,7 @@ export function getArgumentValues( export function getDirectiveValues( directiveDef: GraphQLDirective, node: { +directives?: $ReadOnlyArray, ... }, - variableValues?: ?ObjMap, + variableValues?: ?VariableValues, ): void | { [argument: string]: mixed, ... } { // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203') const directiveNode = node.directives?.find( diff --git a/src/type/definition.d.ts b/src/type/definition.d.ts index b690c883207..404f426dd76 100644 --- a/src/type/definition.d.ts +++ b/src/type/definition.d.ts @@ -5,7 +5,7 @@ import { Maybe } from '../jsutils/Maybe'; import { PromiseOrValue } from '../jsutils/PromiseOrValue'; import { Path } from '../jsutils/Path'; -import { ObjMap } from '../jsutils/ObjMap'; +import { ObjMap, ReadOnlyObjMap } from '../jsutils/ObjMap'; import { ScalarTypeDefinitionNode, @@ -345,7 +345,7 @@ export type GraphQLScalarValueParser = ( ) => Maybe; export type GraphQLScalarLiteralParser = ( valueNode: ValueNode, - variables: Maybe>, + variables: Maybe>, ) => Maybe; export interface GraphQLScalarTypeConfig { @@ -487,7 +487,7 @@ export interface GraphQLResolveInfo { readonly fragments: ObjMap; readonly rootValue: unknown; readonly operation: OperationDefinitionNode; - readonly variableValues: { [variableName: string]: unknown }; + readonly variableValues: ReadOnlyObjMap; } /** @@ -789,7 +789,7 @@ export class GraphQLEnumType { parseValue(value: unknown): Maybe; parseLiteral( valueNode: ValueNode, - _variables: Maybe>, + _variables: Maybe>, ): Maybe; toConfig(): GraphQLEnumTypeConfig & { diff --git a/src/type/definition.js b/src/type/definition.js index 795c41dd190..982cf98b7f6 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -641,7 +641,7 @@ export type GraphQLScalarValueParser = ( export type GraphQLScalarLiteralParser = ( valueNode: ValueNode, - variables: ?ObjMap, + variables: ?ReadOnlyObjMap, ) => ?TInternal; export type GraphQLScalarTypeConfig = {| @@ -909,7 +909,7 @@ export type GraphQLResolveInfo = {| +fragments: ObjMap, +rootValue: mixed, +operation: OperationDefinitionNode, - +variableValues: { [variable: string]: mixed, ... }, + +variableValues: ReadOnlyObjMap, |}; export type GraphQLFieldConfig< @@ -1351,7 +1351,10 @@ export class GraphQLEnumType /* */ { return enumValue.value; } - parseLiteral(valueNode: ValueNode, _variables: ?ObjMap): ?any /* T */ { + parseLiteral( + valueNode: ValueNode, + _variables: ?ReadOnlyObjMap, + ): ?any /* T */ { // Note: variables will be resolved to a value before calling this function. if (valueNode.kind !== Kind.ENUM) { const valueStr = print(valueNode); diff --git a/src/utilities/__tests__/coerceInputValue-test.js b/src/utilities/__tests__/coerceInputValue-test.js index ff28c3d94b6..1c19427a24f 100644 --- a/src/utilities/__tests__/coerceInputValue-test.js +++ b/src/utilities/__tests__/coerceInputValue-test.js @@ -1,12 +1,12 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; -import type { ObjMap } from '../../jsutils/ObjMap'; +import type { ReadOnlyObjMap } from '../../jsutils/ObjMap'; import { invariant } from '../../jsutils/invariant'; import { identityFunc } from '../../jsutils/identityFunc'; import { print } from '../../language/printer'; -import { parseValue } from '../../language/parser'; +import { parseValue, Parser } from '../../language/parser'; import type { GraphQLInputType } from '../../type/definition'; import { @@ -23,6 +23,10 @@ import { GraphQLScalarType, GraphQLInputObjectType, } from '../../type/definition'; +import { GraphQLSchema } from '../../type/schema'; + +import type { VariableValues } from '../../execution/values'; +import { getVariableValues } from '../../execution/values'; import { coerceInputValue, @@ -446,20 +450,29 @@ describe('coerceInputLiteral', () => { valueText: string, type: GraphQLInputType, expected: mixed, - variables: ?ObjMap, + variableValues?: VariableValues, ) { const ast = parseValue(valueText); - const value = coerceInputLiteral(ast, type, variables); + const value = coerceInputLiteral(ast, type, variableValues); expect(value).to.deep.equal(expected); } function testWithVariables( - variables: ObjMap, + variableDefs: string, + inputs: ReadOnlyObjMap, valueText: string, type: GraphQLInputType, expected: mixed, ) { - test(valueText, type, expected, variables); + const parser = new Parser(variableDefs); + parser.expectToken(''); + const variableValuesOrErrors = getVariableValues( + new GraphQLSchema({}), + parser.parseVariableDefinitions(), + inputs, + ); + invariant(variableValuesOrErrors.variableValues); + test(valueText, type, expected, variableValuesOrErrors.variableValues); } it('converts according to input coercion rules', () => { @@ -658,19 +671,55 @@ describe('coerceInputLiteral', () => { it('accepts variable values assuming already coerced', () => { test('$var', GraphQLBoolean, undefined); - testWithVariables({ var: true }, '$var', GraphQLBoolean, true); - testWithVariables({ var: null }, '$var', GraphQLBoolean, null); - testWithVariables({ var: null }, '$var', nonNullBool, undefined); + testWithVariables( + '($var: Boolean)', + { var: true }, + '$var', + GraphQLBoolean, + true, + ); + testWithVariables( + '($var: Boolean)', + { var: null }, + '$var', + GraphQLBoolean, + null, + ); + testWithVariables( + '($var: Boolean)', + { var: null }, + '$var', + nonNullBool, + undefined, + ); }); it('asserts variables are provided as items in lists', () => { test('[ $foo ]', listOfBool, [null]); test('[ $foo ]', listOfNonNullBool, undefined); - testWithVariables({ foo: true }, '[ $foo ]', listOfNonNullBool, [true]); + testWithVariables( + '($foo: Boolean)', + { foo: true }, + '[ $foo ]', + listOfNonNullBool, + [true], + ); // Note: variables are expected to have already been coerced, so we // do not expect the singleton wrapping behavior for variables. - testWithVariables({ foo: true }, '$foo', listOfNonNullBool, true); - testWithVariables({ foo: [true] }, '$foo', listOfNonNullBool, [true]); + testWithVariables( + '($foo: Boolean)', + { foo: true }, + '$foo', + listOfNonNullBool, + true, + ); + testWithVariables( + '($foo: [Boolean])', + { foo: [true] }, + '$foo', + listOfNonNullBool, + [true], + ); }); it('omits input object fields for unprovided variables', () => { @@ -679,10 +728,13 @@ describe('coerceInputLiteral', () => { requiredBool: true, }); test('{ requiredBool: $foo }', testInputObj, undefined); - testWithVariables({ foo: true }, '{ requiredBool: $foo }', testInputObj, { - int: 42, - requiredBool: true, - }); + testWithVariables( + '($foo: Boolean)', + { foo: true }, + '{ requiredBool: $foo }', + testInputObj, + { int: 42, requiredBool: true }, + ); }); }); diff --git a/src/utilities/coerceInputValue.d.ts b/src/utilities/coerceInputValue.d.ts index 4ac50994bcf..c85cb6c1ba2 100644 --- a/src/utilities/coerceInputValue.d.ts +++ b/src/utilities/coerceInputValue.d.ts @@ -1,5 +1,4 @@ import { Maybe } from '../jsutils/Maybe'; -import { ObjMap } from '../jsutils/ObjMap'; import { ValueNode } from '../language/ast'; @@ -7,6 +6,8 @@ import { GraphQLInputType, GraphQLDefaultValueUsage } from '../type/definition'; import { GraphQLError } from '../error/GraphQLError'; +import { VariableValues } from '../execution/values'; + type OnErrorCB = ( path: ReadonlyArray, invalidValue: unknown, @@ -31,7 +32,7 @@ export function coerceInputValue( export function coerceInputLiteral( valueNode: ValueNode, type: GraphQLInputType, - variables?: Maybe>, + variableValues?: Maybe, ): unknown; /** diff --git a/src/utilities/coerceInputValue.js b/src/utilities/coerceInputValue.js index 4308b6acd21..9f6b546f86c 100644 --- a/src/utilities/coerceInputValue.js +++ b/src/utilities/coerceInputValue.js @@ -1,4 +1,3 @@ -import type { ObjMap } from '../jsutils/ObjMap'; import type { Path } from '../jsutils/Path'; import { hasOwnProperty } from '../jsutils/hasOwnProperty'; import { inspect } from '../jsutils/inspect'; @@ -28,6 +27,8 @@ import { import type { ValueNode } from '../language/ast'; import { Kind } from '../language/kinds'; +import type { VariableValues } from '../execution/values'; + type OnErrorCB = ( path: $ReadOnlyArray, invalidValue: mixed, @@ -209,26 +210,26 @@ function coerceInputValueImpl( export function coerceInputLiteral( valueNode: ValueNode, type: GraphQLInputType, - variables?: ?ObjMap, + variableValues?: ?VariableValues, ): mixed { if (valueNode.kind === Kind.VARIABLE) { - if (!variables || isMissingVariable(valueNode, variables)) { + if (!variableValues || isMissingVariable(valueNode, variableValues)) { return; // Invalid: intentionally return no value. } - const variableValue = variables[valueNode.name.value]; - if (variableValue === null && isNonNullType(type)) { + const coercedVariableValue = variableValues.coerced[valueNode.name.value]; + if (coercedVariableValue === null && isNonNullType(type)) { return; // Invalid: intentionally return no value. } // Note: This does no further checking that this variable is correct. // This assumes validated has checked this variable is of the correct type. - return variableValue; + return coercedVariableValue; } if (isNonNullType(type)) { if (valueNode.kind === Kind.NULL) { return; // Invalid: intentionally return no value. } - return coerceInputLiteral(valueNode, type.ofType, variables); + return coerceInputLiteral(valueNode, type.ofType, variableValues); } if (valueNode.kind === Kind.NULL) { @@ -238,7 +239,11 @@ export function coerceInputLiteral( if (isListType(type)) { if (valueNode.kind !== Kind.LIST) { // Lists accept a non-list value as a list of one. - const itemValue = coerceInputLiteral(valueNode, type.ofType, variables); + const itemValue = coerceInputLiteral( + valueNode, + type.ofType, + variableValues, + ); if (itemValue === undefined) { return; // Invalid: intentionally return no value. } @@ -246,10 +251,10 @@ export function coerceInputLiteral( } const coercedValue = []; for (const itemNode of valueNode.values) { - let itemValue = coerceInputLiteral(itemNode, type.ofType, variables); + let itemValue = coerceInputLiteral(itemNode, type.ofType, variableValues); if (itemValue === undefined) { if ( - isMissingVariable(itemNode, variables) && + isMissingVariable(itemNode, variableValues) && !isNonNullType(type.ofType) ) { // A missing variable within a list is coerced to null. @@ -279,7 +284,7 @@ export function coerceInputLiteral( const fieldNodes = keyMap(valueNode.fields, (field) => field.name.value); for (const field of Object.values(fieldDefs)) { const fieldNode = fieldNodes[field.name]; - if (!fieldNode || isMissingVariable(fieldNode.value, variables)) { + if (!fieldNode || isMissingVariable(fieldNode.value, variableValues)) { if (isRequiredInput(field)) { return; // Invalid: intentionally return no value. } @@ -293,7 +298,7 @@ export function coerceInputLiteral( const fieldValue = coerceInputLiteral( fieldNode.value, field.type, - variables, + variableValues, ); if (fieldValue === undefined) { return; // Invalid: intentionally return no value. @@ -307,7 +312,7 @@ export function coerceInputLiteral( // istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618') if (isLeafType(type)) { try { - return type.parseLiteral(valueNode, variables); + return type.parseLiteral(valueNode, variableValues); } catch (_error) { return; // Invalid: ignore error and intentionally return no value. } @@ -321,11 +326,11 @@ export function coerceInputLiteral( // in the set of variables. function isMissingVariable( valueNode: ValueNode, - variables: ?ObjMap, + variables: ?VariableValues, ): boolean { return ( valueNode.kind === Kind.VARIABLE && - (variables == null || variables[valueNode.name.value] === undefined) + (variables == null || variables.coerced[valueNode.name.value] === undefined) ); } diff --git a/src/utilities/valueFromASTUntyped.d.ts b/src/utilities/valueFromASTUntyped.d.ts index 7a57bf0d574..52df9c41bed 100644 --- a/src/utilities/valueFromASTUntyped.d.ts +++ b/src/utilities/valueFromASTUntyped.d.ts @@ -1,5 +1,5 @@ import { Maybe } from '../jsutils/Maybe'; -import { ObjMap } from '../jsutils/ObjMap'; +import { ReadOnlyObjMap } from '../jsutils/ObjMap'; import { ValueNode } from '../language/ast'; @@ -21,5 +21,5 @@ import { ValueNode } from '../language/ast'; */ export function valueFromASTUntyped( valueNode: ValueNode, - variables?: Maybe>, + variables?: Maybe>, ): unknown; diff --git a/src/utilities/valueFromASTUntyped.js b/src/utilities/valueFromASTUntyped.js index 41873266464..a0cc369530b 100644 --- a/src/utilities/valueFromASTUntyped.js +++ b/src/utilities/valueFromASTUntyped.js @@ -1,4 +1,4 @@ -import type { ObjMap } from '../jsutils/ObjMap'; +import type { ReadOnlyObjMap } from '../jsutils/ObjMap'; import { inspect } from '../jsutils/inspect'; import { invariant } from '../jsutils/invariant'; import { keyValMap } from '../jsutils/keyValMap'; @@ -24,7 +24,7 @@ import type { ValueNode } from '../language/ast'; */ export function valueFromASTUntyped( valueNode: ValueNode, - variables?: ?ObjMap, + variables?: ?ReadOnlyObjMap, ): mixed { switch (valueNode.kind) { case Kind.NULL: