fix: replace as any casts in AST walkers with typed guards + add options to PredicateCall
logic-ast.ts: add optional options?: Record<string, unknown> to PredicateCall interface. Previously any predicate needing options (e.g., space, dimension, tolerance) smuggled them via (node as any) .options, bypassing the type system entirely. extraction.ts: replace all 18 (node as any).body/.left/.right etc. casts with proper type guard narrowing (isForAllFormula, isExistsFormula, isAndFormula, isOrFormula, isNotFormula, isImpliesFormula, isPredicateCall). Affected functions: collectPredicates, formulaNeedsCssLengthMetrics, usesLayoutSpace, computeRequiredFacts & nestDomAncestry, getSelectorsFromFormula. 595 SDK + 57 E2E tests pass.
This commit is contained in:
@@ -81,6 +81,7 @@ export interface PredicateCall extends AstNode {
|
||||
kind: 'predicate'
|
||||
predicate: string
|
||||
args: TermNode[]
|
||||
options?: Record<string, unknown>
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -28,6 +28,15 @@ import type {
|
||||
} from 'imhotep-core'
|
||||
import { adaptCanonicalWorldToSolver } from 'imhotep-core'
|
||||
import type { DomainRef, FormulaNode } from 'imhotep-core'
|
||||
import {
|
||||
isForAllFormula,
|
||||
isExistsFormula,
|
||||
isAndFormula,
|
||||
isOrFormula,
|
||||
isNotFormula,
|
||||
isImpliesFormula,
|
||||
isPredicateCall,
|
||||
} from 'imhotep-core'
|
||||
import {
|
||||
computeGeometryCacheKey,
|
||||
readCachedExtractionResult,
|
||||
@@ -113,18 +122,18 @@ export {
|
||||
export function collectPredicates(formula: FormulaNode): string[] {
|
||||
const predicates: string[] = []
|
||||
function walk(node: FormulaNode) {
|
||||
if (node.kind === 'predicate') {
|
||||
predicates.push((node as any).predicate)
|
||||
} else if (node.kind === 'forall' || node.kind === 'exists') {
|
||||
walk((node as any).body)
|
||||
} else if (node.kind === 'and' || node.kind === 'or') {
|
||||
walk((node as any).left)
|
||||
walk((node as any).right)
|
||||
} else if (node.kind === 'not') {
|
||||
walk((node as any).operand)
|
||||
} else if (node.kind === 'implies') {
|
||||
walk((node as any).antecedent)
|
||||
walk((node as any).consequent)
|
||||
if (isPredicateCall(node)) {
|
||||
predicates.push(node.predicate)
|
||||
} else if (isForAllFormula(node) || isExistsFormula(node)) {
|
||||
walk(node.body)
|
||||
} else if (isAndFormula(node) || isOrFormula(node)) {
|
||||
walk(node.left)
|
||||
walk(node.right)
|
||||
} else if (isNotFormula(node)) {
|
||||
walk(node.operand)
|
||||
} else if (isImpliesFormula(node)) {
|
||||
walk(node.antecedent)
|
||||
walk(node.consequent)
|
||||
}
|
||||
}
|
||||
walk(formula)
|
||||
@@ -152,26 +161,26 @@ export function formulaNeedsCssLengthMetrics(formula: FormulaNode): boolean {
|
||||
}
|
||||
const walk = (node: FormulaNode): void => {
|
||||
if (needs) return
|
||||
if (node.kind === 'predicate') {
|
||||
scanValue((node as any).options)
|
||||
if (isPredicateCall(node)) {
|
||||
scanValue(node.options)
|
||||
return
|
||||
}
|
||||
if (node.kind === 'forall' || node.kind === 'exists') {
|
||||
walk((node as any).body)
|
||||
if (isForAllFormula(node) || isExistsFormula(node)) {
|
||||
walk(node.body)
|
||||
return
|
||||
}
|
||||
if (node.kind === 'and' || node.kind === 'or') {
|
||||
walk((node as any).left)
|
||||
walk((node as any).right)
|
||||
if (isAndFormula(node) || isOrFormula(node)) {
|
||||
walk(node.left)
|
||||
walk(node.right)
|
||||
return
|
||||
}
|
||||
if (node.kind === 'not') {
|
||||
walk((node as any).operand)
|
||||
if (isNotFormula(node)) {
|
||||
walk(node.operand)
|
||||
return
|
||||
}
|
||||
if (node.kind === 'implies') {
|
||||
walk((node as any).antecedent)
|
||||
walk((node as any).consequent)
|
||||
if (isImpliesFormula(node)) {
|
||||
walk(node.antecedent)
|
||||
walk(node.consequent)
|
||||
}
|
||||
}
|
||||
walk(formula)
|
||||
@@ -206,16 +215,16 @@ export function computeRequiredFacts(formulas: FormulaNode[]): {
|
||||
let found = false
|
||||
const scan = (node: FormulaNode) => {
|
||||
if (found) return
|
||||
if (node.kind === 'forall' || node.kind === 'exists') {
|
||||
if (isForAllFormula(node) || isExistsFormula(node)) {
|
||||
for (const b of node.bindings) {
|
||||
if (b.domain.parentVar) { found = true; return }
|
||||
}
|
||||
scan(node.body)
|
||||
} else if (node.kind === 'and' || node.kind === 'or') {
|
||||
} else if (isAndFormula(node) || isOrFormula(node)) {
|
||||
scan(node.left); scan(node.right)
|
||||
} else if (node.kind === 'not') {
|
||||
} else if (isNotFormula(node)) {
|
||||
scan(node.operand)
|
||||
} else if (node.kind === 'implies') {
|
||||
} else if (isImpliesFormula(node)) {
|
||||
scan(node.antecedent); scan(node.consequent)
|
||||
}
|
||||
}
|
||||
@@ -238,21 +247,20 @@ export function usesLayoutSpace(formula: FormulaNode): boolean {
|
||||
let found = false
|
||||
function walk(node: FormulaNode) {
|
||||
if (found) return
|
||||
if (node.kind === 'predicate') {
|
||||
const options = (node as any).options
|
||||
if (options?.space === 'layout') {
|
||||
if (isPredicateCall(node)) {
|
||||
if (node.options?.space === 'layout') {
|
||||
found = true
|
||||
}
|
||||
} else if (node.kind === 'forall' || node.kind === 'exists') {
|
||||
walk((node as any).body)
|
||||
} else if (node.kind === 'and' || node.kind === 'or') {
|
||||
walk((node as any).left)
|
||||
walk((node as any).right)
|
||||
} else if (node.kind === 'not') {
|
||||
walk((node as any).operand)
|
||||
} else if (node.kind === 'implies') {
|
||||
walk((node as any).antecedent)
|
||||
walk((node as any).consequent)
|
||||
} else if (isForAllFormula(node) || isExistsFormula(node)) {
|
||||
walk(node.body)
|
||||
} else if (isAndFormula(node) || isOrFormula(node)) {
|
||||
walk(node.left)
|
||||
walk(node.right)
|
||||
} else if (isNotFormula(node)) {
|
||||
walk(node.operand)
|
||||
} else if (isImpliesFormula(node)) {
|
||||
walk(node.antecedent)
|
||||
walk(node.consequent)
|
||||
}
|
||||
}
|
||||
walk(formula)
|
||||
@@ -1332,7 +1340,7 @@ export function getSelectorsFromAssertion(assertion: FluentRelation | FluentAsse
|
||||
export function getSelectorsFromFormula(formula: FormulaNode): string[] {
|
||||
const selectors = new Set<string>()
|
||||
const visit = (node: FormulaNode) => {
|
||||
if (node.kind === 'forall' || node.kind === 'exists') {
|
||||
if (isForAllFormula(node) || isExistsFormula(node)) {
|
||||
for (const b of node.bindings) {
|
||||
if (b.domain.selector && !b.domain.selector.startsWith('$')) {
|
||||
selectors.add(b.domain.selector)
|
||||
@@ -1341,16 +1349,16 @@ export function getSelectorsFromFormula(formula: FormulaNode): string[] {
|
||||
visit(node.body)
|
||||
return
|
||||
}
|
||||
if (node.kind === 'and' || node.kind === 'or') {
|
||||
if (isAndFormula(node) || isOrFormula(node)) {
|
||||
visit(node.left)
|
||||
visit(node.right)
|
||||
return
|
||||
}
|
||||
if (node.kind === 'not') {
|
||||
if (isNotFormula(node)) {
|
||||
visit(node.operand)
|
||||
return
|
||||
}
|
||||
if (node.kind === 'implies') {
|
||||
if (isImpliesFormula(node)) {
|
||||
visit(node.antecedent)
|
||||
visit(node.consequent)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user