diff --git a/packages/core/__tests__/language-server/diagnostic-augmentation.test.ts b/packages/core/__tests__/language-server/diagnostic-augmentation.test.ts
index 83cffdfd1..279efcf16 100644
--- a/packages/core/__tests__/language-server/diagnostic-augmentation.test.ts
+++ b/packages/core/__tests__/language-server/diagnostic-augmentation.test.ts
@@ -792,7 +792,7 @@ describe('Language Server: Diagnostic Augmentation', () => {
},
{
"message": "The {{component}} helper can't be used to directly invoke a component under Glint. Consider first binding the result to a variable, e.g. '{{#let (component 'component-name') as |ComponentName|}}' and then invoking it as ''.
- Argument of type 'Invokable<(named?: PrebindArgs<{ message?: string | undefined; }, \\"message\\"> | undefined) => ComponentReturn, null>>' is not assignable to parameter of type 'ContentValue'.",
+ Argument of type 'Invokable<(named?: PrebindArgs<{ message?: string | undefined; }, \\"message\\"> | undefined) => ComponentReturn, unknown>>' is not assignable to parameter of type 'ContentValue'.",
"range": {
"end": {
"character": 41,
@@ -827,7 +827,7 @@ describe('Language Server: Diagnostic Augmentation', () => {
},
{
"message": "The {{component}} helper can't be used to directly invoke a component under Glint. Consider first binding the result to a variable, e.g. '{{#let (component 'component-name') as |ComponentName|}}' and then invoking it as '...'.
- Argument of type 'Invokable<(named?: PrebindArgs<{ message?: string | undefined; }, \\"message\\"> | undefined) => ComponentReturn, null>>' is not assignable to parameter of type 'ComponentReturn'.",
+ Argument of type 'Invokable<(named?: PrebindArgs<{ message?: string | undefined; }, \\"message\\"> | undefined) => ComponentReturn, unknown>>' is not assignable to parameter of type 'ComponentReturn'.",
"range": {
"end": {
"character": 56,
diff --git a/packages/core/__tests__/language-server/diagnostics.test.ts b/packages/core/__tests__/language-server/diagnostics.test.ts
index c50649001..d696e3eed 100644
--- a/packages/core/__tests__/language-server/diagnostics.test.ts
+++ b/packages/core/__tests__/language-server/diagnostics.test.ts
@@ -59,7 +59,7 @@ describe('Language Server: Diagnostics', () => {
expect(templateDiagnostics).toMatchInlineSnapshot(`
[
{
- "message": "Property 'missingArg' does not exist on type 'EmptyObject'.",
+ "message": "Property 'missingArg' does not exist on type '{}'.",
"range": {
"end": {
"character": 13,
@@ -118,7 +118,7 @@ describe('Language Server: Diagnostics', () => {
expect(diagnostics).toMatchObject([
{
- message: "Property 'foo' does not exist on type 'EmptyObject'.",
+ message: "Property 'foo' does not exist on type '{}'.",
source: 'glint:ts(2339)',
},
]);
@@ -159,7 +159,7 @@ describe('Language Server: Diagnostics', () => {
expect(diagnostics).toMatchObject([
{
- message: "Property 'foo' does not exist on type 'EmptyObject'.",
+ message: "Property 'foo' does not exist on type '{}'.",
source: 'glint:ts(2339)',
},
]);
@@ -358,7 +358,7 @@ describe('Language Server: Diagnostics', () => {
expect(server.getDiagnostics(project.fileURI('component-a.ts'))).toMatchInlineSnapshot(`
[
{
- "message": "Property 'version' does not exist on type 'EmptyObject'.",
+ "message": "Property 'version' does not exist on type '{}'.",
"range": {
"end": {
"character": 36,
diff --git a/packages/environment-ember-loose/-private/dsl/integration-declarations.d.ts b/packages/environment-ember-loose/-private/dsl/integration-declarations.d.ts
index 5a963849b..c199eeedd 100644
--- a/packages/environment-ember-loose/-private/dsl/integration-declarations.d.ts
+++ b/packages/environment-ember-loose/-private/dsl/integration-declarations.d.ts
@@ -3,7 +3,6 @@
import { ComponentLike, HelperLike, ModifierLike } from '@glint/template';
import {
Context,
- EmptyObject,
FlattenBlockParams,
HasContext,
TemplateContext,
@@ -85,7 +84,7 @@ declare module '@ember/routing/route' {
[Context]: TemplateContext<
Controller & ModelField>,
ModelField>,
- EmptyObject,
+ {},
null
>;
}
@@ -93,7 +92,7 @@ declare module '@ember/routing/route' {
declare module '@ember/controller' {
export default interface Controller {
- [Context]: TemplateContext, EmptyObject, null>;
+ [Context]: TemplateContext, {}, null>;
}
}
@@ -103,9 +102,7 @@ declare module '@ember/controller' {
import '@ember/test-helpers';
import 'ember-cli-htmlbars';
-type TestTemplate = abstract new () => HasContext<
- TemplateContext
->;
+type TestTemplate = abstract new () => HasContext>;
declare module '@ember/test-helpers' {
export function render(template: TestTemplate): Promise;
diff --git a/packages/environment-ember-loose/-private/intrinsics/component.d.ts b/packages/environment-ember-loose/-private/intrinsics/component.d.ts
index fecd468ec..05ab250d8 100644
--- a/packages/environment-ember-loose/-private/intrinsics/component.d.ts
+++ b/packages/environment-ember-loose/-private/intrinsics/component.d.ts
@@ -1,7 +1,6 @@
import { WithBoundArgs } from '@glint/template';
import {
ComponentReturn,
- EmptyObject,
DirectInvokable,
InvokableInstance,
Invokable,
@@ -14,8 +13,8 @@ import {
type ComponentNamedArgs = Component extends Invokable<(...args: infer Args) => any>
? Args extends [...positional: infer _, named?: infer Named]
? UnwrapNamedArgs
- : EmptyObject
- : EmptyObject;
+ : {}
+ : {};
type PartiallyAppliedComponent = Component extends Invokable
? WithBoundArgs<
diff --git a/packages/environment-ember-loose/__tests__/type-tests/ember-component.test.ts b/packages/environment-ember-loose/__tests__/type-tests/ember-component.test.ts
index 822de7a07..11a97b31e 100644
--- a/packages/environment-ember-loose/__tests__/type-tests/ember-component.test.ts
+++ b/packages/environment-ember-loose/__tests__/type-tests/ember-component.test.ts
@@ -7,7 +7,6 @@ import {
NamedArgsMarker,
} from '@glint/environment-ember-loose/-private/dsl';
import { expectTypeOf } from 'expect-type';
-import { EmptyObject } from '@glint/template/-private/integration';
import type { ComponentLike } from '@glint/template';
{
@@ -19,12 +18,6 @@ import type { ComponentLike } from '@glint/template';
}
}
- resolve(NoArgsComponent)({
- // @ts-expect-error: extra named arg
- foo: 'bar',
- ...NamedArgsMarker,
- });
-
resolve(NoArgsComponent)(
// @ts-expect-error: extra positional arg
'oops'
@@ -50,7 +43,7 @@ import type { ComponentLike } from '@glint/template';
templateForBackingValue(this, function* (𝚪) {
expectTypeOf(𝚪.this.foo).toEqualTypeOf();
expectTypeOf(𝚪.this).toEqualTypeOf();
- expectTypeOf(𝚪.args).toEqualTypeOf();
+ expectTypeOf(𝚪.args).toEqualTypeOf<{}>();
});
}
}
diff --git a/packages/environment-ember-loose/__tests__/type-tests/glimmer-component.test.ts b/packages/environment-ember-loose/__tests__/type-tests/glimmer-component.test.ts
index 21645ee9b..eb008e366 100644
--- a/packages/environment-ember-loose/__tests__/type-tests/glimmer-component.test.ts
+++ b/packages/environment-ember-loose/__tests__/type-tests/glimmer-component.test.ts
@@ -6,19 +6,12 @@ import {
emitComponent,
NamedArgsMarker,
} from '@glint/environment-ember-loose/-private/dsl';
-import { EmptyObject } from '@glint/template/-private/integration';
import { expectTypeOf } from 'expect-type';
import { ComponentLike } from '@glint/template';
{
class NoArgsComponent extends Component {}
- resolve(NoArgsComponent)({
- // @ts-expect-error: extra named arg
- foo: 'bar',
- ...NamedArgsMarker,
- });
-
resolve(NoArgsComponent)(
// @ts-expect-error: extra positional arg
'oops'
@@ -44,7 +37,7 @@ import { ComponentLike } from '@glint/template';
templateForBackingValue(this, function* (𝚪) {
expectTypeOf(𝚪.this.foo).toEqualTypeOf();
expectTypeOf(𝚪.this).toEqualTypeOf();
- expectTypeOf(𝚪.args).toEqualTypeOf();
+ expectTypeOf(𝚪.args).toEqualTypeOf<{}>();
});
}
}
diff --git a/packages/environment-ember-loose/__tests__/type-tests/helper.test.ts b/packages/environment-ember-loose/__tests__/type-tests/helper.test.ts
index c5335c2c4..84f3d1016 100644
--- a/packages/environment-ember-loose/__tests__/type-tests/helper.test.ts
+++ b/packages/environment-ember-loose/__tests__/type-tests/helper.test.ts
@@ -1,4 +1,4 @@
-import Helper, { helper, EmptyObject } from '@ember/component/helper';
+import Helper, { helper } from '@ember/component/helper';
import { resolve } from '@glint/environment-ember-loose/-private/dsl';
import {
NamedArgsMarker,
@@ -6,7 +6,7 @@ import {
} from '@glint/environment-ember-loose/-private/dsl/without-function-resolution';
import { expectTypeOf } from 'expect-type';
import { HelperLike } from '@glint/template';
-import { EmptyObject as GlintEmptyObject, NamedArgs } from '@glint/template/-private/integration';
+import { NamedArgs } from '@glint/template/-private/integration';
// Functional helper: fixed signature params
{
@@ -56,7 +56,9 @@ import { EmptyObject as GlintEmptyObject, NamedArgs } from '@glint/template/-pri
let definition = helper(([a, b]: [T, U]) => a || b);
let or = resolve(definition);
- expectTypeOf(or).toEqualTypeOf<{ (t: T, u: U, named?: NamedArgs): T | U }>();
+ // Using `toMatch` rather than `toEqual` because helper resolution (currently)
+ // uses a special `EmptyObject` type to represent empty named args.
+ expectTypeOf(or).toMatchTypeOf<{ (t: T, u: U, named?: NamedArgs<{}>): T | U }>();
or('a', 'b', {
// @ts-expect-error: extra named arg
@@ -158,12 +160,12 @@ import { EmptyObject as GlintEmptyObject, NamedArgs } from '@glint/template/-pri
let repeat = resolve(RepeatHelper);
expectTypeOf(repeat).toEqualTypeOf<{
- (value: T, count?: number | undefined, args?: NamedArgs): Array;
+ (value: T, count?: number | undefined): Array;
}>();
repeat(
'hello',
- // @ts-expect-error: extra named arg
+ // @ts-expect-error: unexpected named args
{ word: 'hi', ...NamedArgsMarker }
);
@@ -190,9 +192,7 @@ import { EmptyObject as GlintEmptyObject, NamedArgs } from '@glint/template/-pri
let maybeString = resolve(MaybeStringHelper);
- expectTypeOf(maybeString).toEqualTypeOf<
- (args?: NamedArgs) => string | undefined
- >();
+ expectTypeOf(maybeString).toEqualTypeOf<() => string | undefined>();
}
// Helpers are `HelperLike`
diff --git a/packages/environment-ember-loose/__tests__/type-tests/intrinsics/mut.test.ts b/packages/environment-ember-loose/__tests__/type-tests/intrinsics/mut.test.ts
index 9e86012ba..ae83527f6 100644
--- a/packages/environment-ember-loose/__tests__/type-tests/intrinsics/mut.test.ts
+++ b/packages/environment-ember-loose/__tests__/type-tests/intrinsics/mut.test.ts
@@ -14,8 +14,8 @@ expectTypeOf(fn(mut('hello'))).toEqualTypeOf<(value: string) => void>();
// @ts-expect-error: missing value
mut();
+// @ts-expect-error: unexpected named args
mut('hello', {
- // @ts-expect-error: invalid named arg
hello: 'hi',
...NamedArgsMarker,
});
diff --git a/packages/environment-ember-loose/__tests__/type-tests/intrinsics/outlet.test.ts b/packages/environment-ember-loose/__tests__/type-tests/intrinsics/outlet.test.ts
index df57b85ce..ee327b24a 100644
--- a/packages/environment-ember-loose/__tests__/type-tests/intrinsics/outlet.test.ts
+++ b/packages/environment-ember-loose/__tests__/type-tests/intrinsics/outlet.test.ts
@@ -9,8 +9,8 @@ expectTypeOf(outlet('outlet-name')).toEqualTypeOf();
// Nameless main outlet
outlet();
+// @ts-expect-error: unexpected named args
outlet('outlet-name', {
- // @ts-expect-error: invalid named arg
hello: 'hi',
...NamedArgsMarker,
});
diff --git a/packages/environment-ember-loose/__tests__/type-tests/intrinsics/unbound.test.ts b/packages/environment-ember-loose/__tests__/type-tests/intrinsics/unbound.test.ts
index 9adae144a..0f9bc4089 100644
--- a/packages/environment-ember-loose/__tests__/type-tests/intrinsics/unbound.test.ts
+++ b/packages/environment-ember-loose/__tests__/type-tests/intrinsics/unbound.test.ts
@@ -10,8 +10,8 @@ expectTypeOf(unbound(123)).toEqualTypeOf();
// @ts-expect-error: missing value
unbound();
+// @ts-expect-error: unexpected named args
unbound('hello', {
- // @ts-expect-error: invalid named arg
hello: 'hi',
...NamedArgsMarker,
});
diff --git a/packages/environment-ember-loose/__tests__/type-tests/intrinsics/unique-id.test.ts b/packages/environment-ember-loose/__tests__/type-tests/intrinsics/unique-id.test.ts
index 70c798b59..2204cde16 100644
--- a/packages/environment-ember-loose/__tests__/type-tests/intrinsics/unique-id.test.ts
+++ b/packages/environment-ember-loose/__tests__/type-tests/intrinsics/unique-id.test.ts
@@ -6,8 +6,8 @@ let uniqueId = resolve(Globals['unique-id']);
// Basic plumbing
expectTypeOf(uniqueId()).toEqualTypeOf();
+// @ts-expect-error: unexpected named args
uniqueId({
- // @ts-expect-error: invalid named arg
hello: 'hi',
...NamedArgsMarker,
});
diff --git a/packages/environment-ember-loose/__tests__/type-tests/route-and-controller.test.ts b/packages/environment-ember-loose/__tests__/type-tests/route-and-controller.test.ts
index ae1a331bc..35a969e5d 100644
--- a/packages/environment-ember-loose/__tests__/type-tests/route-and-controller.test.ts
+++ b/packages/environment-ember-loose/__tests__/type-tests/route-and-controller.test.ts
@@ -1,7 +1,6 @@
import Route from '@ember/routing/route';
import Controller from '@ember/controller';
import { expectTypeOf } from 'expect-type';
-import { EmptyObject } from '@glint/template/-private/integration';
import { templateForBackingValue } from '../../-private/dsl';
class TestRoute extends Route {
@@ -14,7 +13,7 @@ templateForBackingValue(TestRoute, function (routeContext) {
expectTypeOf(routeContext.args).toEqualTypeOf<{ model: { message: string } }>();
expectTypeOf(routeContext.element).toBeNull();
expectTypeOf(routeContext.this).toEqualTypeOf();
- expectTypeOf(routeContext.blocks).toEqualTypeOf();
+ expectTypeOf(routeContext.blocks).toEqualTypeOf<{}>();
});
class TestController extends Controller {
@@ -29,5 +28,5 @@ templateForBackingValue(TestController, function (controllerContext) {
expectTypeOf(controllerContext.args).toEqualTypeOf<{ model: { name: string; age: number } }>();
expectTypeOf(controllerContext.element).toBeNull();
expectTypeOf(controllerContext.this).toEqualTypeOf();
- expectTypeOf(controllerContext.blocks).toEqualTypeOf();
+ expectTypeOf(controllerContext.blocks).toEqualTypeOf<{}>();
});
diff --git a/packages/environment-ember-loose/__tests__/type-tests/template-only.test.ts b/packages/environment-ember-loose/__tests__/type-tests/template-only.test.ts
index 72952c74e..d5bf2bc9d 100644
--- a/packages/environment-ember-loose/__tests__/type-tests/template-only.test.ts
+++ b/packages/environment-ember-loose/__tests__/type-tests/template-only.test.ts
@@ -5,7 +5,7 @@ import {
emitComponent,
NamedArgsMarker,
} from '@glint/environment-ember-loose/-private/dsl';
-import { ComponentReturn, EmptyObject, NamedArgs } from '@glint/template/-private/integration';
+import { ComponentReturn, NamedArgs } from '@glint/template/-private/integration';
import { expectTypeOf } from 'expect-type';
import { ComponentKeyword } from '../../-private/intrinsics/component';
import { ComponentLike, WithBoundArgs } from '@glint/template';
@@ -13,14 +13,7 @@ import { ComponentLike, WithBoundArgs } from '@glint/template';
{
const NoArgsComponent = templateOnlyComponent();
- resolve(NoArgsComponent)({
- // @ts-expect-error: extra named arg
- foo: 'bar',
- ...NamedArgsMarker,
- });
-
resolve(NoArgsComponent)(
- { ...NamedArgsMarker },
// @ts-expect-error: extra positional arg
'oops'
);
@@ -38,9 +31,9 @@ import { ComponentLike, WithBoundArgs } from '@glint/template';
templateForBackingValue(NoArgsComponent, function (𝚪) {
expectTypeOf(𝚪.this).toBeNull();
- expectTypeOf(𝚪.args).toEqualTypeOf();
- expectTypeOf(𝚪.element).toBeNull();
- expectTypeOf(𝚪.blocks).toEqualTypeOf();
+ expectTypeOf(𝚪.args).toEqualTypeOf<{}>();
+ expectTypeOf(𝚪.element).toBeUnknown();
+ expectTypeOf(𝚪.blocks).toEqualTypeOf<{}>();
});
}
@@ -124,7 +117,7 @@ import { ComponentLike, WithBoundArgs } from '@glint/template';
const CurriedWithNothing = resolve(componentKeyword)('curried-component');
expectTypeOf(resolve(CurriedWithNothing)).toEqualTypeOf<
- (args: NamedArgs<{ a: string; b: number }>) => ComponentReturn
+ (args: NamedArgs<{ a: string; b: number }>) => ComponentReturn<{}>
>();
const CurriedWithA = resolve(componentKeyword)('curried-component', {
@@ -132,7 +125,7 @@ import { ComponentLike, WithBoundArgs } from '@glint/template';
...NamedArgsMarker,
});
expectTypeOf(resolve(CurriedWithA)).toEqualTypeOf<
- (args: NamedArgs<{ a?: string; b: number }>) => ComponentReturn
+ (args: NamedArgs<{ a?: string; b: number }>) => ComponentReturn<{}>
>();
}
diff --git a/packages/environment-ember-template-imports/-private/dsl/index.d.ts b/packages/environment-ember-template-imports/-private/dsl/index.d.ts
index c216cddab..01caac79d 100644
--- a/packages/environment-ember-template-imports/-private/dsl/index.d.ts
+++ b/packages/environment-ember-template-imports/-private/dsl/index.d.ts
@@ -9,7 +9,6 @@ import {
AnyContext,
AnyFunction,
DirectInvokable,
- EmptyObject,
HasContext,
InvokableInstance,
Invoke,
@@ -38,8 +37,8 @@ export declare const resolveOrReturn: ResolveOrReturn;
import { TemplateOnlyComponent } from '@ember/component/template-only';
export declare function templateExpression<
- Signature extends AnyFunction = () => ComponentReturn,
- Context extends AnyContext = TemplateContext
+ Signature extends AnyFunction = () => ComponentReturn<{}>,
+ Context extends AnyContext = TemplateContext
>(
f: (𝚪: Context, χ: never) => void
): TemplateOnlyComponent &
diff --git a/packages/environment-glimmerx/-private/dsl/index.d.ts b/packages/environment-glimmerx/-private/dsl/index.d.ts
index ba87b0120..93b1a1060 100644
--- a/packages/environment-glimmerx/-private/dsl/index.d.ts
+++ b/packages/environment-glimmerx/-private/dsl/index.d.ts
@@ -27,7 +27,6 @@ import {
AnyContext,
AnyFunction,
DirectInvokable,
- EmptyObject,
HasContext,
InvokableInstance,
Invoke,
@@ -54,8 +53,8 @@ export declare const resolveOrReturn: ResolveOrReturn;
import { TemplateComponentInstance } from '@glimmerx/component';
export declare function templateExpression<
- Signature extends AnyFunction = () => ComponentReturn,
- Context extends AnyContext = TemplateContext
+ Signature extends AnyFunction = () => ComponentReturn<{}>,
+ Context extends AnyContext = TemplateContext
>(
f: (𝚪: Context, χ: never) => void
): abstract new () => TemplateComponentInstance &
diff --git a/packages/environment-glimmerx/__tests__/component.test.ts b/packages/environment-glimmerx/__tests__/component.test.ts
index b4a747d3f..7a6c5428b 100644
--- a/packages/environment-glimmerx/__tests__/component.test.ts
+++ b/packages/environment-glimmerx/__tests__/component.test.ts
@@ -8,7 +8,7 @@ import {
NamedArgsMarker,
} from '@glint/environment-glimmerx/-private/dsl';
import { expectTypeOf } from 'expect-type';
-import { ComponentReturn, EmptyObject } from '@glint/template/-private/integration';
+import { ComponentReturn } from '@glint/template/-private/integration';
{
class NoArgsComponent extends Component {
@@ -17,12 +17,6 @@ import { ComponentReturn, EmptyObject } from '@glint/template/-private/integrati
});
}
- resolve(NoArgsComponent)({
- // @ts-expect-error: extra named arg
- foo: 'bar',
- ...NamedArgsMarker,
- });
-
resolve(NoArgsComponent)(
// @ts-expect-error: bad positional arg
'oops'
@@ -47,7 +41,7 @@ import { ComponentReturn, EmptyObject } from '@glint/template/-private/integrati
static template = templateForBackingValue(this, function (𝚪) {
expectTypeOf(𝚪.this.foo).toEqualTypeOf();
expectTypeOf(𝚪.this).toEqualTypeOf();
- expectTypeOf(𝚪.args).toEqualTypeOf();
+ expectTypeOf(𝚪.args).toEqualTypeOf<{}>();
});
}
@@ -152,11 +146,11 @@ import { ComponentReturn, EmptyObject } from '@glint/template/-private/integrati
const NoAnnotationTC = templateExpression(function (𝚪) {
expectTypeOf(𝚪.this).toBeVoid();
expectTypeOf(𝚪.element).toBeVoid();
- expectTypeOf(𝚪.args).toEqualTypeOf();
- expectTypeOf(𝚪.blocks).toEqualTypeOf();
+ expectTypeOf(𝚪.args).toEqualTypeOf<{}>();
+ expectTypeOf(𝚪.blocks).toEqualTypeOf<{}>();
});
- expectTypeOf(resolve(NoAnnotationTC)).toEqualTypeOf<() => ComponentReturn>();
+ expectTypeOf(resolve(NoAnnotationTC)).toEqualTypeOf<() => ComponentReturn<{}>>();
}
{
@@ -173,7 +167,7 @@ import { ComponentReturn, EmptyObject } from '@glint/template/-private/integrati
let YieldingTC: TC = templateExpression(function (𝚪) {
expectTypeOf(𝚪.this).toEqualTypeOf(null);
expectTypeOf(𝚪.args).toEqualTypeOf<{ values: Array }>();
- expectTypeOf(𝚪.element).toBeNull();
+ expectTypeOf(𝚪.element).toBeUnknown();
expectTypeOf(𝚪.blocks).toEqualTypeOf();
if (𝚪.args.values.length) {
diff --git a/packages/environment-glimmerx/__tests__/helper.test.ts b/packages/environment-glimmerx/__tests__/helper.test.ts
index c00016548..6038db441 100644
--- a/packages/environment-glimmerx/__tests__/helper.test.ts
+++ b/packages/environment-glimmerx/__tests__/helper.test.ts
@@ -1,6 +1,6 @@
import { emitContent, NamedArgsMarker, resolve } from '@glint/environment-glimmerx/-private/dsl';
import { helper, fn as fnDefinition } from '@glimmerx/helper';
-import { EmptyObject, NamedArgs } from '@glint/template/-private/integration';
+import { NamedArgs } from '@glint/template/-private/integration';
import { expectTypeOf } from 'expect-type';
import '@glint/environment-glimmerx';
@@ -32,13 +32,14 @@ import '@glint/environment-glimmerx';
let definition = helper(([a, b]: [T, U]) => a || b);
let or = resolve(definition);
- expectTypeOf(or).toEqualTypeOf<{ (t: T, u: U, named?: NamedArgs): T | U }>();
+ expectTypeOf(or).toEqualTypeOf<{ (t: T, u: U): T | U }>();
- or('a', 'b', {
- // @ts-expect-error: extra named arg
- hello: true,
- ...NamedArgsMarker,
- });
+ or(
+ 'a',
+ 'b',
+ // @ts-expect-error: unexpected named args
+ { hello: true, ...NamedArgsMarker }
+ );
// @ts-expect-error: missing positional arg
or('a');
diff --git a/packages/template/-private/dsl/emit.d.ts b/packages/template/-private/dsl/emit.d.ts
index 06fd7b54c..4b832bf23 100644
--- a/packages/template/-private/dsl/emit.d.ts
+++ b/packages/template/-private/dsl/emit.d.ts
@@ -4,7 +4,6 @@ import {
AnyContext,
AnyFunction,
ModifierReturn,
- EmptyObject,
HasContext,
InvokableInstance,
TemplateContext,
@@ -80,8 +79,8 @@ export declare function emitComponent>(
* environment's DSL export.
*/
export declare function templateExpression<
- Signature extends AnyFunction = () => ComponentReturn,
- Context extends AnyContext = TemplateContext
+ Signature extends AnyFunction = () => ComponentReturn<{}>,
+ Context extends AnyContext = TemplateContext
>(f: (𝚪: Context, χ: never) => void): new () => InvokableInstance & HasContext;
/*
diff --git a/packages/template/-private/integration.d.ts b/packages/template/-private/integration.d.ts
index ec0465b0e..ec1a935bd 100644
--- a/packages/template/-private/integration.d.ts
+++ b/packages/template/-private/integration.d.ts
@@ -6,9 +6,6 @@
// `ComponentLike`/`HelperLike`/`ModifierLike`, but these declarations are
// the primitives on which those types are built.
-declare const Empty: unique symbol;
-export type EmptyObject = { [Empty]?: true };
-
/** Any function, which is the tighest bound we can put on an object's `[Invoke]` field. */
export type AnyFunction = (...params: any) => any;
diff --git a/packages/template/-private/signature.d.ts b/packages/template/-private/signature.d.ts
index 24c91193a..7c4839dbb 100644
--- a/packages/template/-private/signature.d.ts
+++ b/packages/template/-private/signature.d.ts
@@ -2,7 +2,7 @@
// in userspace into our internal representation of an invokable's
// function type signature.
-import { EmptyObject, NamedArgs, UnwrapNamedArgs } from './integration';
+import { NamedArgs, UnwrapNamedArgs } from './integration';
/**
* Given an "args hash" (e.g. `{ Named: {...}; Positional: [...] }`),
@@ -10,7 +10,7 @@ import { EmptyObject, NamedArgs, UnwrapNamedArgs } from './integration';
*/
export type InvokableArgs = [
...positional: Constrain, Array, []>,
- ...named: MaybeNamed>>>
+ ...named: MaybeNamed>>
];
/** Given a signature `S`, get back the normalized `Args` type. */
@@ -22,7 +22,7 @@ export type ComponentSignatureArgs = S extends {
Positional?: unknown[];
}
? {
- Named: Get;
+ Named: Get;
Positional: Get;
}
: {
@@ -30,7 +30,7 @@ export type ComponentSignatureArgs = S extends {
Positional: [];
}
: {
- Named: keyof S extends 'Args' | 'Blocks' | 'Element' ? EmptyObject : S;
+ Named: keyof S extends 'Args' | 'Blocks' | 'Element' ? {} : S;
Positional: [];
};
@@ -41,21 +41,24 @@ export type ComponentSignatureBlocks = S extends { Blocks: infer Blocks }
? { Params: { Positional: Blocks[Block] } }
: Blocks[Block];
}
- : EmptyObject;
+ : {};
/** Given a component signature `S`, get back the `Element` type. */
-export type ComponentSignatureElement = S extends { Element: infer Element } ? Element : null;
-
-// These shenanigans are necessary to get TS to report when named args
-// are passed to a signature that doesn't expect any, because `{}` is
-// special-cased in the type system not to trigger EPC.
-export type GuardEmpty = T extends any ? (keyof T extends never ? EmptyObject : T) : never;
+export type ComponentSignatureElement = S extends { Element: infer Element }
+ ? NonNullable extends never
+ ? unknown
+ : Element
+ : unknown;
export type PrebindArgs> = NamedArgs<
Omit, Args> & Partial, Args>>
>;
-export type MaybeNamed = {} extends UnwrapNamedArgs ? [named?: T] : [named: T];
+export type MaybeNamed = {} extends UnwrapNamedArgs
+ ? keyof UnwrapNamedArgs extends never
+ ? []
+ : [named?: T]
+ : [named: T];
export type Get = K extends keyof T ? T[K] : Otherwise;
export type Constrain = T extends Constraint ? T : Otherwise;
diff --git a/packages/template/__tests__/component-like.test.ts b/packages/template/__tests__/component-like.test.ts
index 6e3332855..7b2dfb3a9 100644
--- a/packages/template/__tests__/component-like.test.ts
+++ b/packages/template/__tests__/component-like.test.ts
@@ -2,21 +2,16 @@ import { ComponentLike, WithBoundArgs } from '@glint/template';
import { resolve, emitComponent, NamedArgsMarker } from '@glint/template/-private/dsl';
import { expectTypeOf } from 'expect-type';
import { ComponentReturn, NamedArgs } from '../-private/integration';
+import TestComponent from './test-component';
{
const NoArgsComponent = {} as ComponentLike<{}>;
+ // @ts-expect-error: extra arg
resolve(NoArgsComponent)({
- // @ts-expect-error: extra named arg
- foo: 'bar',
...NamedArgsMarker,
});
- resolve(NoArgsComponent)(
- // @ts-expect-error: extra positional arg
- 'oops'
- );
-
{
const component = emitComponent(resolve(NoArgsComponent)());
@@ -103,7 +98,7 @@ import { ComponentReturn, NamedArgs } from '../-private/integration';
const PositionalArgsComponent = {} as ComponentLike;
// @ts-expect-error: missing required positional arg
- resolve(PositionalArgsComponent)();
+ resolve(PositionalArgsComponent)({ ...NamedArgsMarker });
resolve(PositionalArgsComponent)(
'hello',
@@ -138,3 +133,80 @@ import { ComponentReturn, NamedArgs } from '../-private/integration';
) => ComponentReturn<{ default: [] }, HTMLCanvasElement>
>();
}
+
+// Assignability
+{
+ // A component with no signaure is a `ComponentLike` with no signature
+ expectTypeOf(TestComponent<{}>).toMatchTypeOf();
+
+ // A component whose args are all optional is a `ComponentLike` with no signature
+ expectTypeOf(TestComponent<{ Args: { optional?: true } }>).toMatchTypeOf();
+
+ // A component with a required arg can't be used as a blank `ComponentLike`
+ expectTypeOf(TestComponent<{ Args: { optional: false } }>).not.toMatchTypeOf();
+
+ // A component that yields a given block can be used without ever passing any blocks
+ expectTypeOf(TestComponent<{ Blocks: { default: [string] } }>).toMatchTypeOf();
+
+ // A component that yields specific args can be used as one that cares about fewer of them
+ expectTypeOf(TestComponent<{ Blocks: { default: [string, number] } }>).toMatchTypeOf<
+ ComponentLike<{ Blocks: { default: [string, ...unknown[]] } }>
+ >();
+
+ // A component that never yields can't be used as one that accepts a specific block
+ expectTypeOf(TestComponent).not.toMatchTypeOf>();
+
+ // `T | null` is useful to humans to signify that a component might splat its ...attributes,
+ // but from a type perspective it's just the same as `T`
+ expectTypeOf>().toEqualTypeOf<
+ ComponentLike<{ Element: HTMLDivElement }>
+ >();
+
+ // Our canonical internal representation of a no-splattributes component's `Element` is `unknown`
+ expectTypeOf().toEqualTypeOf>();
+ expectTypeOf>().toEqualTypeOf<
+ ComponentLike<{ Element: unknown }>
+ >();
+
+ // A component with all-optional args and any arbitrary element/blocks should be usable
+ // as a blank `ComponentLike`.
+ expectTypeOf(
+ TestComponent<{
+ Args: { foo?: string };
+ Element: HTMLImageElement;
+ Blocks: { default: [] };
+ }>
+ ).toMatchTypeOf();
+
+ // Components are contravariant with their named `Args` type
+ expectTypeOf>().toMatchTypeOf<
+ ComponentLike<{ Args: { name: 'Dan' } }>
+ >();
+ expectTypeOf>().not.toMatchTypeOf<
+ ComponentLike<{ Args: { name: string } }>
+ >();
+
+ // Components are contravariant with their positional `Args` type
+ expectTypeOf>().toMatchTypeOf<
+ ComponentLike<{ Args: { Positional: [name: 'Dan'] } }>
+ >();
+ expectTypeOf>().not.toMatchTypeOf<
+ ComponentLike<{ Args: { Positional: [name: string] } }>
+ >();
+
+ // Components are covariant with their `Element` type
+ expectTypeOf>().toMatchTypeOf<
+ ComponentLike<{ Element: HTMLElement }>
+ >();
+ expectTypeOf>().not.toMatchTypeOf<
+ ComponentLike<{ Element: HTMLAudioElement }>
+ >();
+
+ // Components are covariant with their `Blocks`' `Params` types
+ expectTypeOf(TestComponent<{ Blocks: { default: ['abc', 123] } }>).toMatchTypeOf<
+ ComponentLike<{ Blocks: { default: [string, number] } }>
+ >();
+ expectTypeOf(TestComponent<{ Blocks: { default: [string, number] } }>).not.toMatchTypeOf<
+ ComponentLike<{ Blocks: { default: ['abc', 123] } }>
+ >();
+}
diff --git a/packages/template/__tests__/helper-like.test.ts b/packages/template/__tests__/helper-like.test.ts
index 368da189f..6d6d202c7 100644
--- a/packages/template/__tests__/helper-like.test.ts
+++ b/packages/template/__tests__/helper-like.test.ts
@@ -1,7 +1,7 @@
import { NamedArgsMarker, resolve } from '@glint/environment-ember-loose/-private/dsl';
import { expectTypeOf } from 'expect-type';
import { HelperLike, WithBoundArgs } from '@glint/template';
-import { EmptyObject, NamedArgs } from '../-private/integration';
+import { NamedArgs } from '../-private/integration';
// Fixed signature params
{
@@ -54,20 +54,13 @@ import { EmptyObject, NamedArgs } from '../-private/integration';
let definition!: new () => InstanceType>>;
let or = resolve(definition);
- expectTypeOf(or).toEqualTypeOf<{ (t: T, u: U, args?: NamedArgs): T | U }>();
-
- or('a', 'b', {
- // @ts-expect-error: extra named arg
- hello: true,
- ...NamedArgsMarker,
- });
+ expectTypeOf(or).toEqualTypeOf<{ (t: T, u: U): T | U }>();
or(
'a',
'b',
- 'c',
// @ts-expect-error: extra positional arg
- { ...NamedArgsMarker }
+ 'c'
);
expectTypeOf(or('a', 'b')).toEqualTypeOf();
@@ -88,3 +81,30 @@ import { EmptyObject, NamedArgs } from '../-private/integration';
(args: NamedArgs<{ age: number; name?: string }>) => string
>();
}
+
+// Assignability
+{
+ // Helpers are contravariant with their named `Args` type
+ expectTypeOf>().toMatchTypeOf<
+ HelperLike<{ Args: { Named: { name: 'Dan' } } }>
+ >();
+ expectTypeOf>().not.toMatchTypeOf<
+ HelperLike<{ Args: { Named: { name: string } } }>
+ >();
+
+ // Helpers are contravariant with their positional `Args` type
+ expectTypeOf>().toMatchTypeOf<
+ HelperLike<{ Args: { Positional: [name: 'Dan'] } }>
+ >();
+ expectTypeOf>().not.toMatchTypeOf<
+ HelperLike<{ Args: { Positional: [name: string] } }>
+ >();
+
+ // Helpers are contravariant with their `Element` type
+ expectTypeOf>().toMatchTypeOf<
+ HelperLike<{ Return: string }>
+ >();
+ expectTypeOf>().not.toMatchTypeOf<
+ HelperLike<{ Return: 'Hello, World' }>
+ >();
+}
diff --git a/packages/template/__tests__/modifier-like.test.ts b/packages/template/__tests__/modifier-like.test.ts
index 317a1f750..20be3c9a1 100644
--- a/packages/template/__tests__/modifier-like.test.ts
+++ b/packages/template/__tests__/modifier-like.test.ts
@@ -96,3 +96,30 @@ import { ModifierLike, WithBoundArgs } from '@glint/template';
) => ModifierReturn
>();
}
+
+// Assignability
+{
+ // Modifiers are contravariant with their named `Args` type
+ expectTypeOf>().toMatchTypeOf<
+ ModifierLike<{ Args: { Named: { name: 'Dan' } } }>
+ >();
+ expectTypeOf>().not.toMatchTypeOf<
+ ModifierLike<{ Args: { Named: { name: string } } }>
+ >();
+
+ // Modifiers are contravariant with their positional `Args` type
+ expectTypeOf>().toMatchTypeOf<
+ ModifierLike<{ Args: { Positional: [name: 'Dan'] } }>
+ >();
+ expectTypeOf>().not.toMatchTypeOf<
+ ModifierLike<{ Args: { Positional: [name: string] } }>
+ >();
+
+ // Modifiers are contravariant with their `Element` type
+ expectTypeOf>().toMatchTypeOf<
+ ModifierLike<{ Element: HTMLAudioElement }>
+ >();
+ expectTypeOf>().not.toMatchTypeOf<
+ ModifierLike<{ Element: HTMLElement }>
+ >();
+}
diff --git a/packages/template/__tests__/signature-test.test.ts b/packages/template/__tests__/signature-test.test.ts
index 91d4516ca..fa59161d1 100644
--- a/packages/template/__tests__/signature-test.test.ts
+++ b/packages/template/__tests__/signature-test.test.ts
@@ -1,5 +1,4 @@
import { expectTypeOf } from 'expect-type';
-import { EmptyObject } from '../-private/integration';
import {
ComponentSignatureArgs,
ComponentSignatureBlocks,
@@ -14,8 +13,8 @@ expectTypeOf>().toEqualTypeOf<{
Named: LegacyArgs;
Positional: [];
}>();
-expectTypeOf>().toEqualTypeOf();
-expectTypeOf>().toEqualTypeOf();
+expectTypeOf>().toEqualTypeOf<{}>();
+expectTypeOf>().toEqualTypeOf();
// Here, we are testing that the types propertly distribute over union types,
// generics which extend other types, etc.
@@ -25,8 +24,8 @@ expectTypeOf>().toEqualTypeOf<
| { Named: { foo: number }; Positional: [] }
| { Named: { bar: string; baz: boolean }; Positional: [] }
>();
-expectTypeOf>().toEqualTypeOf();
-expectTypeOf>().toEqualTypeOf();
+expectTypeOf>().toEqualTypeOf<{}>();
+expectTypeOf>().toEqualTypeOf();
interface ArgsOnly {
Args: LegacyArgs;
@@ -36,18 +35,18 @@ expectTypeOf>().toEqualTypeOf<{
Named: LegacyArgs;
Positional: [];
}>();
-expectTypeOf>().toEqualTypeOf();
-expectTypeOf>().toEqualTypeOf();
+expectTypeOf>().toEqualTypeOf<{}>();
+expectTypeOf>().toEqualTypeOf();
interface ElementOnly {
Element: HTMLParagraphElement;
}
expectTypeOf>().toEqualTypeOf<{
- Named: EmptyObject;
+ Named: {};
Positional: [];
}>();
-expectTypeOf>().toEqualTypeOf();
+expectTypeOf>().toEqualTypeOf<{}>();
expectTypeOf>().toEqualTypeOf();
interface Blocks {
@@ -60,7 +59,7 @@ interface BlockOnlySig {
}
expectTypeOf>().toEqualTypeOf<{
- Named: EmptyObject;
+ Named: {};
Positional: [];
}>();
expectTypeOf>().toEqualTypeOf<{
@@ -75,7 +74,7 @@ expectTypeOf>().toEqualTypeOf<{
};
};
}>();
-expectTypeOf>().toEqualTypeOf();
+expectTypeOf>().toEqualTypeOf();
interface ArgsAndBlocks {
Args: LegacyArgs;
@@ -98,7 +97,7 @@ expectTypeOf>().toEqualTypeOf<{
};
};
}>();
-expectTypeOf>().toEqualTypeOf();
+expectTypeOf>().toEqualTypeOf();
interface ArgsAndEl {
Args: LegacyArgs;
@@ -109,7 +108,7 @@ expectTypeOf>().toEqualTypeOf<{
Named: LegacyArgs;
Positional: [];
}>();
-expectTypeOf>().toEqualTypeOf();
+expectTypeOf>().toEqualTypeOf<{}>();
expectTypeOf>().toEqualTypeOf();
interface FullShortSig {
diff --git a/packages/template/__tests__/test-component.ts b/packages/template/__tests__/test-component.ts
index f139696f8..cac098332 100644
--- a/packages/template/__tests__/test-component.ts
+++ b/packages/template/__tests__/test-component.ts
@@ -3,7 +3,7 @@
// well as simple examples of a helper and modifier.
import { ComponentLike, ModifierLike } from '../-private/index';
-import { Context, EmptyObject, TemplateContext } from '../-private/integration';
+import { Context, TemplateContext } from '../-private/integration';
import { LetKeyword } from '../-private/keywords';
export default TestComponent;
@@ -19,7 +19,7 @@ export declare const globals: {
>;
};
-type Get = K extends keyof T ? Exclude : Otherwise;
+type Get = K extends keyof T ? Exclude : Otherwise;
interface TestComponent extends InstanceType> {}
declare class TestComponent {
diff --git a/test-packages/ts-ember-app/tests/integration/types/empty-signature-test.ts b/test-packages/ts-ember-app/tests/integration/types/empty-signature-test.ts
index e1c416ad4..bf74eeff6 100644
--- a/test-packages/ts-ember-app/tests/integration/types/empty-signature-test.ts
+++ b/test-packages/ts-ember-app/tests/integration/types/empty-signature-test.ts
@@ -27,8 +27,8 @@ module('Integration | Types | empty object signature members', function (hooks)
<:named>
-
`);