Skip to content

Commit 6bfdda3

Browse files
authored
Do not crash on empty main function body. (#10990)
Fixes #10976 https://github.com/user-attachments/assets/00b2279d-2acf-468b-8c3c-aa6885cba23d Addressed issue of empty body block being incorrectly "repaired" into an empty group, geneating invalid `()` syntax. Appending nodes to an empty function now actually replaces its body block, instead of creating a temporary orphan body block node. Note that empty main function is still considered an error on the engine side, but it doesn't impact the IDE in negative way. Things work again as soon as a node is inserted. Also fixed a few issues causing hot-reloading to break. Now edited AST code properly hot-reloads all affected modules without breaking the app.
1 parent 3420a05 commit 6bfdda3

File tree

9 files changed

+46
-27
lines changed

9 files changed

+46
-27
lines changed

app/gui2/src/components/GraphEditor/GraphNode.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ watchEffect(() => {
417417
@pointerleave="(nodeHovered = false), updateNodeHover(undefined)"
418418
@pointermove="updateNodeHover"
419419
>
420-
<Teleport v-if="navigator && !edited" :to="graphNodeSelections">
420+
<Teleport v-if="navigator && !edited && graphNodeSelections" :to="graphNodeSelections">
421421
<GraphNodeSelection
422422
:data-node-id="nodeId"
423423
:nodePosition="props.node.position"

app/gui2/src/composables/navigator.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,20 +164,20 @@ export function useNavigator(
164164
function panToImpl(points: Partial<Vec2>[]) {
165165
let target = viewport.value
166166
for (const point of points.reverse()) target = target.offsetToInclude(point) ?? target
167-
targetCenter.value = target.center()
167+
targetCenter.value = target.center().finiteOrZero()
168168
}
169169

170170
/** Pan immediately to center the viewport at the given point, in scene coordinates. */
171171
function scrollTo(newCenter: Vec2) {
172172
resetTargetFollowing()
173-
targetCenter.value = newCenter
173+
targetCenter.value = newCenter.finiteOrZero()
174174
center.skip()
175175
}
176176

177177
/** Set viewport center point and scale value immediately, skipping animations. */
178178
function setCenterAndScale(newCenter: Vec2, newScale: number) {
179179
resetTargetFollowing()
180-
targetCenter.value = newCenter
180+
targetCenter.value = newCenter.finiteOrZero()
181181
targetScale.value = newScale
182182
scale.skip()
183183
center.skip()
@@ -325,7 +325,7 @@ export function useNavigator(
325325
const scenePos0 = clientToScenePos(clientPos)
326326
const result = f()
327327
const scenePos1 = clientToScenePos(clientPos)
328-
targetCenter.value = center.value.add(scenePos0.sub(scenePos1))
328+
targetCenter.value = center.value.add(scenePos0.sub(scenePos1)).finiteOrZero()
329329
center.skip()
330330
return result
331331
}

app/gui2/src/providers/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const MISSING = Symbol('MISSING')
3131
* [Context API]: https://vuejs.org/guide/components/provide-inject.html#provide-inject
3232
*/
3333
export function createContextStore<F extends (...args: any[]) => any>(name: string, factory: F) {
34-
const provideKey = Symbol(name) as InjectionKey<ReturnType<F>>
34+
const provideKey = Symbol.for(`contextStore-${name}`) as InjectionKey<ReturnType<F>>
3535

3636
/**
3737
* Create the instance of a store and store it in the current component's context. All child

app/gui2/src/stores/graph/graphDatabase.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class BindingsDb {
4444

4545
readFunctionAst(
4646
func: Ast.Function,
47-
rawFunc: RawAst.Tree.Function,
47+
rawFunc: RawAst.Tree.Function | undefined,
4848
moduleCode: string,
4949
getSpan: (id: AstId) => SourceRange | undefined,
5050
) {
@@ -422,7 +422,7 @@ export class GraphDb {
422422
/** Deeply scan the function to perform alias-analysis. */
423423
updateBindings(
424424
functionAst_: Ast.Function,
425-
rawFunction: RawAst.Tree.Function,
425+
rawFunction: RawAst.Tree.Function | undefined,
426426
moduleCode: string,
427427
getSpan: (id: AstId) => SourceRange | undefined,
428428
) {

app/gui2/src/stores/graph/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC
179179
const methodSpan = moduleSource.getSpan(method.id)
180180
assert(methodSpan != null)
181181
const rawFunc = toRaw.get(sourceRangeKey(methodSpan))
182-
assert(rawFunc != null)
183182
const getSpan = (id: AstId) => moduleSource.getSpan(id)
184183
db.updateBindings(method, rawFunc, moduleSource.text, getSpan)
185184
})

app/gui2/src/util/database/reactiveDb.ts

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@ import { LazySyncEffectSet, NonReactiveView } from '@/util/reactivity'
1515
import * as map from 'lib0/map'
1616
import { ObservableV2 } from 'lib0/observable'
1717
import * as set from 'lib0/set'
18-
import { computed, effectScope, reactive, toRaw, type ComputedRef, type DebuggerOptions } from 'vue'
18+
import {
19+
computed,
20+
effectScope,
21+
onScopeDispose,
22+
reactive,
23+
toRaw,
24+
type ComputedRef,
25+
type DebuggerOptions,
26+
} from 'vue'
1927

2028
export type OnDelete = (cleanupFn: () => void) => void
2129

@@ -218,14 +226,18 @@ export class ReactiveIndex<K, V, IK, IV> {
218226
constructor(db: ReactiveDb<K, V>, indexer: Indexer<K, V, IK, IV>) {
219227
this.forward = reactive(map.create())
220228
this.reverse = reactive(map.create())
221-
this.effects = new LazySyncEffectSet()
222-
db.on('entryAdded', (key, value, onDelete) => {
223-
const stopEffect = this.effects.lazyEffect((onCleanup) => {
224-
const keyValues = indexer(key, value)
225-
keyValues.forEach(([key, value]) => this.writeToIndex(key, value))
226-
onCleanup(() => keyValues.forEach(([key, value]) => this.removeFromIndex(key, value)))
229+
const scope = effectScope()
230+
this.effects = new LazySyncEffectSet(scope)
231+
scope.run(() => {
232+
const handler = db.on('entryAdded', (key, value, onDelete) => {
233+
const stopEffect = this.effects.lazyEffect((onCleanup) => {
234+
const keyValues = indexer(key, value)
235+
keyValues.forEach(([key, value]) => this.writeToIndex(key, value))
236+
onCleanup(() => keyValues.forEach(([key, value]) => this.removeFromIndex(key, value)))
237+
})
238+
onDelete(() => stopEffect())
227239
})
228-
onDelete(() => stopEffect())
240+
onScopeDispose(() => db.off('entryAdded', handler))
229241
})
230242
}
231243

@@ -353,16 +365,20 @@ export class ReactiveMapping<K, V, M> {
353365
*/
354366
constructor(db: ReactiveDb<K, V>, indexer: Mapper<K, V, M>, debugOptions?: DebuggerOptions) {
355367
this.computed = reactive(map.create())
356-
db.on('entryAdded', (key, value, onDelete) => {
357-
const scope = effectScope()
358-
const mappedValue = scope.run(() =>
359-
computed(() => scope.run(() => indexer(key, value)), debugOptions),
360-
)! // This non-null assertion is SAFE, as the scope is initially active.
361-
this.computed.set(key, mappedValue)
362-
onDelete(() => {
363-
scope.stop()
364-
this.computed.delete(key)
368+
const scope = effectScope()
369+
scope.run(() => {
370+
const handler = db.on('entryAdded', (key, value, onDelete) => {
371+
const scope = effectScope()
372+
const mappedValue = scope.run(() =>
373+
computed(() => scope.run(() => indexer(key, value)), debugOptions),
374+
)! // This non-null assertion is SAFE, as the scope is initially active.
375+
this.computed.set(key, mappedValue)
376+
onDelete(() => {
377+
scope.stop()
378+
this.computed.delete(key)
379+
})
365380
})
381+
onScopeDispose(() => db.off('entryAdded', handler))
366382
})
367383
}
368384

app/gui2/src/util/reactivity.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ export type StopEffect = () => void
4545
*/
4646
export class LazySyncEffectSet {
4747
_dirtyRunners = new Set<() => void>()
48-
_scope = effectScope()
4948
_boundFlush = this.flush.bind(this)
5049

50+
constructor(private _scope = effectScope()) {}
51+
5152
/**
5253
* Add an effect to the lazy set. The effect will run once immediately, and any subsequent runs
5354
* will be delayed until the next flush. Only effects that were notified about a dependency change

app/ydoc-shared/src/ast/parse.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,8 @@ function checkSpans(expected: NodeSpanMap, encountered: NodeSpanMap, code: strin
668668
const lostBlock = new Array<Ast>()
669669
for (const [key, ast] of lost) {
670670
const [start, end] = sourceRangeFromKey(key)
671+
// Do not report lost empty body blocks, we don't want them to be considered for repair.
672+
if (start === end && ast instanceof BodyBlock) continue
671673
;(code.substring(start, end).match(/[\r\n]/) ? lostBlock : lostInline).push(ast)
672674
}
673675
return { lostInline, lostBlock }

app/ydoc-shared/src/ast/tree.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,6 +1962,7 @@ export class MutableFunction extends Function implements MutableAst {
19621962
if (oldBody instanceof MutableBodyBlock) return oldBody
19631963
const newBody = BodyBlock.new([], this.module)
19641964
if (oldBody) newBody.push(oldBody.take())
1965+
this.setBody(newBody)
19651966
return newBody
19661967
}
19671968
}

0 commit comments

Comments
 (0)