Skip to content

Commit

Permalink
fix: change reduceConditionals to not resolve variable references out…
Browse files Browse the repository at this point in the history
…side of the condition expression (#409)

Fixes #408
  • Loading branch information
chrispcampbell authored Dec 3, 2023
1 parent 044d135 commit 62e1ab1
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 11 deletions.
34 changes: 34 additions & 0 deletions packages/parse/src/ast/reduce-expr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,38 @@ describe('reduceConditionals', () => {
binaryOp(y, '*', zero)
)
})

it('should try to resolve variable refs in the condition but not elsewhere', () => {
interface Variable {
refId: string
expr?: Expr
}

const xVar: Variable = { refId: '_x', expr: one }

function resolveVarRef(varRef: VariableRef): Expr | undefined {
if (varRef.varId === '_x') {
return xVar.expr
} else {
return undefined
}
}

// Note that unlike the `reduceExpr` tests above, the `reduceConditionals` function
// should not attempt to reduce `variable-ref` expressions outside of the condition
// expression, so the `x` variable references below are expected to remain unchanged
// (should not be reduced to or replaced with the constant value)
const opts: ReduceExprOptions = {
resolveVarRef
}
expect(reduceConditionals(varRef('x'), opts)).toEqual(varRef('x'))
expect(reduceConditionals(varRef('y'), opts)).toEqual(varRef('y'))

// In this example, the `x` used in the condition expression resolves to a constant
// value of 1, so the whole `IF THEN ELSE` is expected to be replaced with the "true"
// branch expression
expect(reduceConditionals(binaryOp(call('IF THEN ELSE', varRef('x'), varRef('x'), zero), '*', two), opts)).toEqual(
binaryOp(varRef('x'), '*', two)
)
})
})
20 changes: 9 additions & 11 deletions packages/parse/src/ast/reduce-expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,7 @@ export function reduceExpr(expr: Expr, opts?: ReduceExprOptions): Expr {
}

default:
return expr
// assertNever(expr)
assertNever(expr)
}
}

Expand All @@ -319,14 +318,9 @@ export function reduceConditionals(expr: Expr, opts?: ReduceExprOptions): Expr {
return expr

case 'variable-ref':
if (opts?.resolveVarRef !== undefined) {
// Note that we assume the resolved expression has already been reduced by
// the callback, and don't attempt to reduce further
const resolvedExpr = opts.resolveVarRef(expr)
if (resolvedExpr) {
return resolvedExpr
}
}
// If we get here, it means that we are processing an expression that is not
// inside the condition expression for an `IF THEN ELSE`, so we do not attempt
// to resolve the variable reference or reduce it further
return expr

case 'unary-op': {
Expand Down Expand Up @@ -367,9 +361,13 @@ export function reduceConditionals(expr: Expr, opts?: ReduceExprOptions): Expr {
case 'function-call': {
if (expr.fnId === '_IF_THEN_ELSE') {
// Note that (unlike all other places in this function) we use `reduceExpr` here
// to aggressively reduce the condition
// to aggressively reduce the condition expression (the first argument for the
// `IF THEN ELSE` call)
const conditionExpr = reduceExpr(expr.args[0], opts)
if (conditionExpr.kind === 'number') {
// The condition resolved to a simple numeric constant. If it is non-zero,
// replace the `IF THEN ELSE` call with the "true" branch, otherwise replace
// it with the "false" branch.
return conditionExpr.value !== 0
? reduceConditionals(expr.args[1], opts)
: reduceConditionals(expr.args[2], opts)
Expand Down

0 comments on commit 62e1ab1

Please sign in to comment.