Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Experimental support for semantic-non-null #4192

Draft
wants to merge 32 commits into
base: on-error
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
7ca49b2
New GraphQLSemanticNonNull type
benjie Sep 14, 2024
16a2114
Handle isNonNullType
benjie Sep 14, 2024
2b13389
More fixes
benjie Sep 14, 2024
04a8e91
More fixes
benjie Sep 14, 2024
076a735
Yet more updates
benjie Sep 14, 2024
c2196a0
Recognize in introspection, enable disabling null bubbling
benjie Sep 14, 2024
f588046
Lint fixes
benjie Sep 14, 2024
fa3f177
More missing pieces
benjie Sep 14, 2024
b5e81bd
More fixes
benjie Sep 14, 2024
1f6a019
Fix schema
benjie Sep 14, 2024
491f49b
Fix another test
benjie Sep 14, 2024
3a91590
More minor test fixes
benjie Sep 14, 2024
56db880
Fix introspection test
benjie Sep 14, 2024
593ce44
Add support for * to lexer
benjie Sep 14, 2024
1311906
Allow specifying errorPropagation at top level
benjie Sep 14, 2024
9d706d2
Factor into getIntrospectionQuery
benjie Sep 14, 2024
e9f9628
Lint
benjie Sep 14, 2024
eb9b6c8
Prettier
benjie Sep 14, 2024
6ef4bec
Merge branch '16.x.x' into semantic-non-null
benjie Mar 26, 2025
8fcacc8
Switch to errorBehavior, replace contextual introspection to simple i…
benjie Mar 26, 2025
88c5d93
Simplify
benjie Mar 26, 2025
62d1b75
Stricter types: semantic non null may only wrap output types
benjie Mar 26, 2025
96e8b53
Use GraphQLNullableOutputType instead of intersection
benjie Mar 26, 2025
a2169ac
Simpler type
benjie Mar 26, 2025
1ce6880
Only allow GraphQLSemanticNonNull of output type
benjie Mar 26, 2025
97256f0
Tidy
benjie Mar 26, 2025
f464644
Memoize
benjie Mar 26, 2025
2113676
Rename errorBehavior to onError and NULL to NO_PROPAGATE
benjie Mar 27, 2025
95da88d
Centralise the definition of GraphQLErrorBehavior
benjie Mar 27, 2025
a1d2dbe
Lint
benjie Mar 27, 2025
70dc6f8
Prettier
benjie Mar 27, 2025
c0d54cf
Merge branch 'on-error' into semantic-non-null
benjie Mar 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/execution/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
isListType,
isNonNullType,
isObjectType,
isSemanticNonNullType,
} from '../type/definition';
import {
SchemaMetaFieldDef,
Expand Down Expand Up @@ -679,9 +680,9 @@ function completeValue(
throw result;
}

// If field type is NonNull, complete for inner type, and throw field error
// If field type is non-nullable, complete for inner type, and throw field error
// if result is null.
if (isNonNullType(returnType)) {
if (isNonNullType(returnType) || isSemanticNonNullType(returnType)) {
const completed = completeValue(
exeContext,
returnType.ofType,
Expand All @@ -692,7 +693,9 @@ function completeValue(
);
if (completed === null) {
throw new Error(
`Cannot return null for non-nullable field ${info.parentType.name}.${info.fieldName}.`,
`Cannot return null for ${
isSemanticNonNullType(returnType) ? 'semantically ' : ''
}non-nullable field ${info.parentType.name}.${info.fieldName}.`,
);
}
return completed;
Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export {
GraphQLInputObjectType,
GraphQLList,
GraphQLNonNull,
GraphQLSemanticNonNull,
// Standard GraphQL Scalars
specifiedScalarTypes,
GraphQLInt,
Expand Down Expand Up @@ -95,6 +96,7 @@ export {
isInputObjectType,
isListType,
isNonNullType,
isSemanticNonNullType,
isInputType,
isOutputType,
isLeafType,
Expand All @@ -120,6 +122,7 @@ export {
assertInputObjectType,
assertListType,
assertNonNullType,
assertSemanticNonNullType,
assertInputType,
assertOutputType,
assertLeafType,
Expand Down Expand Up @@ -148,6 +151,7 @@ export type {
GraphQLAbstractType,
GraphQLWrappingType,
GraphQLNullableType,
GraphQLNullableOutputType,
GraphQLNamedType,
GraphQLNamedInputType,
GraphQLNamedOutputType,
Expand Down Expand Up @@ -287,6 +291,7 @@ export type {
NamedTypeNode,
ListTypeNode,
NonNullTypeNode,
SemanticNonNullTypeNode,
TypeSystemDefinitionNode,
SchemaDefinitionNode,
OperationTypeDefinitionNode,
Expand Down Expand Up @@ -482,6 +487,7 @@ export type {
IntrospectionNamedTypeRef,
IntrospectionListTypeRef,
IntrospectionNonNullTypeRef,
IntrospectionSemanticNonNullTypeRef,
IntrospectionField,
IntrospectionInputValue,
IntrospectionEnumValue,
Expand Down
22 changes: 22 additions & 0 deletions src/jsutils/memoize1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Memoizes the provided single-argument function.
*/
export function memoize1<A1 extends object, R>(
fn: (a1: A1) => R,
): (a1: A1) => R {
let cache0: WeakMap<A1, R>;

return function memoized(a1) {
if (cache0 === undefined) {
cache0 = new WeakMap();
}

let fnResult = cache0.get(a1);
if (fnResult === undefined) {
fnResult = fn(a1);
cache0.set(a1, fnResult);
}

return fnResult;
};
}
17 changes: 17 additions & 0 deletions src/language/__tests__/parser-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,23 @@ describe('Parser', () => {
});
});

it('parses semantic-non-null types', () => {
const result = parseType('MyType*');
expectJSON(result).toDeepEqual({
kind: Kind.SEMANTIC_NON_NULL_TYPE,
loc: { start: 0, end: 7 },
type: {
kind: Kind.NAMED_TYPE,
loc: { start: 0, end: 6 },
name: {
kind: Kind.NAME,
loc: { start: 0, end: 6 },
value: 'MyType',
},
},
});
});

it('parses nested types', () => {
const result = parseType('[MyType!]');
expectJSON(result).toDeepEqual({
Expand Down
1 change: 1 addition & 0 deletions src/language/__tests__/predicates-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ describe('AST node predicates', () => {
'NamedType',
'ListType',
'NonNullType',
'SemanticNonNullType',
]);
});

Expand Down
14 changes: 13 additions & 1 deletion src/language/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export type ASTNode =
| NamedTypeNode
| ListTypeNode
| NonNullTypeNode
| SemanticNonNullTypeNode
| SchemaDefinitionNode
| OperationTypeDefinitionNode
| ScalarTypeDefinitionNode
Expand Down Expand Up @@ -235,6 +236,7 @@ export const QueryDocumentKeys: {
NamedType: ['name'],
ListType: ['type'],
NonNullType: ['type'],
SemanticNonNullType: ['type'],

SchemaDefinition: ['description', 'directives', 'operationTypes'],
OperationTypeDefinition: ['type'],
Expand Down Expand Up @@ -521,7 +523,11 @@ export interface ConstDirectiveNode {

/** Type Reference */

export type TypeNode = NamedTypeNode | ListTypeNode | NonNullTypeNode;
export type TypeNode =
| NamedTypeNode
| ListTypeNode
| NonNullTypeNode
| SemanticNonNullTypeNode;

export interface NamedTypeNode {
readonly kind: Kind.NAMED_TYPE;
Expand All @@ -541,6 +547,12 @@ export interface NonNullTypeNode {
readonly type: NamedTypeNode | ListTypeNode;
}

export interface SemanticNonNullTypeNode {
readonly kind: Kind.SEMANTIC_NON_NULL_TYPE;
readonly loc?: Location;
readonly type: NamedTypeNode | ListTypeNode;
}

/** Type System Definition */

export type TypeSystemDefinitionNode =
Expand Down
1 change: 1 addition & 0 deletions src/language/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type {
NamedTypeNode,
ListTypeNode,
NonNullTypeNode,
SemanticNonNullTypeNode,
TypeSystemDefinitionNode,
SchemaDefinitionNode,
OperationTypeDefinitionNode,
Expand Down
1 change: 1 addition & 0 deletions src/language/kinds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ enum Kind {
NAMED_TYPE = 'NamedType',
LIST_TYPE = 'ListType',
NON_NULL_TYPE = 'NonNullType',
SEMANTIC_NON_NULL_TYPE = 'SemanticNonNullType',

/** Type System Definitions */
SCHEMA_DEFINITION = 'SchemaDefinition',
Expand Down
5 changes: 4 additions & 1 deletion src/language/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export class Lexer {
export function isPunctuatorTokenKind(kind: TokenKind): boolean {
return (
kind === TokenKind.BANG ||
kind === TokenKind.ASTERISK ||
kind === TokenKind.DOLLAR ||
kind === TokenKind.AMP ||
kind === TokenKind.PAREN_L ||
Expand Down Expand Up @@ -246,7 +247,7 @@ function readNextToken(lexer: Lexer, start: number): Token {
// - FloatValue
// - StringValue
//
// Punctuator :: one of ! $ & ( ) ... : = @ [ ] { | }
// Punctuator :: one of ! $ & ( ) * ... : = @ [ ] { | }
case 0x0021: // !
return createToken(lexer, TokenKind.BANG, position, position + 1);
case 0x0024: // $
Expand All @@ -257,6 +258,8 @@ function readNextToken(lexer: Lexer, start: number): Token {
return createToken(lexer, TokenKind.PAREN_L, position, position + 1);
case 0x0029: // )
return createToken(lexer, TokenKind.PAREN_R, position, position + 1);
case 0x002a: // *
return createToken(lexer, TokenKind.ASTERISK, position, position + 1);
case 0x002e: // .
if (
body.charCodeAt(position + 1) === 0x002e &&
Expand Down
8 changes: 8 additions & 0 deletions src/language/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import type {
SchemaExtensionNode,
SelectionNode,
SelectionSetNode,
SemanticNonNullTypeNode,
StringValueNode,
Token,
TypeNode,
Expand Down Expand Up @@ -749,6 +750,7 @@ export class Parser {
* - NamedType
* - ListType
* - NonNullType
* - SemanticNonNullType
*/
parseTypeReference(): TypeNode {
const start = this._lexer.token;
Expand All @@ -770,6 +772,12 @@ export class Parser {
type,
});
}
if (this.expectOptionalToken(TokenKind.ASTERISK)) {
return this.node<SemanticNonNullTypeNode>(start, {
kind: Kind.SEMANTIC_NON_NULL_TYPE,
type,
});
}

return type;
}
Expand Down
3 changes: 2 additions & 1 deletion src/language/predicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export function isTypeNode(node: ASTNode): node is TypeNode {
return (
node.kind === Kind.NAMED_TYPE ||
node.kind === Kind.LIST_TYPE ||
node.kind === Kind.NON_NULL_TYPE
node.kind === Kind.NON_NULL_TYPE ||
node.kind === Kind.SEMANTIC_NON_NULL_TYPE
);
}

Expand Down
1 change: 1 addition & 0 deletions src/language/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const printDocASTReducer: ASTReducer<string> = {
NamedType: { leave: ({ name }) => name },
ListType: { leave: ({ type }) => '[' + type + ']' },
NonNullType: { leave: ({ type }) => type + '!' },
SemanticNonNullType: { leave: ({ type }) => type + '*' },

// Type System Definitions

Expand Down
1 change: 1 addition & 0 deletions src/language/tokenKind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ enum TokenKind {
SOF = '<SOF>',
EOF = '<EOF>',
BANG = '!',
ASTERISK = '*',
DOLLAR = '$',
AMP = '&',
PAREN_L = '(',
Expand Down
21 changes: 20 additions & 1 deletion src/type/__tests__/introspection-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,11 @@ describe('Introspection', () => {
isDeprecated: false,
deprecationReason: null,
},
{
name: 'SEMANTIC_NON_NULL',
isDeprecated: false,
deprecationReason: null,
},
],
possibleTypes: null,
},
Expand Down Expand Up @@ -506,7 +511,21 @@ describe('Introspection', () => {
},
{
name: 'type',
args: [],
args: [
{
name: 'includeSemanticNonNull',
type: {
kind: 'NON_NULL',
name: null,
ofType: {
kind: 'SCALAR',
name: 'Boolean',
ofType: null,
},
},
defaultValue: 'false',
},
],
type: {
kind: 'NON_NULL',
name: null,
Expand Down
Loading
Loading