Improve display of KCL backtrace (#7582)

* Improve display of KCL backtrace

* Fix circular dep
This commit is contained in:
Jonathan Tran
2025-06-23 17:11:13 -04:00
committed by GitHub
parent 0cd6031aae
commit bb3a74076f
12 changed files with 49 additions and 30 deletions

View File

@ -265,6 +265,8 @@ middle(0)
})
await expect(
page.getByText(`assert failed: Expected 0 to be greater than 0 but it wasn't
Backtrace:
assert()
check()
middle()`)

View File

@ -27,7 +27,8 @@ import { getConstraintInfoKw } from '@src/lang/std/sketch'
import type { ConstrainInfo } from '@src/lang/std/stdTypes'
import { topLevelRange } from '@src/lang/util'
import type { CallExpressionKw, Expr, PathToNode } from '@src/lang/wasm'
import { defaultSourceRange, parse, recast, resultIsOk } from '@src/lang/wasm'
import { parse, recast, resultIsOk } from '@src/lang/wasm'
import { defaultSourceRange } from '@src/lang/sourceRange'
import { cameraMouseDragGuards } from '@src/lib/cameraControls'
import {
codeManager,

View File

@ -144,14 +144,13 @@ import type { SegmentInputs } from '@src/lang/std/stdTypes'
import { crossProduct, topLevelRange } from '@src/lang/util'
import type { PathToNode, VariableMap } from '@src/lang/wasm'
import {
defaultSourceRange,
getTangentialArcToInfo,
parse,
recast,
resultIsOk,
sketchFromKclValue,
sourceRangeFromRust,
} from '@src/lang/wasm'
import { defaultSourceRange, sourceRangeFromRust } from '@src/lang/sourceRange'
import { EXECUTION_TYPE_MOCK } from '@src/lib/constants'
import {
getRectangleCallExpressions,

View File

@ -5,7 +5,7 @@ import { getNodeFromPath } from '@src/lang/queryAst'
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
import { codeRefFromRange } from '@src/lang/std/artifactGraph'
import { topLevelRange } from '@src/lang/util'
import { defaultSourceRange } from '@src/lang/wasm'
import { defaultSourceRange } from '@src/lang/sourceRange'
import { codeToIdSelections } from '@src/lib/selections'
import { editorManager, kclManager } from '@src/lib/singletons'
import { trap } from '@src/lib/trap'

View File

@ -16,7 +16,7 @@ import {
codeRefFromRange,
getArtifactFromRange,
} from '@src/lang/std/artifactGraph'
import { sourceRangeFromRust } from '@src/lang/wasm'
import { sourceRangeFromRust } from '@src/lang/sourceRange'
import {
filterOperations,
getOperationIcon,

View File

@ -13,7 +13,7 @@ import {
} from '@src/lang/std/artifactGraph'
import { isTopLevelModule } from '@src/lang/util'
import type { CallExpressionKw, PathToNode } from '@src/lang/wasm'
import { defaultSourceRange } from '@src/lang/wasm'
import { defaultSourceRange } from '@src/lang/sourceRange'
import type { DefaultPlaneStr } from '@src/lib/planes'
import { getEventForSelectWithPoint } from '@src/lib/selections'
import {

View File

@ -16,8 +16,9 @@ import type { Operation } from '@rust/kcl-lib/bindings/Operation'
import type { SourceRange } from '@rust/kcl-lib/bindings/SourceRange'
import { defaultArtifactGraph } from '@src/lang/std/artifactGraph'
import { isTopLevelModule } from '@src/lang/util'
import type { ArtifactGraph } from '@src/lang/wasm'
import { type ArtifactGraph } from '@src/lang/wasm'
import type { BacktraceItem } from '@rust/kcl-lib/bindings/BacktraceItem'
import { sourceRangeContains } from '@src/lang/sourceRange'
type ExtractKind<T> = T extends { kind: infer K } ? K : never
export class KCLError extends Error {
@ -377,13 +378,13 @@ export function kclErrorsToDiagnostics(
let message = err.msg
if (err.kclBacktrace.length > 0) {
// Show the backtrace in the error message.
const backtraceLines: Array<string> = []
for (let i = 0; i < err.kclBacktrace.length; i++) {
const item = err.kclBacktrace[i]
if (
i > 0 &&
isTopLevelModule(item.sourceRange) &&
item.sourceRange[0] !== err.sourceRange[0] &&
item.sourceRange[1] !== err.sourceRange[1]
!sourceRangeContains(item.sourceRange, err.sourceRange)
) {
diagnostics.push({
from: toUtf16(item.sourceRange[0], sourceCode),
@ -397,7 +398,11 @@ export function kclErrorsToDiagnostics(
break
}
const name = item.fnName ? `${item.fnName}()` : '(anonymous)'
message += `\n${name}`
backtraceLines.push(name)
}
// If the backtrace is only one line, it's not helpful to show.
if (backtraceLines.length > 1) {
message += `\n\nBacktrace:\n${backtraceLines.join('\n')}`
}
}
if (err.nonFatal.length > 0) {

28
src/lang/sourceRange.ts Normal file
View File

@ -0,0 +1,28 @@
import type { SourceRange } from '@rust/kcl-lib/bindings/SourceRange'
/**
* Convert a SourceRange as used inside the KCL interpreter into the above one for use in the
* frontend (essentially we're eagerly checking whether the frontend should care about the SourceRange
* so as not to expose details of the interpreter's current representation of module ids throughout
* the frontend).
*/
export function sourceRangeFromRust(s: SourceRange): SourceRange {
return [s[0], s[1], s[2]]
}
/**
* Create a default SourceRange for testing or as a placeholder.
*/
export function defaultSourceRange(): SourceRange {
return [0, 0, 0]
}
/**
* Returns true if the first range is equal to or contains the second range.
*/
export function sourceRangeContains(
outer: SourceRange,
inner: SourceRange
): boolean {
return outer[0] <= inner[0] && outer[1] >= inner[1] && outer[2] === inner[2]
}

View File

@ -12,7 +12,7 @@ import type { EngineCommand, ResponseMap } from '@src/lang/std/artifactGraph'
import type { CommandLog } from '@src/lang/std/commandLog'
import { CommandLogType } from '@src/lang/std/commandLog'
import type { SourceRange } from '@src/lang/wasm'
import { defaultSourceRange } from '@src/lang/wasm'
import { defaultSourceRange } from '@src/lang/sourceRange'
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from '@src/lib/constants'
import { markOnce } from '@src/lib/performance'
import type RustContext from '@src/lib/rustContext'

View File

@ -67,6 +67,7 @@ import {
} from '@src/lang/queryAstConstants'
import type { NumericType } from '@rust/kcl-lib/bindings/NumericType'
import { isTopLevelModule } from '@src/lang/util'
import { defaultSourceRange, sourceRangeFromRust } from '@src/lang/sourceRange'
export type { ArrayExpression } from '@rust/kcl-lib/bindings/ArrayExpression'
export type {
@ -137,23 +138,6 @@ export type { Path } from '@rust/kcl-lib/bindings/Path'
export type { Sketch } from '@rust/kcl-lib/bindings/Sketch'
export type { Solid } from '@rust/kcl-lib/bindings/Solid'
/**
* Convert a SourceRange as used inside the KCL interpreter into the above one for use in the
* frontend (essentially we're eagerly checking whether the frontend should care about the SourceRange
* so as not to expose details of the interpreter's current representation of module ids throughout
* the frontend).
*/
export function sourceRangeFromRust(s: SourceRange): SourceRange {
return [s[0], s[1], s[2]]
}
/**
* Create a default SourceRange for testing or as a placeholder.
*/
export function defaultSourceRange(): SourceRange {
return [0, 0, 0]
}
function bestSourceRange(error: RustKclError): SourceRange {
if (error.details.sourceRanges.length === 0) {
return defaultSourceRange()

View File

@ -1,11 +1,11 @@
import type { NodePath } from '@rust/kcl-lib/bindings/NodePath'
import type { Operation } from '@rust/kcl-lib/bindings/Operation'
import { defaultSourceRange } from '@src/lang/sourceRange'
import { topLevelRange } from '@src/lang/util'
import {
assertParse,
defaultNodePath,
defaultSourceRange,
nodePathFromRange,
type SourceRange,
} from '@src/lang/wasm'

View File

@ -24,7 +24,7 @@ import type {
Program,
SourceRange,
} from '@src/lang/wasm'
import { defaultSourceRange } from '@src/lang/wasm'
import { defaultSourceRange } from '@src/lang/sourceRange'
import type { ArtifactEntry, ArtifactIndex } from '@src/lib/artifactIndex'
import type { CommandArgument } from '@src/lib/commandTypes'
import type { DefaultPlaneStr } from '@src/lib/planes'