Skip to content

Commit

Permalink
Merge pull request #16 from getlang-dev/get-24-bug-subquery-context-s…
Browse files Browse the repository at this point in the history
…witching

bugfix: subquery context switching
  • Loading branch information
mattfysh authored Sep 9, 2024
2 parents 4bd537d + 1c17b8d commit 90ae018
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 87 deletions.
5 changes: 5 additions & 0 deletions .changeset/witty-wolves-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@getlang/parser": patch
---

bugfix: subquery context switching
Binary file modified bun.lockb
Binary file not shown.
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "get",
"license": "Apache-2.0",
"private": true,
"packageManager": "[email protected].22",
"packageManager": "[email protected].27",
"scripts": {
"format": "biome check --write",
"lint": "bun lint:check && bun lint:types && bun lint:unused && bun lint:repo",
Expand All @@ -20,10 +20,10 @@
"devDependencies": {
"@biomejs/biome": "1.8.2",
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.7",
"@types/bun": "^1.1.6",
"knip": "^5.27.2",
"sherif": "^0.10.0",
"@changesets/cli": "^2.27.8",
"@types/bun": "^1.1.8",
"knip": "^5.30.0",
"sherif": "^1.0.0",
"typescript": "^5.5.4"
},
"knip": {
Expand All @@ -36,7 +36,10 @@
],
"workspaces": {
"packages/parser": {
"entry": ["index.ts", "grammar.ts"]
"entry": [
"index.ts",
"grammar.ts"
]
}
}
}
Expand Down
152 changes: 75 additions & 77 deletions packages/parser/desugar/inference/typeinfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import type { TransformVisitor, Visit } from '../../visitor/transform.js'
import { traceVisitor } from '../trace.js'
import { render, selectTypeInfo } from '../utils.js'

