diff --git a/biome.json b/biome.json index dadb42fa7..0dca84ebc 100644 --- a/biome.json +++ b/biome.json @@ -20,6 +20,7 @@ "useConst": "off" }, "suspicious": { + "noConfusingVoidType": "off", "noExplicitAny": "off", "noRedeclare": "off", "useIsArray": "off" diff --git a/packages/runtime/src/resolve.ts b/packages/runtime/src/resolve.ts index 542cd2646..a036cdd55 100644 --- a/packages/runtime/src/resolve.ts +++ b/packages/runtime/src/resolve.ts @@ -1,24 +1,28 @@ -import * as im from "immutable"; +import im from "immutable"; import * as tg from "./index.ts"; import type { MaybePromise } from "./util.ts"; -export type Unresolved = MaybePromise< - T extends - | undefined - | boolean - | number - | string - | tg.Object - | Uint8Array - | tg.Mutation - | tg.Template - ? T - : T extends Array - ? Array> - : T extends { [key: string]: tg.Value } - ? { [K in keyof T]: Unresolved } - : never ->; +export type Unresolved = MaybePromise>; + +export type UnresolvedInner = T extends + | undefined + | boolean + | number + | string + | tg.Object + | Uint8Array + | tg.Mutation + | tg.Template + ? T + : T extends Array + ? Array> + : T extends { + [key: string]: tg.Value; + } + ? { + [K in keyof T]: Unresolved; + } + : never; export type Resolved> = T extends | undefined diff --git a/packages/runtime/src/target.ts b/packages/runtime/src/target.ts index 8d41f2307..5ffc3a755 100644 --- a/packages/runtime/src/target.ts +++ b/packages/runtime/src/target.ts @@ -1,4 +1,5 @@ import * as tg from "./index.ts"; +import type { UnresolvedInner } from "./resolve.ts"; import { type MaybeMutationMap, type MaybeNestedArray, @@ -14,24 +15,26 @@ export let setCurrentTarget = (target: Target) => { type FunctionArg< A extends Array = Array, - R extends tg.Value = tg.Value, + R extends void | tg.Value = void | tg.Value, > = { - function: (...args: A) => tg.Unresolved; + function: ( + ...args: A + ) => MaybePromise>>; module: tg.Module; name: string; }; export function target< A extends Array = Array, - R extends tg.Value = tg.Value, + R extends void | tg.Value = void | tg.Value, >(arg: FunctionArg): Target; export function target< A extends Array = Array, - R extends tg.Value = tg.Value, + R extends void | tg.Value = void | tg.Value, >(...args: tg.Args): Promise>; export function target< A extends Array = Array, - R extends tg.Value = tg.Value, + R extends void | tg.Value = void | tg.Value, >( ...args: [FunctionArg] | tg.Args ): MaybePromise> { @@ -70,7 +73,7 @@ export function target< export interface Target< A extends Array = Array, - R extends tg.Value = tg.Value, + R extends void | tg.Value = void | tg.Value, > extends globalThis.Function { (...args: { [K in keyof A]: tg.Unresolved }): Promise; } @@ -78,7 +81,7 @@ export interface Target< // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: This is necessary to make targets callable. export class Target< A extends Array = Array, - R extends tg.Value = tg.Value, + R extends void | tg.Value = void | tg.Value, > extends globalThis.Function { #state: Target.State; #f: Function | undefined; @@ -116,7 +119,7 @@ export class Target< static async new< A extends Array = Array, - R extends tg.Value = tg.Value, + R extends void | tg.Value = void | tg.Value, >(...args: tg.Args): Promise> { let arg = await Target.arg(...args); let args_ = arg.args ?? []; diff --git a/packages/runtime/tangram.d.ts b/packages/runtime/tangram.d.ts index 9704c3631..d541c321a 100644 --- a/packages/runtime/tangram.d.ts +++ b/packages/runtime/tangram.d.ts @@ -408,7 +408,9 @@ declare namespace tg { } /** Create a symlink. */ - export let symlink: (arg: tg.Unresolved) => Promise; + export let symlink: ( + arg: tg.Unresolved, + ) => Promise; /** A symlink. */ export class Symlink { @@ -546,17 +548,23 @@ declare namespace tg { /** Create a target. */ export function target< A extends Array = Array, - R extends tg.Value = tg.Value, - >(function_: (...args: A) => tg.Unresolved): tg.Target; + R extends void | tg.Value = void | tg.Value, + >( + function_: ( + ...args: A + ) => MaybePromise< + R extends void ? void : tg.UnresolvedInner> + >, + ): tg.Target; export function target< A extends Array = Array, - R extends tg.Value = tg.Value, + R extends void | tg.Value = void | tg.Value, >(...args: tg.Args): Promise>; /** A target. */ export interface Target< A extends Array = Array, - R extends tg.Value = tg.Value, + R extends void | tg.Value = void | tg.Value, > { /** Build this target. */ // biome-ignore lint/style/useShorthandFunctionType: interface is necessary . @@ -566,7 +574,7 @@ declare namespace tg { /** A target. */ export class Target< A extends Array = Array, - R extends tg.Value = tg.Value, + R extends void | tg.Value = void | tg.Value, > extends globalThis.Function { /** Get a target with an ID. */ static withId(id: tg.Target.Id): tg.Target; @@ -941,23 +949,29 @@ declare namespace tg { * ``` */ export type Unresolved = tg.MaybePromise< - T extends - | undefined - | boolean - | number - | string - | tg.Object - | Uint8Array - | tg.Mutation - | tg.Template - ? T - : T extends Array - ? Array> - : T extends { [key: string]: tg.Value } - ? { [K in keyof T]: tg.Unresolved } - : never + tg.UnresolvedInner >; + type UnresolvedInner = T extends + | undefined + | boolean + | number + | string + | tg.Object + | Uint8Array + | tg.Mutation + | tg.Template + ? T + : T extends Array + ? Array> + : T extends { + [key: string]: tg.Value; + } + ? { + [K in keyof T]: Unresolved; + } + : never; + /** * This computed type performs the inverse of `Unresolved`. It takes a type and returns the output of calling `resolve` on a value of that type. Here are some examples: * diff --git a/packages/server/src/package/check/tests.rs b/packages/server/src/package/check/tests.rs index 10f46f434..a913c1283 100644 --- a/packages/server/src/package/check/tests.rs +++ b/packages/server/src/package/check/tests.rs @@ -70,6 +70,26 @@ async fn nonexistent_function() -> tg::Result<()> { .await } +#[tokio::test] +async fn no_return_value() -> tg::Result<()> { + test( + temp::directory! { + "tangram.ts" => indoc!(r" + export default tg.target(() => {}); + "), + }, + |_, output| async move { + assert_json_snapshot!(output, @r#" + { + "diagnostics": [] + } + "#); + Ok(()) + }, + ) + .await +} + async fn test(artifact: temp::Artifact, assertions: F) -> tg::Result<()> where F: FnOnce(Server, tg::package::check::Output) -> Fut,