Skip to content

Functions

Functions are an experimental TypeSpec feature. The API and behavior of functions may reasonably be expected to change in future releases as we gather feedback. If you choose to use functions in your TypeSpec libraries and programs, please be aware that you may need to make adjustments to your code when updating to new versions of TypeSpec.

TypeSpec functions, like Decorators, are implemented using JavaScript functions. To provide a function in your library, you must:

  1. Declare the function signature in TypeSpec.
  2. Implement the function in JavaScript.

Unlike decorators, declaring a function’s signature in TypeSpec is mandatory. A function signature is declared using the fn keyword. Functions are implemented in JavaScript and therefore the signature also requires the extern keyword.

extern fn myFn();

A function signature can specify a list of parameters and optionally a return type constraint.

/**
* Concatenates to strings, equivalent to `l + r` in JavaScript, where `l` and `r` are strings.
*
* @param l String to be appended first to the result string.
* @param r String to be appended second to the result string.
* @returns the result of concatenating `l` and `r`.
*/
extern fn concat(l: valueof string, r: valueof string): valueof string;

Type constraints for parameters work exactly the same as constraints for Decorator.

You can mark a function parameter as optional using ?:

/**
* Renames a model, if `name` is provided and different from the input model's name.
*
* @param m the input Model to rename
* @param name if set, the name of the output model
* @returns `m` if `name` is not set or `m`'s name is equal to `name`, otherwise a new Model instance with the given
* name that is otherwise identical to `m`.
*/
extern fn rename(m: Reflection.Model, name?: valueof string): Reflection.Model;

Functions may also specify “rest” parameters. The rest parameter collects all remaining arguments passed to the function, and is declared using .... The type of a rest parameter must be an array.

/**
* Joins a list of strings, equivalent to `rest.join(sep)` in JavaScript.
*
* @param sep the separator string used to join the list.
* @param rest the list of strings to join
* @returns the list of strings joined by the separator
*/
extern fn join(sep: valueof string, ...rest: valueof string[]): valueof string;

Functions may optionally specify a return type constraint. The return type constraint is checked when the function is called, and whatever the function returns must be assignable to the constraint.

The void return type is treated specially. A JS implementation for a TypeSpec function that returns void may return either undefined, or an instance of the void intrinsic type, for compatibility with JavaScript void functions. Regardless of what the implementation returns, the TypeSpec function call will always evaluate to void.

namespace Example;
extern fn myFn(): void;
// Calling myFn() is guaranteed to evaluate to the `void` intrinsic type.

Functions must be implemented in a JavaScript library by exporting the functions the library implements using the $functions variable.

lib.ts
import { FunctionContext } from "@typespec/compiler";
export const $functions = {
// Namespace
"MyOrg.MyLib": {
concat,
},
};
function concat(context: FunctionContext, l: string, r: string): string {
return l + r;
}

The function implementation must be imported from TypeSpec to bind to the declaration in the signature:

lib.tsp
import "./lib.js";
namespace MyOrg.MyLib;
extern fn concat(l: valueof string, r: valueof string): valueof string;

The first argument passed to a JS function implementation is always the function’s context, which has type FunctionContext. The context provides information about where the function call was located in TypeSpec source, and can be used to call other functions or invoke decorators from within the function implementation.

When function arguments are Types, the type is passed to the function as-is. When a function argument is a value, the function implementation receives a JavaScript value with a type that is appropriate for representing that value.

TypeSpec value typeMarshalled type in JS
stringstring
booleanboolean
numericNumeric or number (see below)
nullnull
enum memberEnumValue

When marshalling numeric values, either the Numeric wrapper type is used, or a number is passed directly, depending on whether the value can be represented as a JavaScript number without precision loss. In particular, the types numeric, integer, decimal, float, int64, uint64, and decimal128 are marshalled as a Numeric type. All other numeric types are marshalled as number.

When marshalling custom scalar subtypes, the marshalling behavior of the known supertype is used. For example, a scalar customScalar extends numeric will marshal as a Numeric, regardless of any value constraints that might be present.

The function context provides the functionCallTarget and getArgumentTarget helpers.

import type { FunctionContext, Type } from "typespec/compiler";
import { reportDiagnostic } from "./lib.js";
export function renamed(ctx: FunctionContext, model: Model, name: string): Model {
// To report a diagnostic on the function call
reportDiagnostic({
code: "my-diagnostic-code",
target: ctx.functionCallTarget,
});
// To report an error on a specific argument (for example the `model`), use the argument target.
// Note: targeting the `model` itself will put the diagnostic on the type's _declaration_, but using
// getArgumentTarget will put it on the _function argument_, which is probably what you want.
reportDiagnostic({
code: "my-other-code",
target: ctx.getArgumentTarget(0),
});
}