function clone(a: unknown) {
return JSON.parse(JSON.stringify(a))
const modTypeMap: Record<string, TypeInfo> = {
html: { type: Type.Html },
json: { type: Type.Value },
js: { type: Type.Js },
headers: { type: Type.Headers },
cookies: { type: Type.Cookies },
link: { type: Type.Value },
}

function unwrap(typeInfo: TypeInfo) {
Expand All @@ -25,6 +30,26 @@ function unwrap(typeInfo: TypeInfo) {
}
}

function rewrap(
typeInfo: TypeInfo | undefined,
itemTypeInfo: TypeInfo,
optional: boolean,
): TypeInfo {
switch (typeInfo?.type) {
case Type.List:
return { ...typeInfo, of: rewrap(typeInfo.of, itemTypeInfo, optional) }
case Type.Maybe: {
const option = rewrap(typeInfo.option, itemTypeInfo, optional)
if (option.type === Type.Maybe || !optional) {
return option
}
return { ...typeInfo, option }
}
default:
return structuredClone(itemTypeInfo)
}
}

export function inferTypeInfo(): TransformVisitor {
const scope = new RootScope<Expr>()

Expand All @@ -37,41 +62,25 @@ export function inferTypeInfo(): TransformVisitor {
return ret
}

function rewrap(
typeInfo: TypeInfo | undefined,
itemTypeInfo: TypeInfo,
): TypeInfo {
switch (typeInfo?.type) {
case Type.List:
return { ...typeInfo, of: rewrap(typeInfo.of, itemTypeInfo) }
case Type.Maybe: {
const option = rewrap(typeInfo.option, itemTypeInfo)
if (option.type === Type.Maybe || !optional) {
return option
}
return { ...typeInfo, option }
function ctx<C extends CExpr>(cb: (tnode: C, ivisit: Visit) => C) {
return function enter(node: C, visit: Visit): C {
if (!node.context) return cb(node, visit)
const context = visit(node.context)
const itemContext: any = {
...context,
typeInfo: unwrap(context.typeInfo),
}
default:
return clone(itemTypeInfo)
}
}

function itemVisit(node: CExpr, visit: Visit): Visit {
return child => {
if (child === node.context || !scope.context?.typeInfo) {
return visit(child)
}
scope.pushContext({
...scope.context,
typeInfo: unwrap(scope.context.typeInfo),
})
const xnode = setOptional(false, () => visit(child))
scope.popContext()
return xnode
const ivisit: Visit = child =>
child === itemContext ? itemContext : visit(child)

const xnode = cb({ ...node, context: itemContext }, ivisit)

const typeInfo = rewrap(context.typeInfo, xnode.typeInfo, optional)
return { ...xnode, context, typeInfo }
}
}

// SliceExpr and ModuleCallExpr use default Type.Value
const trace = traceVisitor(scope)

return {
Expand Down Expand Up @@ -102,8 +111,7 @@ export function inferTypeInfo(): TransformVisitor {
const id = node.value.value
const value = scope.vars[id]
invariant(value, new ValueReferenceError(node.value.value))
const { typeInfo } = value
return { ...node, typeInfo: clone(typeInfo) }
return { ...node, typeInfo: structuredClone(value.typeInfo) }
},

RequestExpr(node) {
Expand All @@ -122,17 +130,17 @@ export function inferTypeInfo(): TransformVisitor {
},

SliceExpr: {
enter(node, visit) {
const xnode = trace.SliceExpr.enter(node, itemVisit(node, visit))
enter: ctx((node, visit) => {
const xnode = trace.SliceExpr.enter(node, visit)
let typeInfo: TypeInfo = { type: Type.Value }
if (optional) typeInfo = { type: Type.Maybe, option: typeInfo }
return { ...xnode, typeInfo: rewrap(xnode.context?.typeInfo, typeInfo) }
},
return { ...xnode, typeInfo }
}),
},

SelectorExpr: {
enter(node, visit) {
const xnode = trace.SelectorExpr.enter(node, itemVisit(node, visit))
enter: ctx((node, visit) => {
const xnode = trace.SelectorExpr.enter(node, visit)
let typeInfo: TypeInfo = unwrap(
xnode.context?.typeInfo ?? { type: Type.Value },
)
Expand All @@ -153,52 +161,42 @@ export function inferTypeInfo(): TransformVisitor {
} else if (optional) {
typeInfo = { type: Type.Maybe, option: typeInfo }
}
return { ...xnode, typeInfo: rewrap(xnode.context?.typeInfo, typeInfo) }
},

return { ...xnode, typeInfo }
}),
},

ModifierExpr: {
enter(node, visit) {
const modTypeMap: Record<string, TypeInfo> = {
html: { type: Type.Html },
json: { type: Type.Value },
js: { type: Type.Js },
headers: { type: Type.Headers },
cookies: { type: Type.Cookies },
link: { type: Type.Value },
}
const xnode = trace.ModifierExpr.enter(node, itemVisit(node, visit))
enter: ctx((node, visit) => {
const xnode = trace.ModifierExpr.enter(node, visit)
const mod = xnode.value.value
const typeInfo = modTypeMap[mod]
invariant(typeInfo, new QuerySyntaxError(`Unknown modifier: ${mod}`))
return { ...xnode, typeInfo: rewrap(xnode.context?.typeInfo, typeInfo) }
},
return { ...xnode, typeInfo }
}),
},

SubqueryExpr: {
enter(node, visit) {
const xnode = trace.SubqueryExpr.enter(node, itemVisit(node, visit))
enter: ctx((node, visit) => {
const xnode = trace.SubqueryExpr.enter(node, visit)
const typeInfo = xnode.body.find(
stmt => stmt.kind === NodeKind.ExtractStmt,
)?.value.typeInfo ?? { type: Type.Never }
return { ...xnode, typeInfo: rewrap(xnode.context?.typeInfo, typeInfo) }
},
return { ...xnode, typeInfo }
}),
},

ObjectLiteralExpr: {
enter(node, visit) {
const xnode = trace.ObjectLiteralExpr.enter(
node,
itemVisit(node, child => {
if (child === node.context) return visit(child)
const entry = node.entries.find(e => e.value === child)
invariant(
entry,
new QuerySyntaxError('Object entry missing typeinfo'),
)
return setOptional(entry.optional, () => visit(child))
}),
)
enter: ctx((node, visit) => {
const xnode = trace.ObjectLiteralExpr.enter(node, child => {
if (child === node.context) return visit(child)
const entry = node.entries.find(e => e.value === child)
invariant(
entry,
new QuerySyntaxError('Object entry missing typeinfo'),
)
return setOptional(entry.optional, () => visit(child))
})

const typeInfo: TypeInfo = {
type: Type.Struct,
Expand All @@ -215,16 +213,16 @@ export function inferTypeInfo(): TransformVisitor {
),
}

return { ...xnode, typeInfo: rewrap(xnode.context?.typeInfo, typeInfo) }
},
return { ...xnode, typeInfo }
}),
},

ModuleCallExpr: {
enter(node, visit) {
const xnode = trace.ModuleCallExpr.enter(node, itemVisit(node, visit))
enter: ctx((node, visit) => {
const xnode = trace.ModuleCallExpr.enter(node, visit)
const typeInfo: TypeInfo = { type: Type.Value }
return { ...xnode, typeInfo: rewrap(xnode.context?.typeInfo, typeInfo) }
},
return { ...xnode, typeInfo }
}),
},
}
}
9 changes: 5 additions & 4 deletions packages/parser/desugar/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import type { TransformVisitor, Visit } from '../visitor/transform.js'

export function traceVisitor(scope: RootScope<Expr>) {
function ctx<C extends CExpr>(node: C, visit: Visit, cb: (tnode: C) => C) {
const context = node.context && visit(node.context)
context && scope.pushContext(context)
if (!node.context) return cb(node)
const context = visit(node.context)
scope.pushContext(context)
const xnode = cb({ ...node, context })
context && scope.popContext()
scope.popContext()
return xnode
}

Expand Down Expand Up @@ -84,7 +85,7 @@ export function traceVisitor(scope: RootScope<Expr>) {

SliceExpr: {
enter(node, visit) {
// contains no additional expressions beyond .context
// contains no additional expressions (only .context)
return ctx(node, visit, node => node)
},
},
Expand Down
14 changes: 14 additions & 0 deletions test/request.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,20 @@ describe('request', () => {
`)
expect(result).toEqual({ whicha: 1, whichb: 2 })
})

it('updates subquery context', async () => {
const result = await execute(`
set x? = \`'<p>'\` -> @html -> (
GET http://example.com
extract h1
)
extract $x
`)

expect(result).toEqual('test')
})
})

describe('inference', () => {
Expand Down

0 comments on commit 90ae018

Please sign in to comment.