From 72d0216b9b5fe4fefb66dea0481d16766a676d43 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Tue, 28 Jul 2020 22:03:37 -0700 Subject: [PATCH] [REFACTOR] Simplify get and computeds This refactor simplifies get and computeds in the following ways: - Removes several brand checks that were unnecessary from `get`: - `isEmberArray`: Ember Arrays that are not native arrays will automatically be tracked through proper usage. - `isProxy`: This was only necessary for conditionals, and updates in the VM allow us to autotrack that instead. - Uses `tagFor` instead of `tagForProperty` in many places including `get`. This allows us to avoid the brand check for `CUSTOM_PROPERTY_TAG`, which is really only necessary for chain tags. - Updates everywhere that looks up tags multiple times on the same object to use a shared `tagMeta` so we don't lookup that map multiple times. - Moves computed revision and value caches onto Meta, so we're looking up fewer maps. - Creates a new AutoComputed descriptor for computed properties that use autotracking, so we can simplify the get logic for standard CPs. --- .../-internals/glimmer/lib/environment.ts | 3 +- .../glimmer/lib/helpers/-track-array.ts | 5 +- .../-internals/glimmer/lib/utils/iterator.ts | 10 +- .../-internals/glimmer/lib/utils/to-bool.ts | 7 +- packages/@ember/-internals/meta/lib/meta.ts | 54 ++++- packages/@ember/-internals/metal/index.ts | 6 +- packages/@ember/-internals/metal/lib/alias.ts | 27 +-- .../-internals/metal/lib/array_events.ts | 8 +- .../@ember/-internals/metal/lib/chain-tags.ts | 106 +++++---- .../@ember/-internals/metal/lib/computed.ts | 212 +++++++++++------- .../-internals/metal/lib/computed_cache.ts | 108 +++++---- .../@ember/-internals/metal/lib/observer.ts | 14 +- .../-internals/metal/lib/property_get.ts | 63 +++--- packages/@ember/-internals/metal/lib/tags.ts | 22 +- .../@ember/-internals/metal/lib/tracked.ts | 9 +- .../metal/tests/tracked/validation_test.js | 47 ---- .../-internals/runtime/lib/mixins/-proxy.js | 21 +- .../runtime/lib/mixins/observable.js | 8 +- .../runtime/lib/system/array_proxy.js | 14 +- packages/@ember/object/compat.ts | 5 +- .../lib/computed/reduce_computed_macros.js | 11 +- 21 files changed, 410 insertions(+), 350 deletions(-) diff --git a/packages/@ember/-internals/glimmer/lib/environment.ts b/packages/@ember/-internals/glimmer/lib/environment.ts index 57f2eeb3b8b..d5de72884e3 100644 --- a/packages/@ember/-internals/glimmer/lib/environment.ts +++ b/packages/@ember/-internals/glimmer/lib/environment.ts @@ -1,5 +1,5 @@ import { ENV } from '@ember/-internals/environment'; -import { get, set } from '@ember/-internals/metal'; +import { _getProp, get, set } from '@ember/-internals/metal'; import { Owner } from '@ember/-internals/owner'; import { getDebugName } from '@ember/-internals/utils'; import { constructStyleDeprecationMessage } from '@ember/-internals/views'; @@ -117,6 +117,7 @@ export class EmberEnvironmentDelegate implements EnvironmentDelegate { @@ -131,10 +131,10 @@ class ObjectIterator extends BoundedIterator { // Add the tag of the returned value if it is an array, since arrays // should always cause updates if they are consumed and then changed if (isTracking()) { - consumeTag(tagForProperty(obj, key)); + consumeTag(tagFor(obj, key)); - if (Array.isArray(value) || isEmberArray(value)) { - consumeTag(tagForProperty(value, '[]')); + if (Array.isArray(value)) { + consumeTag(tagFor(value, '[]')); } } diff --git a/packages/@ember/-internals/glimmer/lib/utils/to-bool.ts b/packages/@ember/-internals/glimmer/lib/utils/to-bool.ts index 42c29a424c5..455e3bfeb58 100644 --- a/packages/@ember/-internals/glimmer/lib/utils/to-bool.ts +++ b/packages/@ember/-internals/glimmer/lib/utils/to-bool.ts @@ -1,11 +1,16 @@ -import { get } from '@ember/-internals/metal'; +import { get, tagForProperty } from '@ember/-internals/metal'; import { isArray } from '@ember/-internals/runtime'; import { isProxy } from '@ember/-internals/utils'; +import { consumeTag } from '@glimmer/validator'; export default function toBool(predicate: unknown): boolean { if (isProxy(predicate)) { + consumeTag(tagForProperty(predicate as object, 'content')); + return Boolean(get(predicate, 'isTruthy')); } else if (isArray(predicate)) { + consumeTag(tagForProperty(predicate as object, '[]')); + return (predicate as { length: number }).length !== 0; } else { return Boolean(predicate); diff --git a/packages/@ember/-internals/meta/lib/meta.ts b/packages/@ember/-internals/meta/lib/meta.ts index a63dafea1ce..4d1634cf69a 100644 --- a/packages/@ember/-internals/meta/lib/meta.ts +++ b/packages/@ember/-internals/meta/lib/meta.ts @@ -2,7 +2,7 @@ import { symbol, toString } from '@ember/-internals/utils'; import { assert, deprecate } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import { isDestroyed, isDestroying } from '@glimmer/runtime'; -import { UpdatableTag } from '@glimmer/validator'; +import { Revision, UpdatableTag } from '@glimmer/validator'; type ObjMap = { [key: string]: T }; @@ -89,7 +89,10 @@ export class Meta { _descriptors: Map | undefined; _mixins: any | undefined; _isInit: boolean; - _lazyChains: ObjMap> | undefined; + _lazyChains: ObjMap<[UpdatableTag, unknown][]> | undefined; + _values: ObjMap | undefined; + _tags: ObjMap | undefined; + _revisions: ObjMap | undefined; source: object; proto: object | undefined; _parent: Meta | undefined | null; @@ -100,16 +103,17 @@ export class Meta { _flattenedVersion = 0; // DEBUG - _values: any | undefined; - constructor(obj: object) { if (DEBUG) { counters!.metaInstantiated++; - this._values = undefined; } this._parent = undefined; this._descriptors = undefined; this._mixins = undefined; + this._lazyChains = undefined; + this._values = undefined; + this._tags = undefined; + this._revisions = undefined; // initial value for all flags right now is false // see FLAGS const for detailed list of flags used @@ -231,21 +235,47 @@ export class Meta { return false; } - writableLazyChainsFor(key: string) { + valueFor(key: string): unknown { + let values = this._values; + + return values !== undefined ? values[key] : undefined; + } + + setValueFor(key: string, value: unknown) { + let values = this._getOrCreateOwnMap('_values'); + + values[key] = value; + } + + revisionFor(key: string): Revision | undefined { + let revisions = this._revisions; + + return revisions !== undefined ? revisions[key] : undefined; + } + + setRevisionFor(key: string, revision: Revision | undefined) { + let revisions = this._getOrCreateOwnMap('_revisions'); + + revisions[key] = revision; + } + + writableLazyChainsFor(key: string): [UpdatableTag, unknown][] { if (DEBUG) { counters!.writableLazyChainsCalls++; } let lazyChains = this._getOrCreateOwnMap('_lazyChains'); - if (!(key in lazyChains)) { - lazyChains[key] = Object.create(null); + let chains = lazyChains[key]; + + if (chains === undefined) { + chains = lazyChains[key] = []; } - return lazyChains[key]; + return chains; } - readableLazyChainsFor(key: string) { + readableLazyChainsFor(key: string): [UpdatableTag, unknown][] | undefined { if (DEBUG) { counters!.readableLazyChainsCalls++; } @@ -687,10 +717,10 @@ export const meta: { counters!.metaCalls++; } - let maybeMeta = peekMeta(obj); + let maybeMeta = metaStore.get(obj); // remove this code, in-favor of explicit parent - if (maybeMeta !== null && maybeMeta.source === obj) { + if (maybeMeta !== undefined) { return maybeMeta; } diff --git a/packages/@ember/-internals/metal/index.ts b/packages/@ember/-internals/metal/index.ts index 2db4dd0c322..53aef4e4483 100644 --- a/packages/@ember/-internals/metal/index.ts +++ b/packages/@ember/-internals/metal/index.ts @@ -1,13 +1,13 @@ export { default as computed, + autoComputed, isComputed, _globalsComputed, ComputedProperty, } from './lib/computed'; -export { getCacheFor, getCachedValueFor, peekCacheFor } from './lib/computed_cache'; export { default as alias } from './lib/alias'; export { deprecateProperty } from './lib/deprecate_property'; -export { PROXY_CONTENT, _getPath, get, getWithDefault } from './lib/property_get'; +export { PROXY_CONTENT, _getPath, get, getWithDefault, _getProp } from './lib/property_get'; export { set, trySet } from './lib/property_set'; export { objectAt, @@ -44,7 +44,7 @@ export { isClassicDecorator, setClassicDecorator, } from './lib/descriptor_map'; -export { getChainTagsForKey } from './lib/chain-tags'; +export { getChainsTag } from './lib/chain-tags'; export { default as libraries, Libraries } from './lib/libraries'; export { default as getProperties } from './lib/get_properties'; export { default as setProperties } from './lib/set_properties'; diff --git a/packages/@ember/-internals/metal/lib/alias.ts b/packages/@ember/-internals/metal/lib/alias.ts index 61cf847721d..0b4e07c0a4f 100644 --- a/packages/@ember/-internals/metal/lib/alias.ts +++ b/packages/@ember/-internals/metal/lib/alias.ts @@ -1,18 +1,18 @@ -import { Meta } from '@ember/-internals/meta'; +import { Meta, meta as metaFor } from '@ember/-internals/meta'; import { inspect } from '@ember/-internals/utils'; import { assert } from '@ember/debug'; import EmberError from '@ember/error'; import { - combine, consumeTag, + tagFor, + tagMetaFor, untrack, UpdatableTag, updateTag, validateTag, valueForTag, } from '@glimmer/validator'; -import { finishLazyChains, getChainTagsForKey } from './chain-tags'; -import { getLastRevisionFor, setLastRevisionFor } from './computed_cache'; +import { finishLazyChains, getChainsTag } from './chain-tags'; import { ComputedDescriptor, Decorator, @@ -23,7 +23,6 @@ import { descriptorForDecorator } from './descriptor_map'; import { defineProperty } from './properties'; import { get } from './property_get'; import { set } from './property_set'; -import { tagForProperty } from './tags'; export type AliasDecorator = Decorator & PropertyDecorator & AliasDecoratorImpl; @@ -73,14 +72,12 @@ export class AliasedProperty extends ComputedDescriptor { super.setup(obj, keyName, propertyDesc, meta); } - teardown(obj: object, keyName: string, meta: Meta): void { - super.teardown(obj, keyName, meta); - } - get(obj: object, keyName: string): any { let ret: any; - let propertyTag = tagForProperty(obj, keyName) as UpdatableTag; + let meta = metaFor(obj); + let tagMeta = tagMetaFor(obj); + let propertyTag = tagFor(obj, keyName, tagMeta) as UpdatableTag; // We don't use the tag since CPs are not automatic, we just want to avoid // anything tracking while we get the altKey @@ -88,12 +85,12 @@ export class AliasedProperty extends ComputedDescriptor { ret = get(obj, this.altKey); }); - let lastRevision = getLastRevisionFor(obj, keyName); + let lastRevision = meta.revisionFor(keyName); - if (!validateTag(propertyTag, lastRevision)) { - updateTag(propertyTag, combine(getChainTagsForKey(obj, this.altKey, true))); - setLastRevisionFor(obj, keyName, valueForTag(propertyTag)); - finishLazyChains(obj, keyName, ret); + if (lastRevision === undefined || !validateTag(propertyTag, lastRevision)) { + updateTag(propertyTag, getChainsTag(obj, this.altKey, tagMeta, meta)); + meta.setRevisionFor(keyName, valueForTag(propertyTag)); + finishLazyChains(meta, keyName, ret); } consumeTag(propertyTag); diff --git a/packages/@ember/-internals/metal/lib/array_events.ts b/packages/@ember/-internals/metal/lib/array_events.ts index f898b76c9bf..ee7c73285c5 100644 --- a/packages/@ember/-internals/metal/lib/array_events.ts +++ b/packages/@ember/-internals/metal/lib/array_events.ts @@ -1,5 +1,4 @@ import { peekMeta } from '@ember/-internals/meta'; -import { peekCacheFor } from './computed_cache'; import { sendEvent } from './events'; import { notifyPropertyChange } from './property_events'; @@ -61,8 +60,7 @@ export function arrayContentDidChange( sendEvent(array, '@array:change', [array, startIdx, removeAmt, addAmt]); - let cache = peekCacheFor(array); - if (cache !== undefined) { + if (meta !== null) { let length = array.length; let addedAmount = addAmt === -1 ? 0 : addAmt; let removedAmount = removeAmt === -1 ? 0 : removeAmt; @@ -70,11 +68,11 @@ export function arrayContentDidChange( let previousLength = length - delta; let normalStartIdx = startIdx < 0 ? previousLength + startIdx : startIdx; - if (cache.has('firstObject') && normalStartIdx === 0) { + if (meta.revisionFor('firstObject') !== undefined && normalStartIdx === 0) { notifyPropertyChange(array, 'firstObject', meta); } - if (cache.has('lastObject')) { + if (meta.revisionFor('lastObject') !== undefined) { let previousLastIndex = previousLength - 1; let lastAffectedIndex = normalStartIdx + removedAmount; if (previousLastIndex < lastAffectedIndex) { diff --git a/packages/@ember/-internals/metal/lib/chain-tags.ts b/packages/@ember/-internals/metal/lib/chain-tags.ts index de6b769a2e1..69bbe82bdc9 100644 --- a/packages/@ember/-internals/metal/lib/chain-tags.ts +++ b/packages/@ember/-internals/metal/lib/chain-tags.ts @@ -1,4 +1,5 @@ -import { meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { isObject } from '@ember/-internals/utils'; import { assert, deprecate } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import { @@ -6,52 +7,62 @@ import { combine, createUpdatableTag, Tag, + TagMeta, + tagMetaFor, updateTag, validateTag, } from '@glimmer/validator'; import { objectAt } from './array'; -import { getLastRevisionFor, peekCacheFor } from './computed_cache'; -import { descriptorForProperty } from './descriptor_map'; import { tagForProperty } from './tags'; -export function finishLazyChains(obj: any, key: string, value: any) { - let meta = peekMeta(obj); - let lazyTags = meta !== null ? meta.readableLazyChainsFor(key) : undefined; +export function finishLazyChains(meta: Meta, key: string, value: any) { + let lazyTags = meta.readableLazyChainsFor(key); if (lazyTags === undefined) { return; } - if (value === null || (typeof value !== 'object' && typeof value !== 'function')) { - for (let path in lazyTags) { - delete lazyTags[path]; + if (isObject(value)) { + for (let i = 0; i < lazyTags.length; i++) { + let [tag, deps] = lazyTags[i]; + updateTag(tag, getChainsTag(value, deps as string)); } - return; } - for (let path in lazyTags) { - let tag = lazyTags[path]; - - updateTag(tag, combine(getChainTagsForKey(value, path))); - - delete lazyTags[path]; - } + lazyTags.length = 0; } -export function getChainTagsForKeys(obj: any, keys: string[], addMandatorySetter = false) { - let chainTags: Tag[] = []; - - for (let i = 0; i < keys.length; i++) { - chainTags.push(...getChainTagsForKey(obj, keys[i], addMandatorySetter)); +export function getChainsTag( + obj: object, + keys: string | string[], + _tagMeta?: TagMeta, + _meta?: Meta | null +) { + let tags: Tag[] = []; + let tagMeta = _tagMeta || tagMetaFor(obj); + let meta = _meta === undefined ? peekMeta(obj) : _meta; + + if (typeof keys === 'string') { + getChainTagsForKey(tags, obj, keys, tagMeta, meta); + } else { + for (let i = 0; i < keys.length; i++) { + getChainTagsForKey(tags, obj, keys[i], tagMeta, meta); + } } - return chainTags; + return combine(tags); } -export function getChainTagsForKey(obj: any, path: string, addMandatorySetter = false) { - let chainTags: Tag[] = []; - +function getChainTagsForKey( + chainTags: Tag[], + obj: object, + path: string, + tagMeta: TagMeta, + meta: Meta | null +) { let current: any = obj; + let currentTagMeta = tagMeta; + let currentMeta = meta; let pathLength = path.length; let segmentEnd = -1; @@ -60,13 +71,6 @@ export function getChainTagsForKey(obj: any, path: string, addMandatorySetter = // eslint-disable-next-line no-constant-condition while (true) { - let currentType = typeof current; - - if (current === null || (currentType !== 'object' && currentType !== 'function')) { - // we've hit the end of the chain for now, break out - break; - } - let lastSegmentEnd = segmentEnd + 1; segmentEnd = path.indexOf('.', lastSegmentEnd); @@ -137,20 +141,18 @@ export function getChainTagsForKey(obj: any, path: string, addMandatorySetter = typeof item === 'object' ); - chainTags.push(tagForProperty(item, segment, addMandatorySetter)); + chainTags.push(tagForProperty(item, segment, true, currentTagMeta)); } } // Push the tag for the array length itself - chainTags.push(tagForProperty(current, '[]', addMandatorySetter)); + chainTags.push(tagForProperty(current, '[]', true, currentTagMeta)); break; } - // TODO: Assert that current[segment] isn't an undecorated, non-MANDATORY_SETTER/dependentKeyCompat getter - - let propertyTag = tagForProperty(current, segment, addMandatorySetter); - descriptor = descriptorForProperty(current, segment); + let propertyTag = tagForProperty(current, segment, true, currentTagMeta); + descriptor = currentMeta !== null ? currentMeta.peekDescriptors(segment) : undefined; chainTags.push(propertyTag); @@ -191,26 +193,32 @@ export function getChainTagsForKey(obj: any, path: string, addMandatorySetter = // the CP is still valid, and if so we use the cached value. If not, then // we create a lazy chain lookup, and the next time the CP is calculated, // it will update that lazy chain. - let lastRevision = getLastRevisionFor(current, segment); + let instanceMeta = currentMeta!.source === current ? currentMeta! : metaFor(current); + let lastRevision = instanceMeta.revisionFor(segment); - if (validateTag(propertyTag, lastRevision)) { - current = peekCacheFor(current).get(segment); + if (lastRevision !== undefined && validateTag(propertyTag, lastRevision)) { + current = instanceMeta.valueFor(segment); } else { - let lazyChains = metaFor(current).writableLazyChainsFor(segment); - + // use metaFor here to ensure we have the meta for the instance + let lazyChains = instanceMeta.writableLazyChainsFor(segment); let rest = path.substr(segmentEnd + 1); - let placeholderTag = lazyChains[rest]; - - if (placeholderTag === undefined) { - placeholderTag = lazyChains[rest] = createUpdatableTag(); - } + let placeholderTag = createUpdatableTag(); + lazyChains.push([placeholderTag, rest]); chainTags.push(placeholderTag); break; } } + + if (!isObject(current)) { + // we've hit the end of the chain for now, break out + break; + } + + currentTagMeta = tagMetaFor(current); + currentMeta = peekMeta(current); } if (DEBUG) { diff --git a/packages/@ember/-internals/metal/lib/computed.ts b/packages/@ember/-internals/metal/lib/computed.ts index 205e84fd0e0..e4274034ced 100644 --- a/packages/@ember/-internals/metal/lib/computed.ts +++ b/packages/@ember/-internals/metal/lib/computed.ts @@ -1,13 +1,14 @@ import { Meta, meta as metaFor } from '@ember/-internals/meta'; import { addObserver, PROPERTY_DID_CHANGE } from '@ember/-internals/metal'; -import { inspect, isEmberArray, toString } from '@ember/-internals/utils'; +import { inspect, symbol, toString } from '@ember/-internals/utils'; import { assert, deprecate, warn } from '@ember/debug'; import EmberError from '@ember/error'; +import { DEBUG } from '@glimmer/env'; import { isDestroyed } from '@glimmer/runtime'; import { - combine, consumeTag, - Tag, + tagFor, + tagMetaFor, track, untrack, UpdatableTag, @@ -15,14 +16,7 @@ import { validateTag, valueForTag, } from '@glimmer/validator'; -import { finishLazyChains, getChainTagsForKeys } from './chain-tags'; -import { - getCachedValueFor, - getCacheFor, - getLastRevisionFor, - peekCacheFor, - setLastRevisionFor, -} from './computed_cache'; +import { finishLazyChains, getChainsTag } from './chain-tags'; import { ComputedDescriptor, Decorator, @@ -35,12 +29,12 @@ import { descriptorForProperty, isClassicDecorator, } from './descriptor_map'; -import expandProperties from './expand_properties'; import { setObserverSuspended } from './observer'; import { defineProperty } from './properties'; import { beginPropertyChanges, endPropertyChanges, notifyPropertyChange } from './property_events'; import { set } from './property_set'; -import { tagForProperty } from './tags'; + +export const AUTO = symbol('COMPUTED_AUTO'); export type ComputedPropertyGetter = (keyName: string) => any; export type ComputedPropertySetter = (keyName: string, value: any, cachedValue?: any) => any; @@ -255,13 +249,12 @@ function noop(): void {} @public */ export class ComputedProperty extends ComputedDescriptor { - private _volatile = false; - private _readOnly = false; - private _hasConfig = false; + protected _volatile = false; + protected _readOnly = false; + protected _hasConfig = false; _getter?: ComputedPropertyGetter = undefined; _setter?: ComputedPropertySetter = undefined; - _auto?: boolean; constructor(args: Array) { super(); @@ -531,25 +524,20 @@ export class ComputedProperty extends ComputedDescriptor { this._property(...passedArgs); } - _property(...passedArgs: string[]): void { - let args: string[] = []; - - function addArg(property: string): void { - warn( - `Dependent keys containing @each only work one level deep. ` + - `You used the key "${property}" which is invalid. ` + - `Please create an intermediary computed property.`, - DEEP_EACH_REGEX.test(property) === false, - { id: 'ember-metal.computed-deep-each' } - ); - args.push(property); - } - - for (let i = 0; i < passedArgs.length; i++) { - expandProperties(passedArgs[i], addArg); + _property(...deps: string[]): void { + if (DEBUG) { + for (let dep of deps) { + warn( + `Dependent keys containing @each only work one level deep. ` + + `You used the key "${dep}" which is invalid. ` + + `Please create an intermediary computed property.`, + DEEP_EACH_REGEX.test(dep) === false, + { id: 'ember-metal.computed-deep-each' } + ); + } } - this._dependentKeys = args; + this._dependentKeys = deps; } /** @@ -604,13 +592,17 @@ export class ComputedProperty extends ComputedDescriptor { return this._getter!.call(obj, keyName); } - let cache = getCacheFor(obj); - let propertyTag = tagForProperty(obj, keyName) as UpdatableTag; + let meta = metaFor(obj); + let tagMeta = tagMetaFor(obj); + + let propertyTag = tagFor(obj, keyName, tagMeta) as UpdatableTag; let ret; - if (cache.has(keyName) && validateTag(propertyTag, getLastRevisionFor(obj, keyName))) { - ret = cache.get(keyName); + let revision = meta.revisionFor(keyName); + + if (revision !== undefined && validateTag(propertyTag, revision)) { + ret = meta.valueFor(keyName); } else { // For backwards compatibility, we only throw if the CP has any dependencies. CPs without dependencies // should be allowed, even after the object has been destroyed, which is why we check _dependentKeys. @@ -619,42 +611,29 @@ export class ComputedProperty extends ComputedDescriptor { this._dependentKeys === undefined || !isDestroyed(obj) ); - let upstreamTag: Tag | undefined = undefined; - - if (this._auto === true) { - upstreamTag = track(() => { - ret = this._getter!.call(obj, keyName); - }); - } else { - // Create a tracker that absorbs any trackable actions inside the CP - untrack(() => { - ret = this._getter!.call(obj, keyName); - }); - } + let { _getter, _dependentKeys } = this; - if (this._dependentKeys !== undefined) { - let tag = combine(getChainTagsForKeys(obj, this._dependentKeys, true)); - - upstreamTag = upstreamTag === undefined ? tag : combine([upstreamTag, tag]); - } + // Create a tracker that absorbs any trackable actions inside the CP + untrack(() => { + ret = _getter!.call(obj, keyName); + }); - if (upstreamTag !== undefined) { - updateTag(propertyTag!, upstreamTag); + if (_dependentKeys !== undefined) { + updateTag(propertyTag!, getChainsTag(obj, _dependentKeys, tagMeta, meta)); } - setLastRevisionFor(obj, keyName, valueForTag(propertyTag)); + meta.setValueFor(keyName, ret); + meta.setRevisionFor(keyName, valueForTag(propertyTag)); - cache.set(keyName, ret); - - finishLazyChains(obj, keyName, ret); + finishLazyChains(meta, keyName, ret); } consumeTag(propertyTag!); // Add the tag of the returned value if it is an array, since arrays // should always cause updates if they are consumed and then changed - if (Array.isArray(ret) || isEmberArray(ret)) { - consumeTag(tagForProperty(ret, '[]')); + if (Array.isArray(ret)) { + consumeTag(tagFor(ret, '[]', tagMeta)); } return ret; @@ -673,19 +652,21 @@ export class ComputedProperty extends ComputedDescriptor { return this.volatileSet(obj, keyName, value); } + let meta = metaFor(obj); + // ensure two way binding works when the component has defined a computed // property with both a setter and dependent keys, in that scenario without // the sync observer added below the caller's value will never be updated // // See GH#18147 / GH#19028 for details. if ( + // ensure that we only run this once, while the component is being instantiated + meta.isInitializing() && this._dependentKeys !== undefined && this._dependentKeys.length > 0 && // These two properties are set on Ember.Component typeof obj[PROPERTY_DID_CHANGE] === 'function' && - (obj as any).isComponent && - // ensure that we only run this once, while the component is being instantiated - metaFor(obj).isInitializing() + (obj as any).isComponent ) { addObserver( obj, @@ -703,17 +684,20 @@ export class ComputedProperty extends ComputedDescriptor { try { beginPropertyChanges(); - ret = this._set(obj, keyName, value); + ret = this._set(obj, keyName, value, meta); + + finishLazyChains(meta, keyName, ret); - finishLazyChains(obj, keyName, ret); + let tagMeta = tagMetaFor(obj); + let propertyTag = tagFor(obj, keyName, tagMeta) as UpdatableTag; - let propertyTag = tagForProperty(obj, keyName) as UpdatableTag; + let { _dependentKeys } = this; - if (this._dependentKeys !== undefined) { - updateTag(propertyTag, combine(getChainTagsForKeys(obj, this._dependentKeys, true))); + if (_dependentKeys !== undefined) { + updateTag(propertyTag, getChainsTag(obj, _dependentKeys, tagMeta, meta)); } - setLastRevisionFor(obj, keyName, valueForTag(propertyTag)); + meta.setRevisionFor(keyName, valueForTag(propertyTag)); } finally { endPropertyChanges(); } @@ -738,7 +722,7 @@ export class ComputedProperty extends ComputedDescriptor { } ); - let cachedValue = getCachedValueFor(obj, keyName); + let cachedValue = metaFor(obj).valueFor(keyName); defineProperty(obj, keyName, null, cachedValue); set(obj, keyName, value); return value; @@ -748,17 +732,17 @@ export class ComputedProperty extends ComputedDescriptor { return this._setter!.call(obj, keyName, value); } - _set(obj: object, keyName: string, value: unknown): any { - let cache = getCacheFor(obj); - let hadCachedValue = cache.has(keyName); - let cachedValue = cache.get(keyName); + _set(obj: object, keyName: string, value: unknown, meta: Meta): any { + let hadCachedValue = meta.revisionFor(keyName) !== undefined; + let cachedValue = meta.valueFor(keyName); let ret; + let { _setter } = this; setObserverSuspended(obj, keyName, true); try { - ret = this._setter!.call(obj, keyName, value, cachedValue); + ret = _setter!.call(obj, keyName, value, cachedValue); } finally { setObserverSuspended(obj, keyName, false); } @@ -768,9 +752,7 @@ export class ComputedProperty extends ComputedDescriptor { return ret; } - let meta = metaFor(obj); - - cache.set(keyName, ret); + meta.setValueFor(keyName, ret); notifyPropertyChange(obj, keyName, meta, value); @@ -778,18 +760,65 @@ export class ComputedProperty extends ComputedDescriptor { } /* called before property is overridden */ - teardown(obj: object, keyName: string, meta?: any): void { + teardown(obj: object, keyName: string, meta: Meta): void { if (!this._volatile) { - let cache = peekCacheFor(obj); - if (cache !== undefined) { - cache.delete(keyName); + if (meta.revisionFor(keyName) !== undefined) { + meta.setRevisionFor(keyName, undefined); + meta.setValueFor(keyName, undefined); } } + super.teardown(obj, keyName, meta); } +} + +class AutoComputedProperty extends ComputedProperty { + get(obj: object, keyName: string): any { + if (this._volatile) { + return this._getter!.call(obj, keyName); + } - auto() { - this._auto = true; + let meta = metaFor(obj); + let tagMeta = tagMetaFor(obj); + + let propertyTag = tagFor(obj, keyName, tagMeta) as UpdatableTag; + + let ret; + + let revision = meta.revisionFor(keyName); + + if (revision !== undefined && validateTag(propertyTag, revision)) { + ret = meta.valueFor(keyName); + } else { + assert( + `Attempted to access the computed ${obj}.${keyName} on a destroyed object, which is not allowed`, + !isDestroyed(obj) + ); + + let { _getter } = this; + + // Create a tracker that absorbs any trackable actions inside the CP + let tag = track(() => { + ret = _getter!.call(obj, keyName); + }); + + updateTag(propertyTag!, tag); + + meta.setValueFor(keyName, ret); + meta.setRevisionFor(keyName, valueForTag(propertyTag)); + + finishLazyChains(meta, keyName, ret); + } + + consumeTag(propertyTag!); + + // Add the tag of the returned value if it is an array, since arrays + // should always cause updates if they are consumed and then changed + if (Array.isArray(ret)) { + consumeTag(tagFor(ret, '[]', tagMeta)); + } + + return ret; } } @@ -1011,6 +1040,15 @@ export function computed( ) as ComputedDecorator; } +export function autoComputed( + ...config: [ComputedPropertyConfig] +): ComputedDecorator | DecoratorPropertyDescriptor { + return makeComputedDecorator( + new AutoComputedProperty(config), + ComputedDecoratorImpl + ) as ComputedDecorator; +} + /** Allows checking if a given property on an object is a computed property. For the most part, this doesn't matter (you would normally just access the property directly and use its value), diff --git a/packages/@ember/-internals/metal/lib/computed_cache.ts b/packages/@ember/-internals/metal/lib/computed_cache.ts index f038db4ce87..d15054eaa4d 100644 --- a/packages/@ember/-internals/metal/lib/computed_cache.ts +++ b/packages/@ember/-internals/metal/lib/computed_cache.ts @@ -1,59 +1,69 @@ -const COMPUTED_PROPERTY_CACHED_VALUES = new WeakMap>(); -const COMPUTED_PROPERTY_LAST_REVISION = new WeakMap>(); +import { Meta } from '@ember/-internals/meta'; -export function getCacheFor(obj: object): Map { - let cache = COMPUTED_PROPERTY_CACHED_VALUES.get(obj); - if (cache === undefined) { - cache = new Map(); +// const COMPUTED_PROPERTY_CACHED_VALUES = new WeakMap>(); +// const COMPUTED_PROPERTY_LAST_REVISION = new WeakMap>(); - COMPUTED_PROPERTY_CACHED_VALUES.set(obj, cache); - } - return cache; +export function valueFor(meta: Meta, key: string) { + return meta.valueFor(key); } -/** - Returns the cached value for a property, if one exists. - This can be useful for peeking at the value of a computed - property that is generated lazily, without accidentally causing - it to be created. - - @method cacheFor - @static - @for @ember/object/internals - @param {Object} obj the object whose property you want to check - @param {String} key the name of the property whose cached value you want - to return - @return {Object} the cached value - @public -*/ -export function getCachedValueFor(obj: object, key: string): any { - let cache = COMPUTED_PROPERTY_CACHED_VALUES.get(obj); - if (cache !== undefined) { - return cache.get(key); - } +export function setValueFor(obj: object, key: string, value: unknown, meta: Meta) { + meta.setValueFor(key, value); } -export function setLastRevisionFor(obj: object, key: string, revision: number): void { - let cache = COMPUTED_PROPERTY_LAST_REVISION!.get(obj); +// export function getCacheFor(obj: object, ): Map { +// let cache = COMPUTED_PROPERTY_CACHED_VALUES.get(obj); +// if (cache === undefined) { +// cache = new Map(); - if (cache === undefined) { - cache = new Map(); - COMPUTED_PROPERTY_LAST_REVISION!.set(obj, cache); - } +// COMPUTED_PROPERTY_CACHED_VALUES.set(obj, cache); +// } +// return cache; +// } - cache!.set(key, revision); -} +// /** +// Returns the cached value for a property, if one exists. +// This can be useful for peeking at the value of a computed +// property that is generated lazily, without accidentally causing +// it to be created. -export function getLastRevisionFor(obj: object, key: string): number { - let cache = COMPUTED_PROPERTY_LAST_REVISION!.get(obj); - if (cache === undefined) { - return 0; - } else { - let revision = cache.get(key); - return revision === undefined ? 0 : revision; - } -} +// @method cacheFor +// @static +// @for @ember/object/internals +// @param {Object} obj the object whose property you want to check +// @param {String} key the name of the property whose cached value you want +// to return +// @return {Object} the cached value +// @public +// */ +// export function getCachedValueFor(obj: object, key: string): any { +// let cache = COMPUTED_PROPERTY_CACHED_VALUES.get(obj); +// if (cache !== undefined) { +// return cache.get(key); +// } +// } -export function peekCacheFor(obj: object): any { - return COMPUTED_PROPERTY_CACHED_VALUES.get(obj); -} +// export function setLastRevisionFor(obj: object, key: string, revision: number): void { +// let cache = COMPUTED_PROPERTY_LAST_REVISION!.get(obj); + +// if (cache === undefined) { +// cache = new Map(); +// COMPUTED_PROPERTY_LAST_REVISION!.set(obj, cache); +// } + +// cache!.set(key, revision); +// } + +// export function getLastRevisionFor(obj: object, key: string): number { +// let cache = COMPUTED_PROPERTY_LAST_REVISION!.get(obj); +// if (cache === undefined) { +// return 0; +// } else { +// let revision = cache.get(key); +// return revision === undefined ? 0 : revision; +// } +// } + +// export function peekCacheFor(obj: object): any { +// return COMPUTED_PROPERTY_CACHED_VALUES.get(obj); +// } diff --git a/packages/@ember/-internals/metal/lib/observer.ts b/packages/@ember/-internals/metal/lib/observer.ts index 4d49087d507..8374cc1357b 100644 --- a/packages/@ember/-internals/metal/lib/observer.ts +++ b/packages/@ember/-internals/metal/lib/observer.ts @@ -2,8 +2,8 @@ import { ENV } from '@ember/-internals/environment'; import { peekMeta } from '@ember/-internals/meta'; import { schedule } from '@ember/runloop'; import { registerDestructor } from '@glimmer/runtime'; -import { combine, CURRENT_TAG, Tag, validateTag, valueForTag } from '@glimmer/validator'; -import { getChainTagsForKey } from './chain-tags'; +import { CURRENT_TAG, Tag, validateTag, valueForTag } from '@glimmer/validator'; +import { getChainsTag } from './chain-tags'; import changeEvent from './change_event'; import { addListener, removeListener, sendEvent } from './events'; @@ -97,7 +97,7 @@ export function activateObserver(target: object, eventName: string, sync = false activeObservers.get(eventName)!.count++; } else { let [path] = eventName.split(':'); - let tag = combine(getChainTagsForKey(target, path, true)); + let tag = getChainsTag(target, path); activeObservers.set(eventName, { count: 1, @@ -161,14 +161,14 @@ export function resumeObserverDeactivation() { export function revalidateObservers(target: object) { if (ASYNC_OBSERVERS.has(target)) { ASYNC_OBSERVERS.get(target)!.forEach(observer => { - observer.tag = combine(getChainTagsForKey(target, observer.path, true)); + observer.tag = getChainsTag(target, observer.path); observer.lastRevision = valueForTag(observer.tag); }); } if (SYNC_OBSERVERS.has(target)) { SYNC_OBSERVERS.get(target)!.forEach(observer => { - observer.tag = combine(getChainTagsForKey(target, observer.path, true)); + observer.tag = getChainsTag(target, observer.path); observer.lastRevision = valueForTag(observer.tag); }); } @@ -192,7 +192,7 @@ export function flushAsyncObservers(shouldSchedule = true) { try { sendEvent(target, eventName, [target, observer.path], undefined, meta); } finally { - observer.tag = combine(getChainTagsForKey(target, observer.path, true)); + observer.tag = getChainsTag(target, observer.path); observer.lastRevision = valueForTag(observer.tag); } }; @@ -221,7 +221,7 @@ export function flushSyncObservers() { observer.suspended = true; sendEvent(target, eventName, [target, observer.path], undefined, meta); } finally { - observer.tag = combine(getChainTagsForKey(target, observer.path, true)); + observer.tag = getChainsTag(target, observer.path); observer.lastRevision = valueForTag(observer.tag); observer.suspended = false; } diff --git a/packages/@ember/-internals/metal/lib/property_get.ts b/packages/@ember/-internals/metal/lib/property_get.ts index 4f891b6c3c2..08a7b090133 100644 --- a/packages/@ember/-internals/metal/lib/property_get.ts +++ b/packages/@ember/-internals/metal/lib/property_get.ts @@ -1,16 +1,17 @@ /** @module @ember/object */ -import { HAS_NATIVE_PROXY, isEmberArray, isProxy, symbol } from '@ember/-internals/utils'; +import { HAS_NATIVE_PROXY, setProxy, symbol } from '@ember/-internals/utils'; import { assert, deprecate } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import { consumeTag, deprecateMutationsInAutotrackingTransaction, isTracking, + tagFor, + track, } from '@glimmer/validator'; import { isPath } from './path_cache'; -import { tagForProperty } from './tags'; export const PROXY_CONTENT = symbol('PROXY_CONTENT'); @@ -92,17 +93,17 @@ export function get(obj: object, keyName: string): any { typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0 ); + return isPath(keyName) ? _getPath(obj, keyName) : _getProp(obj, keyName); +} + +export function _getProp(obj: object, keyName: string) { let type = typeof obj; let isObject = type === 'object'; let isFunction = type === 'function'; let isObjectLike = isObject || isFunction; - if (isPath(keyName)) { - return isObjectLike ? _getPath(obj, keyName) : undefined; - } - - let value: any; + let value: unknown; if (isObjectLike) { if (DEBUG && HAS_NATIVE_PROXY) { @@ -110,12 +111,9 @@ export function get(obj: object, keyName: string): any { } else { value = obj[keyName]; } - } else { - value = obj[keyName]; - } - if (value === undefined) { if ( + value === undefined && isObject && !(keyName in obj) && typeof (obj as MaybeHasUnknownProperty).unknownProperty === 'function' @@ -128,22 +126,18 @@ export function get(obj: object, keyName: string): any { value = (obj as MaybeHasUnknownProperty).unknownProperty!(keyName); } } - } - if (isObjectLike && isTracking()) { - consumeTag(tagForProperty(obj, keyName)); + if (isTracking()) { + consumeTag(tagFor(obj, keyName)); - // Add the tag of the returned value if it is an array, since arrays - // should always cause updates if they are consumed and then changed - if (Array.isArray(value) || isEmberArray(value)) { - consumeTag(tagForProperty(value, '[]')); - } - - // Add the value of the content if the value is a proxy. This is because - // content changes the truthiness/falsiness of the proxy. - if (isProxy(value)) { - consumeTag(tagForProperty(value, 'content')); + if (Array.isArray(value)) { + // Add the tag of the returned value if it is an array, since arrays + // should always cause updates if they are consumed and then changed + consumeTag(tagFor(value, '[]')); + } } + } else { + value = obj[keyName]; } return value; @@ -158,7 +152,7 @@ export function _getPath(root: T, path: string | string[]): an return undefined; } - obj = get(obj, parts[i]); + obj = _getProp(obj, parts[i]); } return obj; @@ -207,3 +201,22 @@ export function getWithDefault _getProp({}, 'a')); +track(() => _getProp({}, 1 as any)); +track(() => _getProp({ a: [] }, 'a')); +track(() => _getProp({ a: fakeProxy }, 'a')); diff --git a/packages/@ember/-internals/metal/lib/tags.ts b/packages/@ember/-internals/metal/lib/tags.ts index 5cd76e5746f..d19d39087c6 100644 --- a/packages/@ember/-internals/metal/lib/tags.ts +++ b/packages/@ember/-internals/metal/lib/tags.ts @@ -2,7 +2,7 @@ import { isObject, setupMandatorySetter, symbol, toString } from '@ember/-intern import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import { isDestroyed } from '@glimmer/runtime'; -import { CONSTANT_TAG, dirtyTagFor, Tag, tagFor } from '@glimmer/validator'; +import { CONSTANT_TAG, dirtyTagFor, Tag, tagFor, TagMeta } from '@glimmer/validator'; ///////// @@ -12,27 +12,19 @@ export const CUSTOM_TAG_FOR = symbol('CUSTOM_TAG_FOR'); export const SELF_TAG: string = symbol('SELF_TAG'); export function tagForProperty( - obj: unknown, + obj: object, propertyKey: string | symbol, - addMandatorySetter = false + addMandatorySetter = false, + meta?: TagMeta ): Tag { - if (!isObject(obj)) { - return CONSTANT_TAG; - } - if (typeof obj[CUSTOM_TAG_FOR] === 'function') { return obj[CUSTOM_TAG_FOR](propertyKey, addMandatorySetter); } - let tag = tagFor(obj, propertyKey); - - if (DEBUG) { - if (addMandatorySetter) { - setupMandatorySetter!(tag, obj, propertyKey); - } + let tag = tagFor(obj, propertyKey, meta); - // TODO: Replace this with something more first class for tracking tags in DEBUG - (tag as any)._propertyKey = propertyKey; + if (DEBUG && addMandatorySetter) { + setupMandatorySetter!(tag, obj, propertyKey); } return tag; diff --git a/packages/@ember/-internals/metal/lib/tracked.ts b/packages/@ember/-internals/metal/lib/tracked.ts index 8f415805486..ad90cb75969 100644 --- a/packages/@ember/-internals/metal/lib/tracked.ts +++ b/packages/@ember/-internals/metal/lib/tracked.ts @@ -1,10 +1,9 @@ -import { isEmberArray } from '@ember/-internals/utils'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; -import { consumeTag, dirtyTagFor, trackedData } from '@glimmer/validator'; +import { consumeTag, dirtyTagFor, tagFor, trackedData } from '@glimmer/validator'; import { Decorator, DecoratorPropertyDescriptor, isElementDescriptor } from './decorator'; import { setClassicDecorator } from './descriptor_map'; -import { SELF_TAG, tagForProperty } from './tags'; +import { SELF_TAG } from './tags'; /** @decorator @@ -159,8 +158,8 @@ function descriptorForField([_target, key, desc]: [ // Add the tag of the returned value if it is an array, since arrays // should always cause updates if they are consumed and then changed - if (Array.isArray(value) || isEmberArray(value)) { - consumeTag(tagForProperty(value, '[]')); + if (Array.isArray(value)) { + consumeTag(tagFor(value, '[]')); } return value; diff --git a/packages/@ember/-internals/metal/tests/tracked/validation_test.js b/packages/@ember/-internals/metal/tests/tracked/validation_test.js index 052f5c4ff14..075a4df4785 100644 --- a/packages/@ember/-internals/metal/tests/tracked/validation_test.js +++ b/packages/@ember/-internals/metal/tests/tracked/validation_test.js @@ -8,7 +8,6 @@ import { notifyPropertyChange, } from '../..'; -import { EMBER_ARRAY } from '@ember/-internals/utils'; import { AbstractTestCase, moduleFor } from 'internal-test-helpers'; import { track, valueForTag, validateTag } from '@glimmer/validator'; @@ -364,52 +363,6 @@ moduleFor( ); } - ['@test ember get interaction with ember arrays'](assert) { - class EmberObject { - emberArray = { - [EMBER_ARRAY]: true, - }; - } - - let obj = new EmberObject(); - let emberArray; - - let tag = track(() => (emberArray = get(obj, 'emberArray'))); - let snapshot = valueForTag(tag); - - assert.equal(validateTag(tag, snapshot), true); - - notifyPropertyChange(emberArray, '[]'); - assert.equal( - validateTag(tag, snapshot), - false, - 'invalid after setting a property on the object' - ); - } - - ['@test native get interaction with ember arrays'](assert) { - class EmberObject { - @tracked emberArray = { - [EMBER_ARRAY]: true, - }; - } - - let obj = new EmberObject(); - let emberArray; - - let tag = track(() => (emberArray = obj.emberArray)); - let snapshot = valueForTag(tag); - - assert.equal(validateTag(tag, snapshot), true); - - notifyPropertyChange(emberArray, '[]'); - assert.equal( - validateTag(tag, snapshot), - false, - 'invalid after setting a property on the object' - ); - } - ['@test gives helpful assertion when a tracked property is mutated after access in with an autotracking transaction']() { class EmberObject { @tracked value; diff --git a/packages/@ember/-internals/runtime/lib/mixins/-proxy.js b/packages/@ember/-internals/runtime/lib/mixins/-proxy.js index 25dca814d6e..3d92ca2652f 100644 --- a/packages/@ember/-internals/runtime/lib/mixins/-proxy.js +++ b/packages/@ember/-internals/runtime/lib/mixins/-proxy.js @@ -11,12 +11,12 @@ import { tagForObject, computed, CUSTOM_TAG_FOR, - getChainTagsForKey, + tagForProperty, } from '@ember/-internals/metal'; -import { setProxy, setupMandatorySetter } from '@ember/-internals/utils'; +import { setProxy, setupMandatorySetter, isObject } from '@ember/-internals/utils'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; -import { combine, updateTag, tagFor } from '@glimmer/validator'; +import { combine, updateTag, tagFor, tagMetaFor } from '@glimmer/validator'; export function contentFor(proxy) { let content = get(proxy, 'content'); @@ -59,7 +59,8 @@ export default Mixin.create({ }), [CUSTOM_TAG_FOR](key, addMandatorySetter) { - let tag = tagFor(this, key); + let meta = tagMetaFor(this); + let tag = tagFor(this, key, meta); if (DEBUG) { // TODO: Replace this with something more first class for tracking tags in DEBUG @@ -73,7 +74,17 @@ export default Mixin.create({ return tag; } else { - return combine([tag, ...getChainTagsForKey(this, `content.${key}`, addMandatorySetter)]); + let tags = [tag]; + + tags.push(tagFor(this, 'content', meta)); + + let content = contentFor(this); + + if (isObject(content)) { + tags.push(tagForProperty(content, key, addMandatorySetter)); + } + + return combine(tags); } }, diff --git a/packages/@ember/-internals/runtime/lib/mixins/observable.js b/packages/@ember/-internals/runtime/lib/mixins/observable.js index 840bda8f4ca..763a86cc0c9 100644 --- a/packages/@ember/-internals/runtime/lib/mixins/observable.js +++ b/packages/@ember/-internals/runtime/lib/mixins/observable.js @@ -2,6 +2,7 @@ @module @ember/object */ +import { peekMeta } from '@ember/-internals/meta'; import { get, getWithDefault, @@ -15,7 +16,6 @@ import { endPropertyChanges, addObserver, removeObserver, - getCachedValueFor, } from '@ember/-internals/metal'; import { assert } from '@ember/debug'; @@ -496,6 +496,10 @@ export default Mixin.create({ @public */ cacheFor(keyName) { - return getCachedValueFor(this, keyName); + let meta = peekMeta(this); + + if (meta !== null) { + return meta.valueFor(keyName); + } }, }); diff --git a/packages/@ember/-internals/runtime/lib/system/array_proxy.js b/packages/@ember/-internals/runtime/lib/system/array_proxy.js index 0d7034759a7..3d48365c52d 100644 --- a/packages/@ember/-internals/runtime/lib/system/array_proxy.js +++ b/packages/@ember/-internals/runtime/lib/system/array_proxy.js @@ -10,10 +10,11 @@ import { addArrayObserver, removeArrayObserver, replace, - getChainTagsForKey, CUSTOM_TAG_FOR, arrayContentDidChange, + tagForProperty, } from '@ember/-internals/metal'; +import { isObject } from '@ember/-internals/utils'; import EmberObject from './object'; import { isArray, MutableArray } from '../mixins/array'; import { assert } from '@ember/debug'; @@ -119,7 +120,14 @@ export default class ArrayProxy extends EmberObject { // revalidate eagerly if we're being tracked, since we no longer will // be able to later on due to backtracking re-render assertion this._revalidate(); - return combine(getChainTagsForKey(this, `arrangedContent.${key}`, addMandatorySetter)); + let tags = [this._arrangedContentTag]; + let arrangedContent = get(this, 'arrangedContent'); + + if (isObject(arrangedContent)) { + tags.push(tagForProperty(arrangedContent, key, addMandatorySetter)); + } + + return combine(tags); } return tagFor(this, key); @@ -318,7 +326,7 @@ export default class ArrayProxy extends EmberObject { this._arrangedContentIsUpdating = false; } - this._arrangedContentTag = combine(getChainTagsForKey(this, 'arrangedContent')); + this._arrangedContentTag = tagFor(this, 'arrangedContent'); this._arrangedContentRevision = valueForTag(this._arrangedContentTag); } } diff --git a/packages/@ember/object/compat.ts b/packages/@ember/object/compat.ts index ff987096979..0769033c58e 100644 --- a/packages/@ember/object/compat.ts +++ b/packages/@ember/object/compat.ts @@ -4,17 +4,16 @@ import { DecoratorPropertyDescriptor, isElementDescriptor, setClassicDecorator, - tagForProperty, } from '@ember/-internals/metal'; import { assert } from '@ember/debug'; -import { consumeTag, track, UpdatableTag, updateTag } from '@glimmer/validator'; +import { consumeTag, tagFor, track, UpdatableTag, updateTag } from '@glimmer/validator'; let wrapGetterSetter = function(_target: object, key: string, desc: PropertyDescriptor) { let { get: originalGet } = desc; if (originalGet !== undefined) { desc.get = function() { - let propertyTag = tagForProperty(this, key) as UpdatableTag; + let propertyTag = tagFor(this, key) as UpdatableTag; let ret; let tag = track(() => { diff --git a/packages/@ember/object/lib/computed/reduce_computed_macros.js b/packages/@ember/object/lib/computed/reduce_computed_macros.js index 2950fd49511..69fdab3f1ea 100644 --- a/packages/@ember/object/lib/computed/reduce_computed_macros.js +++ b/packages/@ember/object/lib/computed/reduce_computed_macros.js @@ -3,12 +3,7 @@ */ import { DEBUG } from '@glimmer/env'; import { assert } from '@ember/debug'; -import { - computed, - descriptorForDecorator, - get, - isElementDescriptor, -} from '@ember/-internals/metal'; +import { computed, autoComputed, get, isElementDescriptor } from '@ember/-internals/metal'; import { compare, isArray, A as emberA, uniqBy as uniqByArray } from '@ember/-internals/runtime'; function reduceMacro(dependentKey, callback, initialValue, name) { @@ -1479,7 +1474,7 @@ function customSort(itemsKey, additionalDependentKeys, comparator) { // This one needs to dynamically set up and tear down observers on the itemsKey // depending on the sortProperties function propertySort(itemsKey, sortPropertiesKey) { - let cp = computed(`${itemsKey}.[]`, `${sortPropertiesKey}.[]`, function(key) { + let cp = autoComputed(function(key) { let sortProperties = get(this, sortPropertiesKey); assert( @@ -1502,8 +1497,6 @@ function propertySort(itemsKey, sortPropertiesKey) { } }).readOnly(); - descriptorForDecorator(cp).auto(); - return cp; }