Skip to content

Commit

Permalink
Merge pull request #10 from lume/fix-duplicate-signals
Browse files Browse the repository at this point in the history
fix: ensure that signalifying properties already signalified by the @signal decorator does not create double signals
  • Loading branch information
trusktr authored Oct 16, 2024
2 parents 019d8c1 + 4ecfad5 commit b9f9c55
Show file tree
Hide file tree
Showing 15 changed files with 126 additions and 24 deletions.
2 changes: 1 addition & 1 deletion dist/decorators/signal.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions dist/decorators/signal.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/decorators/signal.js.map

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions dist/decorators/signal.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/decorators/signal.test.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/signals/signalify.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export declare function signalify<T extends object>(obj: T, ...props: (keyof T)[
export declare function signalify<T extends object>(obj: T, ...props: [key: keyof T, initialValue: unknown][]): T;
export declare function __isPropSetAtLeastOnce(instance: object, prop: string | symbol): boolean;
export declare function __trackPropSetAtLeastOnce(instance: object, prop: string | symbol): void;
export declare const isSignalGetter: WeakSet<Function>;
export declare function __createSignalAccessor<T extends object>(obj: T, prop: Exclude<keyof T, number>, initialVal: unknown): void;
export declare function __getSignal(obj: object, storage: WeakMap<object, SignalFunction<unknown>>, initialVal: unknown): SignalFunction<unknown>;
//# sourceMappingURL=signalify.d.ts.map
2 changes: 1 addition & 1 deletion dist/signals/signalify.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/signals/signalify.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/signals/signalify.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/signals/signalify.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/signals/signalify.test.js.map

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions src/decorators/signal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {createEffect} from 'solid-js'
import {testButterflyProps} from '../index.test.js'
import {reactive} from './reactive.js'
import {signal} from './signal.js'
import {signalify} from '../signals/signalify.js'

describe('classy-solid', () => {
describe('@reactive, @signal', () => {
Expand Down Expand Up @@ -359,5 +360,47 @@ describe('classy-solid', () => {
expect(count).toBe(1)
expect(b!).toBe(b2)
})

it.only('prevents duplicate signals for any property', () => {
@reactive
class Insect {
@signal venomous = 0

@signal accessor legs = 6

#eyes = 10
@signal get eyes() {
return this.#eyes
}
@signal set eyes(n) {
this.#eyes = n
}

antennas = 0

constructor() {
// This should not add any extra signals for properties that
// are already signalified by the @signal decorator
signalify(this, 'venomous', 'legs', 'eyes', 'antennas')
}
}
const i = new Insect()

testNoDuplicateSignal(i, 'venomous')
testNoDuplicateSignal(i, 'legs')
testNoDuplicateSignal(i, 'eyes')
testNoDuplicateSignal(i, 'antennas')

function testNoDuplicateSignal(o: Insect, prop: keyof Insect) {
let count = 0
createEffect(() => {
count++
o[prop]
})
expect(count).toBe(1)
o[prop]++
expect(count).toBe(2) // it would be 3 if there were an extra signal
}
})
})
})
19 changes: 12 additions & 7 deletions src/decorators/signal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {$PROXY} from 'solid-js'
import {__getSignal, __trackPropSetAtLeastOnce, __createSignalAccessor} from '../signals/signalify.js'
import {__getSignal, __trackPropSetAtLeastOnce, __createSignalAccessor, isSignalGetter} from '../signals/signalify.js'
import type {PropKey, PropSpec} from './types.js'
import type {SignalFunction} from '../signals/createSignalFunction.js'

Expand Down Expand Up @@ -77,12 +77,9 @@ export function signal(
let initialValue = propSpec.initialValue

// @prod-prune
if (!Object.hasOwn(this, prop))
// continue
throw new PropNotFoundError(prop)
if (!Object.hasOwn(this, prop)) throw new PropNotFoundError(prop)

__createSignalAccessor(this as any, prop, initialValue)
// CONTINUE testing this way of finalizing signal fields
}
return
}
Expand All @@ -95,7 +92,7 @@ export function signal(
const signalStorage = new WeakMap<object, SignalFunction<unknown>>()
let initialValue: unknown = undefined

return {
const newValue = {
init: function (this: object, initialVal: unknown) {
initialValue = initialVal
return initialVal
Expand All @@ -112,6 +109,10 @@ export function signal(
s(typeof newValue === 'function' ? () => newValue : newValue)
},
}

isSignalGetter.add(newValue.get)

return newValue
} else if (kind === 'getter' || kind === 'setter') {
const getOrSet = value as Function
const initialValue = Undefined
Expand All @@ -134,10 +135,14 @@ export function signal(
pairs[name] ??= 0
pairs[name]++

return function (this: object): unknown {
const newGetter = function (this: object): unknown {
__getSignal(this, signalStorage, initialValue)()
return getOrSet.call(this)
}

isSignalGetter.add(newGetter)

return newGetter
} else {
pairs[name] ??= 0
pairs[name]++
Expand Down
2 changes: 1 addition & 1 deletion src/signals/signalify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('classy-solid', () => {

// A deeper effect will be reading the property.
createEffect(() => {
console.log(obj.n)
obj.n
})
})

Expand Down
2 changes: 1 addition & 1 deletion src/signals/signalify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export function __trackPropSetAtLeastOnce(instance: object, prop: string | symbo
propsSetAtLeastOnce.get(instance)!.add(prop)
}

const isSignalGetter = new WeakSet<Function>()
export const isSignalGetter = new WeakSet<Function>()

export function __createSignalAccessor<T extends object>(
obj: T,
Expand Down

0 comments on commit b9f9c55

Please sign in to comment.