refactor(core): Create `NodeInjectorOffset` type which better describes NodeInjector (#38707)

`NodeInjector` is store in expando as a list of values in an array. The
offset constant into the array have been brought together into a single
`NodeInjectorOffset` enum with better documentation explaining their usage.

PR Close #38707
This commit is contained in:
Misko Hevery 2020-09-25 14:47:33 -07:00 committed by Alex Rickabaugh
parent fc3b3fe39e
commit 494a2f3be4
8 changed files with 68 additions and 28 deletions

View File

@ -10,7 +10,7 @@ import {assertDefined, assertEqual, assertNumber, throwError} from '../util/asse
import {getComponentDef, getNgModuleDef} from './definition';
import {LContainer} from './interfaces/container';
import {DirectiveDef} from './interfaces/definition';
import {PARENT_INJECTOR} from './interfaces/injector';
import {NodeInjectorOffset} from './interfaces/injector';
import {TNode} from './interfaces/node';
import {isLContainer, isLView} from './interfaces/type_checks';
import {HEADER_OFFSET, LView, TVIEW, TView} from './interfaces/view';
@ -136,7 +136,7 @@ export function assertBetween(lower: number, upper: number, index: number) {
*/
export function assertNodeInjector(lView: LView, injectorIndex: number) {
assertIndexInExpandoRange(lView, injectorIndex);
assertIndexInExpandoRange(lView, injectorIndex + PARENT_INJECTOR);
assertIndexInExpandoRange(lView, injectorIndex + NodeInjectorOffset.PARENT);
assertNumber(lView[injectorIndex + 0], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 1], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 2], 'injectorIndex should point to a bloom filter');
@ -146,6 +146,6 @@ export function assertNodeInjector(lView: LView, injectorIndex: number) {
assertNumber(lView[injectorIndex + 6], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 7], 'injectorIndex should point to a bloom filter');
assertNumber(
lView[injectorIndex + 8 /*PARENT_INJECTOR*/],
lView[injectorIndex + NodeInjectorOffset.PARENT],
'injectorIndex should point to parent injector');
}

View File

