-
Notifications
You must be signed in to change notification settings - Fork 25
fix(evaluator): handle numerical addition/subtraction from datetime #316
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
fix(evaluator): handle numerical addition/subtraction from datetime #316
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
sgulseth
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a few bugs here:
- This is order dependent.
dateTime() + 10vs10 - dateTime() - Now we would also allow
"foo" + 10which should be null and not string|null.
WDYT about a "sub type" to string, which we don't expose:
diff --git i/src/typeEvaluator/functions.ts w/src/typeEvaluator/functions.ts
index 3a143eb..05bd53e 100644
--- i/src/typeEvaluator/functions.ts
+++ w/src/typeEvaluator/functions.ts
@@ -4,7 +4,7 @@ import {optimizeUnions} from './optimizations'
import type {Scope} from './scope'
import {walk} from './typeEvaluate'
import {createGeoJson, mapNode, nullUnion} from './typeHelpers'
-import type {NullTypeNode, TypeNode} from './types'
+import {type NullTypeNode, STRING_TYPE_DATETIME, type TypeNode} from './types'
function unionWithoutNull(unionTypeNode: TypeNode): TypeNode {
if (unionTypeNode.type === 'union') {
@@ -237,7 +237,10 @@ export function handleFuncCallNode(node: FuncCallNode, scope: Scope): TypeNode {
}
if (arg.type === 'string') {
- return nullUnion({type: 'string'}) // we don't know wether the string is a valid date or not, so we return a [null, string]-union
+ if (arg.value !== undefined) {
+ return {type: 'string', [STRING_TYPE_DATETIME]: true, value: arg.value} // we don't know wether the string is a valid date or not, so we return a [null, string]-union
+ }
+ return nullUnion({type: 'string', [STRING_TYPE_DATETIME]: true}) // we don't know wether the string is a valid date or not, so we return a [null, string]-union
}
return {type: 'null'} satisfies NullTypeNode
diff --git i/src/typeEvaluator/typeEvaluate.ts w/src/typeEvaluator/typeEvaluate.ts
index 6ea8d3b..e7953a5 100644
--- i/src/typeEvaluator/typeEvaluate.ts
+++ w/src/typeEvaluator/typeEvaluate.ts
@@ -33,20 +33,21 @@ import {match} from './matching'
import {optimizeUnions} from './optimizations'
import {Context, Scope} from './scope'
import {isFuncCall, mapNode, nullUnion, resolveInline} from './typeHelpers'
-import type {
- ArrayTypeNode,
- BooleanTypeNode,
- Document,
- NullTypeNode,
- NumberTypeNode,
- ObjectAttribute,
- ObjectTypeNode,
- PrimitiveTypeNode,
- Schema,
- StringTypeNode,
- TypeNode,
- UnionTypeNode,
- UnknownTypeNode,
+import {
+ type ArrayTypeNode,
+ type BooleanTypeNode,
+ type Document,
+ type NullTypeNode,
+ type NumberTypeNode,
+ type ObjectAttribute,
+ type ObjectTypeNode,
+ type PrimitiveTypeNode,
+ type Schema,
+ STRING_TYPE_DATETIME,
+ type StringTypeNode,
+ type TypeNode,
+ type UnionTypeNode,
+ type UnknownTypeNode,
} from './types'
const $trace = debug('typeEvaluator:evaluate:trace')
@@ -618,7 +619,7 @@ function handleOpCallNode(node: OpCallNode, scope: Scope): TypeNode {
}
// datetime + number -> datetime (datetimes are represented as strings)
// Returns null for non-datetime strings at runtime, so we include null in the union
- if (left.type === 'string' && right.type === 'number') {
+ if (left.type === 'string' && left[STRING_TYPE_DATETIME] && right.type === 'number') {
return nullUnion({type: 'string'})
}
@@ -654,8 +655,8 @@ function handleOpCallNode(node: OpCallNode, scope: Scope): TypeNode {
}
// datetime - number -> datetime (datetimes are represented as strings)
// Returns null for non-datetime strings at runtime, so we include null in the union
- if (left.type === 'string' && right.type === 'number') {
- return nullUnion({type: 'string'})
+ if (left.type === 'string' && left[STRING_TYPE_DATETIME] && right.type === 'number') {
+ return nullUnion({type: 'string', [STRING_TYPE_DATETIME]: true})
}
if (left.type === 'number' && right.type === 'number') {
return {
diff --git i/src/typeEvaluator/types.ts w/src/typeEvaluator/types.ts
index f69bc0d..d827819 100644
--- i/src/typeEvaluator/types.ts
+++ w/src/typeEvaluator/types.ts
@@ -21,12 +21,16 @@ export interface TypeDeclaration {
/** A schema consisting of a list of Document or TypeDeclaration items, allowing for complex type definitions. */
export type Schema = (Document | TypeDeclaration)[]
+export const STRING_TYPE_DATETIME = Symbol('groq-js.type.string_datetime')
+
/** Describes a type node for string values, optionally including a value. If a value is provided it will always be the given string value. */
export interface StringTypeNode {
/** can be used to identify the type of the node, in this case it's always 'string' */
type: 'string'
/** an optional value of the string, if provided it will always be the given string value */
value?: string
+
+ [STRING_TYPE_DATETIME]?: true
}
this adds a STRING_TYPE_DATETIME-symbol which we can then use when checking the +/- ops
|
@sgulseth The spec defines only |
|
I guess supporting both makes sense. Support both and updated the spec, then..? |
1779b74 to
0da8b30
Compare
sgulseth
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's go with the spec for now 👍 LGTM!
|
Preferably these types of fixes should be done by adding support for dateTime in typeEvaluateCompare: groq-js/test/typeEvaluateCompare.test.ts Line 71 in df81e4a
|
Cool. I've added a task for it in linear and will follow up later. |


Description
Adds support for add/subtract operations where the left operand is a datetime string and the right operand is a number. If a valid datetime string is passed the type is a string, while for any invalid datatime values the type is null. Based on this we return a
string-nullunion.This fixes the underlying issue reported in sanity-io/sanity#8255 where the generated type for the following query would not be correct:
it now yields a
boolean-nullunion, as is correct since an invalid dateTime string would result innullany comparison operation that includes anulloperand evaluates tonull.https://linear.app/sanity/issue/CLDX-3609/groq-js-support-datetime-numerical-operations-in-type-evaluator
What to review
The code
Testing
Have tested the whole typegen flow in a local project with both a simple subtraction and a subtraction as part of a comparison operation. Also added a test for the case that failed before, with
dateTime(...) - number.