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'
|
kind: 'predicate'
|
||||||
predicate: string
|
predicate: string
|
||||||
args: TermNode[]
|
args: TermNode[]
|
||||||
|
options?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -28,6 +28,15 @@ import type {
|
|||||||
} from 'imhotep-core'
|
} from 'imhotep-core'
|
||||||
import { adaptCanonicalWorldToSolver } from 'imhotep-core'
|
import { adaptCanonicalWorldToSolver } from 'imhotep-core'
|
||||||
import type { DomainRef, FormulaNode } from 'imhotep-core'
|
import type { DomainRef, FormulaNode } from 'imhotep-core'
|
||||||
|
import {
|
||||||
|
isForAllFormula,
|
||||||
|
isExistsFormula,
|
||||||
|
isAndFormula,
|
||||||
|
isOrFormula,
|
||||||
|
isNotFormula,
|
||||||
|
isImpliesFormula,
|
||||||
|
isPredicateCall,
|
||||||
|
} from 'imhotep-core'
|
||||||
import {
|
import {
|
||||||
computeGeometryCacheKey,
|
computeGeometryCacheKey,
|
||||||
readCachedExtractionResult,
|
readCachedExtractionResult,
|
||||||
@@ -113,18 +122,18 @@ export {
|
|||||||
export function collectPredicates(formula: FormulaNode): string[] {
|
export function collectPredicates(formula: FormulaNode): string[] {
|
||||||
const predicates: string[] = []
|
const predicates: string[] = []
|
||||||
function walk(node: FormulaNode) {
|
function walk(node: FormulaNode) {
|
||||||
if (node.kind === 'predicate') {
|
if (isPredicateCall(node)) {
|
||||||
predicates.push((node as any).predicate)
|
predicates.push(node.predicate)
|
||||||
} else if (node.kind === 'forall' || node.kind === 'exists') {
|
} else if (isForAllFormula(node) || isExistsFormula(node)) {
|
||||||
walk((node as any).body)
|
walk(node.body)
|
||||||
} else if (node.kind === 'and' || node.kind === 'or') {
|
} else if (isAndFormula(node) || isOrFormula(node)) {
|
||||||
walk((node as any).left)
|
walk(node.left)
|
||||||
walk((node as any).right)
|
walk(node.right)
|
||||||
} else if (node.kind === 'not') {
|
} else if (isNotFormula(node)) {
|
||||||
walk((node as any).operand)
|
walk(node.operand)
|
||||||
} else if (node.kind === 'implies') {
|
} else if (isImpliesFormula(node)) {
|
||||||
walk((node as any).antecedent)
|
walk(node.antecedent)
|
||||||
walk((node as any).consequent)
|
walk(node.consequent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
walk(formula)
|
walk(formula)
|
||||||
@@ -152,26 +161,26 @@ export function formulaNeedsCssLengthMetrics(formula: FormulaNode): boolean {
|
|||||||
}
|
}
|
||||||
const walk = (node: FormulaNode): void => {
|
const walk = (node: FormulaNode): void => {
|
||||||
if (needs) return
|
if (needs) return
|
||||||
if (node.kind === 'predicate') {
|
if (isPredicateCall(node)) {
|
||||||
scanValue((node as any).options)
|
scanValue(node.options)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (node.kind === 'forall' || node.kind === 'exists') {
|
if (isForAllFormula(node) || isExistsFormula(node)) {
|
||||||
walk((node as any).body)
|
walk(node.body)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (node.kind === 'and' || node.kind === 'or') {
|
if (isAndFormula(node) || isOrFormula(node)) {
|
||||||
walk((node as any).left)
|
walk(node.left)
|
||||||
walk((node as any).right)
|
walk(node.right)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (node.kind === 'not') {
|
if (isNotFormula(node)) {
|
||||||
walk((node as any).operand)
|
walk(node.operand)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (node.kind === 'implies') {
|
if (isImpliesFormula(node)) {
|
||||||
walk((node as any).antecedent)
|
walk(node.antecedent)
|
||||||
walk((node as any).consequent)
|
walk(node.consequent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
walk(formula)
|
walk(formula)
|
||||||
@@ -206,16 +215,16 @@ export function computeRequiredFacts(formulas: FormulaNode[]): {
|
|||||||
let found = false
|
let found = false
|
||||||
const scan = (node: FormulaNode) => {
|
const scan = (node: FormulaNode) => {
|
||||||
if (found) return
|
if (found) return
|
||||||
if (node.kind === 'forall' || node.kind === 'exists') {
|
if (isForAllFormula(node) || isExistsFormula(node)) {
|
||||||
for (const b of node.bindings) {
|
for (const b of node.bindings) {
|
||||||
if (b.domain.parentVar) { found = true; return }
|
if (b.domain.parentVar) { found = true; return }
|
||||||
}
|
}
|
||||||
scan(node.body)
|
scan(node.body)
|
||||||
} else if (node.kind === 'and' || node.kind === 'or') {
|
} else if (isAndFormula(node) || isOrFormula(node)) {
|
||||||
scan(node.left); scan(node.right)
|
scan(node.left); scan(node.right)
|
||||||
} else if (node.kind === 'not') {
|
} else if (isNotFormula(node)) {
|
||||||
scan(node.operand)
|
scan(node.operand)
|
||||||
} else if (node.kind === 'implies') {
|
} else if (isImpliesFormula(node)) {
|
||||||
scan(node.antecedent); scan(node.consequent)
|
scan(node.antecedent); scan(node.consequent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,21 +247,20 @@ export function usesLayoutSpace(formula: FormulaNode): boolean {
|
|||||||
let found = false
|
let found = false
|
||||||
function walk(node: FormulaNode) {
|
function walk(node: FormulaNode) {
|
||||||
if (found) return
|
if (found) return
|
||||||
if (node.kind === 'predicate') {
|
if (isPredicateCall(node)) {
|
||||||
const options = (node as any).options
|
if (node.options?.space === 'layout') {
|
||||||
if (options?.space === 'layout') {
|
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
} else if (node.kind === 'forall' || node.kind === 'exists') {
|
} else if (isForAllFormula(node) || isExistsFormula(node)) {
|
||||||
walk((node as any).body)
|
walk(node.body)
|
||||||
} else if (node.kind === 'and' || node.kind === 'or') {
|
} else if (isAndFormula(node) || isOrFormula(node)) {
|
||||||
walk((node as any).left)
|
walk(node.left)
|
||||||
walk((node as any).right)
|
walk(node.right)
|
||||||
} else if (node.kind === 'not') {
|
} else if (isNotFormula(node)) {
|
||||||
walk((node as any).operand)
|
walk(node.operand)
|
||||||
} else if (node.kind === 'implies') {
|
} else if (isImpliesFormula(node)) {
|
||||||
walk((node as any).antecedent)
|
walk(node.antecedent)
|
||||||
walk((node as any).consequent)
|
walk(node.consequent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
walk(formula)
|
walk(formula)
|
||||||
@@ -1332,7 +1340,7 @@ export function getSelectorsFromAssertion(assertion: FluentRelation | FluentAsse
|
|||||||
export function getSelectorsFromFormula(formula: FormulaNode): string[] {
|
export function getSelectorsFromFormula(formula: FormulaNode): string[] {
|
||||||
const selectors = new Set<string>()
|
const selectors = new Set<string>()
|
||||||
const visit = (node: FormulaNode) => {
|
const visit = (node: FormulaNode) => {
|
||||||
if (node.kind === 'forall' || node.kind === 'exists') {
|
if (isForAllFormula(node) || isExistsFormula(node)) {
|
||||||
for (const b of node.bindings) {
|
for (const b of node.bindings) {
|
||||||
if (b.domain.selector && !b.domain.selector.startsWith('$')) {
|
if (b.domain.selector && !b.domain.selector.startsWith('$')) {
|
||||||
selectors.add(b.domain.selector)
|
selectors.add(b.domain.selector)
|
||||||
@@ -1341,16 +1349,16 @@ export function getSelectorsFromFormula(formula: FormulaNode): string[] {
|
|||||||
visit(node.body)
|
visit(node.body)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (node.kind === 'and' || node.kind === 'or') {
|
if (isAndFormula(node) || isOrFormula(node)) {
|
||||||
visit(node.left)
|
visit(node.left)
|
||||||
visit(node.right)
|
visit(node.right)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (node.kind === 'not') {
|
if (isNotFormula(node)) {
|
||||||
visit(node.operand)
|
visit(node.operand)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (node.kind === 'implies') {
|
if (isImpliesFormula(node)) {
|
||||||
visit(node.antecedent)
|
visit(node.antecedent)
|
||||||
visit(node.consequent)
|
visit(node.consequent)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user