@ -207,7 +207,7 @@ function traverseNextElement(tNode: TNode): TNode|null {
if (tNode.child && tNode.child.parent === tNode) {
// FIXME(misko): checking if `tNode.child.parent === tNode` should not be necessary
// We have added it here because i18n creates TNode's which are not valid, so this is a work
// around. The i18n code is being refactored in #??? and once it lands this extra check can be
// around. The i18n code will be refactored in #39003 and once it lands this extra check can be
// deleted.
return tNode.child;
} else if (tNode.next) {

View File

@ -21,7 +21,7 @@ import {getFactoryDef} from './definition';
import {NG_ELEMENT_ID, NG_FACTORY_DEF} from './fields';
import {registerPreOrderHooks} from './hooks';
import {DirectiveDef, FactoryFn} from './interfaces/definition';
import {isFactory, NO_PARENT_INJECTOR, NodeInjectorFactory, PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags, TNODE} from './interfaces/injector';
import {isFactory, NO_PARENT_INJECTOR, NodeInjectorFactory, NodeInjectorOffset, RelativeInjectorLocation, RelativeInjectorLocationFlags} from './interfaces/injector';
import {AttributeMarker, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNode, TNodeProviderIndexes, TNodeType} from './interfaces/node';
import {isComponentDef, isComponentHost} from './interfaces/type_checks';
import {DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, INJECTOR, LView, T_HOST, TData, TVIEW, TView, TViewType} from './interfaces/view';
@ -170,12 +170,12 @@ export function getOrCreateNodeInjectorForNode(
const parentData = parentLView[TVIEW].data as any;
// Creates a cumulative bloom filter that merges the parent's bloom filter
// and its own cumulative bloom (which contains tokens for all ancestors)
for (let i = 0; i < 8; i++) {
for (let i = 0; i < NodeInjectorOffset.BLOOM_SIZE; i++) {
lView[injectorIndex + i] = parentLView[parentIndex + i] | parentData[parentIndex + i];
}
}
lView[injectorIndex + PARENT_INJECTOR] = parentLoc;
lView[injectorIndex + NodeInjectorOffset.PARENT] = parentLoc;
return injectorIndex;
}
@ -191,7 +191,7 @@ export function getInjectorIndex(tNode: TNode, lView: LView): number {
(tNode.parent && tNode.parent.injectorIndex === tNode.injectorIndex) ||
// After the first template pass, the injector index might exist but the parent values
// might not have been calculated yet for this instance
lView[tNode.injectorIndex + PARENT_INJECTOR] == null) {
lView[tNode.injectorIndex + NodeInjectorOffset.PARENT] === null) {
return -1;
} else {
ngDevMode && assertIndexInRange(lView, tNode.injectorIndex);
@ -402,7 +402,7 @@ export function getOrCreateInjectable<T>(
// searching the parent injector.
if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) {
parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) :
lView[injectorIndex + PARENT_INJECTOR];
lView[injectorIndex + NodeInjectorOffset.PARENT];
if (parentLocation === NO_PARENT_INJECTOR || !shouldSearchParent(flags, false)) {
injectorIndex = -1;
@ -420,7 +420,9 @@ export function getOrCreateInjectable<T>(
// Check the current injector. If it matches, see if it contains token.
const tView = lView[TVIEW];
ngDevMode && assertTNodeForLView(tView.data[injectorIndex + TNODE] as TNode, lView);
ngDevMode &&
assertTNodeForLView(
tView.data[injectorIndex + NodeInjectorOffset.TNODE] as TNode, lView);
if (bloomHasToken(bloomHash, injectorIndex, tView.data)) {
// At this point, we have an injector which *may* contain the token, so we step through
// the providers and directives associated with the injector's corresponding node to get
@ -431,10 +433,11 @@ export function getOrCreateInjectable<T>(
return instance;
}
}
parentLocation = lView[injectorIndex + PARENT_INJECTOR];
parentLocation = lView[injectorIndex + NodeInjectorOffset.PARENT];
if (parentLocation !== NO_PARENT_INJECTOR &&
shouldSearchParent(
flags, lView[TVIEW].data[injectorIndex + TNODE] === hostTElementNode) &&
flags,
lView[TVIEW].data[injectorIndex + NodeInjectorOffset.TNODE] === hostTElementNode) &&
bloomHasToken(bloomHash, injectorIndex, lView)) {
// The def wasn't found anywhere on this node, so it was a false positive.
// Traverse up the tree and continue searching.
@ -485,7 +488,7 @@ function searchTokensOnInjector<T>(
injectorIndex: number, lView: LView, token: Type<T>|InjectionToken<T>,
previousTView: TView|null, flags: InjectFlags, hostTElementNode: TNode|null) {
const currentTView = lView[TVIEW];
const tNode = currentTView.data[injectorIndex + TNODE] as TNode;
const tNode = currentTView.data[injectorIndex + NodeInjectorOffset.TNODE] as TNode;
// First, we need to determine if view providers can be accessed by the starting element.
// There are two possibilities
const canAccessViewProviders = previousTView == null ?

View File

@ -16,7 +16,7 @@ import {assertNodeInjector} from '../assert';
import {getInjectorIndex} from '../di';
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
import {NO_PARENT_INJECTOR, PARENT_INJECTOR, TNODE} from '../interfaces/injector';
import {NO_PARENT_INJECTOR, NodeInjectorOffset} from '../interfaces/injector';
import {AttributeMarker, PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TNodeTypeAsString} from '../interfaces/node';
import {SelectorFlags} from '../interfaces/projection';
import {LQueries, TQueries} from '../interfaces/query';
@ -218,9 +218,9 @@ class TNode implements ITNode {
let injectorIndex = getInjectorIndex(this, lView);
ngDevMode && assertNodeInjector(lView, injectorIndex);
while (injectorIndex !== -1) {
const tNode = lView[TVIEW].data[injectorIndex + TNODE] as TNode;
const tNode = lView[TVIEW].data[injectorIndex + NodeInjectorOffset.TNODE] as TNode;
path.push(buildDebugNode(tNode, lView));
const parentLocation = lView[injectorIndex + PARENT_INJECTOR];
const parentLocation = lView[injectorIndex + NodeInjectorOffset.PARENT];
if (parentLocation === NO_PARENT_INJECTOR) {
injectorIndex = -1;
} else {

View File

@ -25,7 +25,7 @@ import {throwMultipleComponentError} from '../errors';
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} from '../hooks';
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS} from '../interfaces/container';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
import {NodeInjectorFactory, NodeInjectorOffset} from '../interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode} from '../interfaces/node';
import {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization';
@ -88,7 +88,7 @@ export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lVie
// Injector block and providers are taken into account.
const providerCount = (expandoInstructions[++i] as number);
bindingRootIndex += INJECTOR_BLOOM_PARENT_SIZE + providerCount;
bindingRootIndex += NodeInjectorOffset.SIZE + providerCount;
currentDirectiveIndex = bindingRootIndex;
} else {

View File

@ -15,13 +15,49 @@ import {TDirectiveHostNode} from './node';
import {LView, TData} from './view';
/**
* Offset from 'injectorIndex' where:
* - `TViewData[injectorIndex + TNODE]` => `TNode` associated with the current injector.
* - `LView[injectorIndex + TNODE]` => index to the parent injector.
* Offsets of the `NodeInjector` data structure in the expando.
*
* `NodeInjector` is stored in both `LView` as well as `TView.data`. All storage requires 9 words.
* First 8 are reserved for bloom filter and the 9th is reserved for the associated `TNode` as well
* as parent `NodeInjector` pointer. All indexes are starting with `index` and have an offset as
* shown.
*
* `LView` layout:
* ```
* index + 0: cumulative bloom filter
* index + 1: cumulative bloom filter
* index + 2: cumulative bloom filter
* index + 3: cumulative bloom filter
* index + 4: cumulative bloom filter
* index + 5: cumulative bloom filter
* index + 6: cumulative bloom filter
* index + 7: cumulative bloom filter
* index + 8: cumulative bloom filter
* index + PARENT: Index to the parent injector. See `RelativeInjectorLocation`
* `const parent = lView[index + NodeInjectorOffset.PARENT]`
* ```
*
* `TViewData` layout:
* ```
* index + 0: cumulative bloom filter
* index + 1: cumulative bloom filter
* index + 2: cumulative bloom filter
* index + 3: cumulative bloom filter
* index + 4: cumulative bloom filter
* index + 5: cumulative bloom filter
* index + 6: cumulative bloom filter
* index + 7: cumulative bloom filter
* index + 8: cumulative bloom filter
* index + TNODE: TNode associated with this `NodeInjector`
* `canst tNode = tView.data[index + NodeInjectorOffset.TNODE]`
* ```
*/
export const TNODE = 8;
export const PARENT_INJECTOR = 8;
export const INJECTOR_BLOOM_PARENT_SIZE = 9;
export const enum NodeInjectorOffset {
TNODE = 8,
PARENT = 8,
BLOOM_SIZE = 8,
SIZE = 9,
}
/**
* Represents a relative location of parent injector.

View File

@ -22,7 +22,7 @@ import {assertLContainer, assertNodeInjector} from './assert';
import {getParentInjectorLocation, NodeInjector} from './di';
import {addToViewTree, createLContainer, createLView, renderView} from './instructions/shared';
import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE, VIEW_REFS} from './interfaces/container';
import {TNODE} from './interfaces/injector';
import {NodeInjectorOffset} from './interfaces/injector';
import {TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNode, TNodeType} from './interfaces/node';
import {isProceduralRenderer, RComment, RElement} from './interfaces/renderer';
import {isComponentHost, isLContainer, isLView, isRootView} from './interfaces/type_checks';
@ -190,7 +190,8 @@ export function createContainerRef(
const parentView = getParentInjectorView(parentLocation, this._hostView);
const injectorIndex = getParentInjectorIndex(parentLocation);
ngDevMode && assertNodeInjector(parentView, injectorIndex);
const parentTNode = parentView[TVIEW].data[injectorIndex + TNODE] as TElementNode;
const parentTNode =
parentView[TVIEW].data[injectorIndex + NodeInjectorOffset.TNODE] as TElementNode;
return new NodeInjector(parentTNode, parentView);
} else {
return new NodeInjector(null, this._hostView);

View File

@ -9,11 +9,11 @@
import {InjectFlags, Optional, Renderer2, Self} from '@angular/core';
import {createLView, createTView, getOrCreateTNode} from '@angular/core/src/render3/instructions/shared';
import {RenderFlags} from '@angular/core/src/render3/interfaces/definition';
import {NodeInjectorOffset} from '@angular/core/src/render3/interfaces/injector';
import {ɵɵdefineComponent} from '../../src/render3/definition';
import {bloomAdd, bloomHashBitOrFactory as bloomHash, bloomHasToken, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
import {ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵtext} from '../../src/render3/index';
import {TNODE} from '../../src/render3/interfaces/injector';
import {TNodeType} from '../../src/render3/interfaces/node';
import {isProceduralRenderer} from '../../src/render3/interfaces/renderer';
import {LViewFlags, TVIEW, TViewType} from '../../src/render3/interfaces/view';
@ -150,7 +150,7 @@ describe('di', () => {
});
function bloomState() {
return mockTView.data.slice(0, TNODE).reverse();
return mockTView.data.slice(0, NodeInjectorOffset.TNODE).reverse();
}
class Dir0 {