Skip to content

Commit 8405d02

Browse files
committed
fix(function): report code-placeholder compiler invariants as server failures
Overlapping source edits and exhausted parser-sentinel/Python-marker allocation are broken compiler invariants, not problems in the user's code, but they threw CodePlaceholderCompileError and the Function route answered them with 422 like a user syntax error. They now throw a sibling CodePlaceholderInvariantError, which the route's instanceof check does not match, so it falls through to the existing 500 path. User placeholder and syntax errors keep their 422.
1 parent 66e5705 commit 8405d02

4 files changed

Lines changed: 40 additions & 3 deletions

File tree

‎apps/sim/lib/execution/code-placeholders/compiler.test.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import {
66
type CodePlaceholderRuntimeBinding,
77
compileCodePlaceholders,
88
} from '@/lib/execution/code-placeholders'
9+
import {
10+
applySourceEdits,
11+
CodePlaceholderCompileError,
12+
CodePlaceholderInvariantError,
13+
} from '@/lib/execution/code-placeholders/shared'
914
import { CodeLanguage } from '@/lib/execution/languages'
1015

1116
const installedGlobals = new Set<string>()
@@ -1543,3 +1548,21 @@ describe('python true positives survive the dot guard', () => {
15431548
expect(await directReadNames(code, CodeLanguage.Python)).toEqual(['API_KEY'])
15441549
})
15451550
})
1551+
1552+
describe('compiler invariant failures', () => {
1553+
it('reports overlapping source edits as a compiler invariant, not a user compile error', () => {
1554+
const thrown = (() => {
1555+
try {
1556+
applySourceEdits('return value', [
1557+
{ start: 0, end: 6, text: 'yield' },
1558+
{ start: 3, end: 9, text: 'x' },
1559+
])
1560+
} catch (error) {
1561+
return error
1562+
}
1563+
})()
1564+
1565+
expect(thrown).toBeInstanceOf(CodePlaceholderInvariantError)
1566+
expect(thrown).not.toBeInstanceOf(CodePlaceholderCompileError)
1567+
})
1568+
})

‎apps/sim/lib/execution/code-placeholders/javascript.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import ts from '@typescript/typescript6'
22
import {
33
applySourceEdits,
44
CodePlaceholderCompileError,
5+
CodePlaceholderInvariantError,
56
createCodePlaceholderCompilationContext,
67
isOffsetInRanges,
78
type SourceEdit,
@@ -261,7 +262,7 @@ function createSentinel(
261262
return sentinel
262263
}
263264
}
264-
throw new CodePlaceholderCompileError('Unable to allocate a collision-free parser sentinel')
265+
throw new CodePlaceholderInvariantError('Unable to allocate a collision-free parser sentinel')
265266
}
266267

267268
function createSentinelSource(

‎apps/sim/lib/execution/code-placeholders/python.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { sha256Hex } from '@sim/security/hash'
22
import {
33
applySourceEdits,
44
CodePlaceholderCompileError,
5+
CodePlaceholderInvariantError,
56
createCodePlaceholderCompilationContext,
67
isOffsetInRanges,
78
type SourceEdit,
@@ -243,7 +244,7 @@ function createSentinel(code: string): string {
243244
const sentinel = `__sim_placeholder_${digest}__`
244245
if (!code.includes(sentinel)) return sentinel
245246
}
246-
throw new CodePlaceholderCompileError('Unable to allocate a collision-free Python marker')
247+
throw new CodePlaceholderInvariantError('Unable to allocate a collision-free Python marker')
247248
}
248249

249250
function pythonTriviaGap(

‎apps/sim/lib/execution/code-placeholders/shared.ts‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ export class CodePlaceholderCompileError extends Error {
2727
}
2828
}
2929

30+
/**
31+
* A broken compiler invariant: Sim's own compilation failed, not the user's code.
32+
* Deliberately not a {@link CodePlaceholderCompileError}, so callers that answer a
33+
* user compile error with a client status treat this as a server failure.
34+
*/
35+
export class CodePlaceholderInvariantError extends Error {
36+
constructor(message: string) {
37+
super(message)
38+
this.name = 'CodePlaceholderInvariantError'
39+
}
40+
}
41+
3042
/**
3143
* Scans `{{name}}` placeholders, accepting exactly what `/\{\{([^}]+)\}\}/g` accepts —
3244
* a name may contain `{`, because parameter keys are arbitrary strings rather than
@@ -270,7 +282,7 @@ export function applySourceEdits(code: string, edits: SourceEdit[]): string {
270282
let output = ''
271283
for (const edit of sorted) {
272284
if (edit.start < cursor || edit.end < edit.start || edit.end > code.length) {
273-
throw new CodePlaceholderCompileError('Overlapping code placeholder transformations')
285+
throw new CodePlaceholderInvariantError('Overlapping code placeholder transformations')
274286
}
275287
output += code.slice(cursor, edit.start)
276288
output += edit.text

0 commit comments

Comments
 (0)