Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds type annotations for ember-glimmer macro implementations #15862

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions packages/ember-glimmer/lib/syntax.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { InlineMacros, BlockMacros, OpcodeBuilderDSL, Block } from '@glimmer/runtime';
import * as WireFormat from '@glimmer/wire-format';
import { assert } from 'ember-debug';
import { textAreaMacro } from './syntax/-text-area';
import {
blockComponentMacro,
inlineComponentMacro,
} from './syntax/dynamic-component';
import Environment from './environment';
import { inputMacro } from './syntax/input';
import { mountMacro } from './syntax/mount';
import { outletMacro } from './syntax/outlet';
import { renderMacro } from './syntax/render';
import { hashToArgs } from './syntax/utils';
import { wrapComponentClassAttribute } from './utils/bindings';
import { Option } from '@glimmer/util';

function refineInlineSyntax(name: string, params: any[], hash: any, builder: any) {
assert(`You attempted to overwrite the built-in helper "${name}" which is not allowed. Please rename the helper.`, !(builder.env.builtInHelpers[name] && builder.env.owner.hasRegistration(`helper:${name}`)));
function refineInlineSyntax(name: string, params: WireFormat.Core.Params, hash: WireFormat.Core.Hash, builder: OpcodeBuilderDSL) {
assert(`You attempted to overwrite the built-in helper "${name}" which is not allowed. Please rename the helper.`, !((builder.env as Environment).builtInHelpers[name] && (builder.env as Environment).owner.hasRegistration(`helper:${name}`)));

let definition;
if (name.indexOf('-') > -1) {
Expand All @@ -28,7 +32,7 @@ function refineInlineSyntax(name: string, params: any[], hash: any, builder: any
return false;
}

function refineBlockSyntax(name: string, params: any[], hash: any, _default: any, inverse: any, builder: any) {
function refineBlockSyntax(name: string, params: WireFormat.Core.Params, hash: WireFormat.Core.Hash, _default: Option<Block>, inverse: Option<Block>, builder: OpcodeBuilderDSL) {
if (name.indexOf('-') === -1) {
return false;
}
Expand All @@ -53,16 +57,17 @@ function refineBlockSyntax(name: string, params: any[], hash: any, _default: any
return false;
}

export const experimentalMacros: any[] = [];
export type ExperimentalMacro = (blocks: BlockMacros, inlines: InlineMacros) => void;
export const experimentalMacros: ExperimentalMacro[] = [];

// This is a private API to allow for experimental macros
// to be created in user space. Registering a macro should
// should be done in an initializer.
export function registerMacros(macro: any) {
export function registerMacros(macro: ExperimentalMacro) {
experimentalMacros.push(macro);
}

export function populateMacros(blocks: any, inlines: any) {
export function populateMacros(blocks: BlockMacros, inlines: InlineMacros) {
inlines.add('outlet', outletMacro);
inlines.add('component', inlineComponentMacro);
inlines.add('render', renderMacro);
Expand Down
4 changes: 3 additions & 1 deletion packages/ember-glimmer/lib/syntax/-text-area.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { OpcodeBuilderDSL } from '@glimmer/runtime';
import * as WireFormat from '@glimmer/wire-format';
import { wrapComponentClassAttribute } from '../utils/bindings';
import { hashToArgs } from './utils';

export function textAreaMacro(_name: string, params: any[], hash: any, builder: any) {
export function textAreaMacro(_name: string, params: WireFormat.Core.Params, hash: WireFormat.Core.Hash, builder: OpcodeBuilderDSL) {
let definition = builder.env.getComponentDefinition('-text-area', builder.meta.templateMeta);
wrapComponentClassAttribute(hash);
builder.component.static(definition, [params, hashToArgs(hash), null, null]);
Expand Down
11 changes: 7 additions & 4 deletions packages/ember-glimmer/lib/syntax/input.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
/**
@module ember
*/
import * as WireFormat from '@glimmer/wire-format';
import { assert } from 'ember-debug';
import { wrapComponentClassAttribute } from '../utils/bindings';
import { dynamicComponentMacro } from './dynamic-component';
import { hashToArgs } from './utils';
import { OpcodeBuilderDSL } from '@glimmer/runtime';
import { unwrap } from '@glimmer/util';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is stripped in glimmer-vm, but I don’t believe that is true here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had that question too, @chadhietala probably knows that stuff the most intimately.


function buildSyntax(type: string, params: any[], hash: any, builder: any) {
function buildSyntax(type: string, params: WireFormat.Core.Params, hash: WireFormat.Core.Hash, builder: OpcodeBuilderDSL) {
let definition = builder.env.getComponentDefinition(type, builder.meta.templateMeta);
builder.component.static(definition, [params, hashToArgs(hash), null, null]);
return true;
Expand Down Expand Up @@ -148,7 +151,7 @@ function buildSyntax(type: string, params: any[], hash: any, builder: any) {
@public
*/

export function inputMacro(_name: string, params: any[], hash: any[], builder: any) {
export function inputMacro(_name: string, params: WireFormat.Core.Params, hash: WireFormat.Core.Hash, builder: OpcodeBuilderDSL): boolean {
let keys;
let values;
let typeIndex = -1;
Expand All @@ -164,7 +167,7 @@ export function inputMacro(_name: string, params: any[], hash: any[], builder: a
if (!params) { params = []; }

if (typeIndex > -1) {
let typeArg = values[typeIndex];
let typeArg = unwrap(values)[typeIndex];
if (Array.isArray(typeArg)) {
return dynamicComponentMacro(params, hash, null, null, builder);
} else if (typeArg === 'checkbox') {
Expand All @@ -173,7 +176,7 @@ export function inputMacro(_name: string, params: any[], hash: any[], builder: a
'you must use `checked=someBooleanValue` instead.',
valueIndex === -1,
);
wrapComponentClassAttribute(hash);
wrapComponentClassAttribute(unwrap(hash));
return buildSyntax('-checkbox', params, hash, builder);
}
}
Expand Down
43 changes: 25 additions & 18 deletions packages/ember-glimmer/lib/syntax/mount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@
*/
import {
Arguments,
VM
VM,
OpcodeBuilderDSL,
ComponentArgs
} from '@glimmer/runtime';
import * as WireFormat from '@glimmer/wire-format';
import { assert } from 'ember-debug';
import { EMBER_ENGINES_MOUNT_PARAMS } from 'ember/features';
import Environment from '../environment';
import { MountDefinition } from '../component-managers/mount';
import { hashToArgs } from './utils';
import { Option } from '@glimmer/util';
import { Tag, PathReference } from '@glimmer/reference';

function dynamicEngineFor(vm: VM, args: Arguments, meta: any) {
let env = vm.env;
let nameRef = args.positional.at(0);
function dynamicEngineFor(vm: VM, args: Arguments) {
let env = vm.env as Environment;
let nameRef = args.positional.at(0) as PathReference<string>;

return new DynamicEngineReference({ nameRef, env, meta });
return new DynamicEngineReference({ nameRef, env });
}

/**
Expand Down Expand Up @@ -59,7 +64,7 @@ function dynamicEngineFor(vm: VM, args: Arguments, meta: any) {
@category ember-application-engines
@public
*/
export function mountMacro(_name: string, params: any[], hash: any, builder: any) {
export function mountMacro(_name: string, params: WireFormat.Core.Params, hash: WireFormat.Core.Hash, builder: OpcodeBuilderDSL) {
if (EMBER_ENGINES_MOUNT_PARAMS) {
assert(
'You can only pass a single positional argument to the {{mount}} helper, e.g. {{mount "chat-engine"}}.',
Expand All @@ -72,30 +77,32 @@ export function mountMacro(_name: string, params: any[], hash: any, builder: any
);
}

let definitionArgs = [params.slice(0, 1), null, null, null];
let args = [null, hashToArgs(hash), null, null];
let definitionArgs: ComponentArgs = [params.slice(0, 1), null, null, null];
let args: ComponentArgs = [[], hashToArgs(hash), null, null];
builder.component.dynamic(definitionArgs, dynamicEngineFor, args);
return true;
}

class DynamicEngineReference {
public tag: any;
public nameRef: any;
public tag: Tag;
public nameRef: PathReference<string>;
public env: Environment;
public meta: any;
private _lastName: any;
private _lastDef: any;
constructor({ nameRef, env, meta }: any) {
private _lastName: Option<string>;
private _lastDef: Option<MountDefinition>;
constructor({ nameRef, env }: { nameRef: PathReference<string>, env: Environment }) {
this.tag = nameRef.tag;
this.nameRef = nameRef;
this.env = env;
this.meta = meta;
this._lastName = undefined;
this._lastDef = undefined;
this._lastName = null;
this._lastDef = null;
}

get(): never {
throw new Error('unexpected get on DynamicEngineReference');
}

value() {
let { env, nameRef /*meta*/ } = this;
let { env, nameRef } = this;
let nameOrDef = nameRef.value();

if (typeof nameOrDef === 'string') {
Expand Down
55 changes: 36 additions & 19 deletions packages/ember-glimmer/lib/syntax/outlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,65 @@ import {
ConstReference,
TagWrapper,
UpdatableTag,
PathReference,
Tag,
} from '@glimmer/reference';
import {
Arguments,
VM,
OpcodeBuilderDSL,
ComponentArgs,
} from '@glimmer/runtime';
import * as WireFormat from '@glimmer/wire-format';
import { Option, unwrap } from '@glimmer/util';

import { OutletComponentDefinition } from '../component-managers/outlet';
import { DynamicScope } from '../renderer';
import { OutletState } from '../views/outlet';

class OutletComponentReference {
public outletNameRef: any;
public parentOutletStateRef: any;
public definition: any;
public lastState: any;
public outletNameRef: PathReference<string>;
public parentOutletStateRef: PathReference<Option<OutletState>>;
public definition: Option<OutletComponentDefinition>;
public lastState: Option<OutletState>;
public outletStateTag: TagWrapper<UpdatableTag>;
public tag: any;
public tag: Tag;

constructor(outletNameRef: any, parentOutletStateRef: any) {
constructor(outletNameRef: PathReference<string>, parentOutletStateRef: PathReference<Option<OutletState>>) {
this.outletNameRef = outletNameRef;
this.parentOutletStateRef = parentOutletStateRef;
this.definition = null;
this.lastState = null;
let outletStateTag = this.outletStateTag = UpdatableTag.create(parentOutletStateRef.tag);
this.tag = combine([outletStateTag.inner, outletNameRef.tag]);
this.tag = combine([outletStateTag, outletNameRef.tag]);
}

get(): never {
throw new Error('unexpected get on OutletComponentReference');
}

value() {
let { outletNameRef, parentOutletStateRef, definition, lastState } = this;

let outletName = outletNameRef.value();
let outletStateRef = parentOutletStateRef.get('outlets').get(outletName);
let outletStateRef = parentOutletStateRef.get('outlets').get(outletName) as PathReference<Option<OutletState>>;
let newState = this.lastState = outletStateRef.value();

this.outletStateTag.inner.update(outletStateRef.tag);

definition = revalidate(definition, lastState, newState);

let hasTemplate = newState && newState.render.template;

if (definition) {
return definition;
} else if (hasTemplate) {
} else if (newState && newState.render && newState.render.template) {
return this.definition = new OutletComponentDefinition(outletName, newState.render.template);
} else {
return this.definition = null;
}
}
}

function revalidate(definition: any, lastState: any, newState: any) {
function revalidate(definition: Option<OutletComponentDefinition>, lastState: Option<OutletState>, newState: Option<OutletState>): Option<OutletComponentDefinition> {
if (!lastState && !newState) {
return definition;
}
Expand All @@ -60,16 +70,22 @@ function revalidate(definition: any, lastState: any, newState: any) {
return null;
}

if (
newState.render.template === lastState.render.template &&
newState.render.controller === lastState.render.controller
) {
if (outletStatesEqual(unwrap(lastState), unwrap(newState))) {
return definition;
}

return null;
}

function outletStatesEqual(lastState: OutletState, newState: OutletState): boolean {
if (!lastState.render || !newState.render) { return false; }

return (
newState.render.template === lastState.render.template &&
newState.render.controller === lastState.render.controller
);
}

function outletComponentFor(vm: VM, args: Arguments) {
let { outletState } = vm.dynamicScope() as DynamicScope;

Expand Down Expand Up @@ -133,12 +149,13 @@ function outletComponentFor(vm: VM, args: Arguments) {
@for Ember.Templates.helpers
@public
*/
export function outletMacro(_name: string, params: any[], _hash: any[], builder: any) {
export function outletMacro(_name: string, params: Option<WireFormat.Core.Params>, _hash: Option<WireFormat.Core.Hash>, builder: OpcodeBuilderDSL) {
if (!params) {
params = [];
}
let definitionArgs = [params.slice(0, 1), null, null, null];
let emptyArgs = [[], null, null, null]; // FIXME

let definitionArgs: ComponentArgs = [params.slice(0,1), null, null, null];
let emptyArgs: ComponentArgs = [[], null, null, null]; // FIXME
builder.component.dynamic(definitionArgs, outletComponentFor, emptyArgs);
return true;
}
19 changes: 14 additions & 5 deletions packages/ember-glimmer/lib/syntax/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { ConstReference, isConst } from '@glimmer/reference';
import {
Arguments,
VM,
OpcodeBuilderDSL,
ComponentArgs,
} from '@glimmer/runtime';
import * as WireFormat from '@glimmer/wire-format';
import { Option } from '@glimmer/util';
import { assert } from 'ember-debug';
import {
NON_SINGLETON_RENDER_MANAGER,
Expand All @@ -17,7 +21,7 @@ import Environment from '../environment';
import { OwnedTemplate } from '../template';
import { hashToArgs } from './utils';

function makeComponentDefinition(vm: VM, args: Arguments) {
function makeComponentDefinition(vm: VM, args: Arguments): ConstReference<RenderDefinition> {
let env = vm.env as Environment;
let nameRef = args.positional.at(0);

Expand Down Expand Up @@ -128,12 +132,17 @@ function makeComponentDefinition(vm: VM, args: Arguments) {
@public
@deprecated Use a component instead
*/
export function renderMacro(_name: string, params: any[], hash: any[], builder: any) {
export function renderMacro(_name: string, params: Option<WireFormat.Core.Params>, hash: WireFormat.Core.Hash, builder: OpcodeBuilderDSL): boolean {
if (!params) {
params = [];
}
let definitionArgs = [params.slice(0), hash, null, null];
let args = [params.slice(1), hashToArgs(hash), null, null];
builder.component.dynamic(definitionArgs, makeComponentDefinition, args);
let definitionArgs: ComponentArgs = [params.slice(0), hash, null, null];
let args: ComponentArgs = [params.slice(1), hashToArgs(hash), null, null];

// We have to coerce makeComponentDefinition into an `any` here because
// Glimmer VM is typing this too restrictively as a PathReference when it
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you cross link to the issue in glimmer-vm that you will be opening to track removal of the coercion?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API no longer exists in Glimmer VM, so I don't think I will be opening that issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomdale - So a future update of glimmer-vm will remove this comment (and what it is doing)? Can we link to anything that will help us understand this when we come back to this code in 6 months? I fear without any pointers to additional things to dig into, this will become yet another murder mystery for our future selves to solve. 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rwjblue It is already removed in @smfoote's upgrade branch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thank you (sorry for the run around).

// should just be a Reference (paths are never looked up on
// ComponentDefinitions.
builder.component.dynamic(definitionArgs, makeComponentDefinition as any, args);
return true;
}
4 changes: 3 additions & 1 deletion packages/ember-glimmer/lib/syntax/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export function hashToArgs(hash: any[]) {
import * as WireFormat from '@glimmer/wire-format';

export function hashToArgs(hash: WireFormat.Core.Hash): WireFormat.Core.Hash {
if (hash === null) { return null; }
let names = hash[0].map((key: string) => `@${key}`);
return [names, hash[1]];
Expand Down
Loading