Skip to content

[compiler] Functions, reborn#9060

Merged
witemple-msft merged 83 commits intomicrosoft:mainfrom
witemple-msft:witemple-msft/extern-fn
Feb 25, 2026
Merged

[compiler] Functions, reborn#9060
witemple-msft merged 83 commits intomicrosoft:mainfrom
witemple-msft:witemple-msft/extern-fn

Conversation

@witemple-msft
Copy link
Copy Markdown
Member

@witemple-msft witemple-msft commented Nov 21, 2025

This PR implements fn in TypeSpec. Functions are like decorators in that they are callable entities bound to implementations through the TypeSpec JavaScript host function interface.

Functions are declared using extern fn and must bind to an implementation declared through a $functions export in a JS source file.

Functions accept and produce entities. Value arguments to functions are converted to JS values like decorator arguments are, and the inverse is also true for functions: returned JS values are converted to TSP Value entities through an "unmarshaling" process. This allows the implementation to be natural for JS developers while integrating with the TSP value space.

Functions are values, not types. They can be assigned to const declarations. The result of calling a function can be either a Type or a Value, but the function itself is a value.

Functions can have a return type constraint. The default return type constraint of a function, if none is specified, is unknown. Functions can also return void, in which case JS functions that return undefined are specially accepted as if they returned voidType. This allows JS void functions to bind naturally to TypeSpec functions that return void.

Functions calls are evaluated at check-time. When a CallExpression is checked, where the callee is an instance of FunctionValue:

  • We first check the arguments to the function for compatibility with the function's signature.
  • We marshal the arguments to JS if necessary.
  • We call the underlying implementation to get the value it returns.
  • We unmarshal the return value to TypeSpec if necessary.
  • We check the unmarshaled entity for assignability to the return constraint.
  • The CallExpression checking result is the unmarshaled entity.

Functions support mixed constraints (Type | valueof Type) in both parameter and return position.

Like decorators, functions cannot serve as regular types and are only allowed to appear when resolving the target of a CallExpression. model Foo { p: f } where f is a FunctionType is not allowed, but model Foo { p: f() } is.

Unlike decorators, function host bindings MUST use $functions. Bare exported functions are not bound to JS source files.

Functions appear in the type graph as functionDeclarations on a Namespace. The semantic walker has been updated to visit FunctionValue declarations.

TSPD is updated to generate extern signatures for functions, like decorators.

In addition to function declarations, this PR also adds syntax for function types. A function type is the signature of a function and does not contain an implementation (it is not unique to a particular function declaration). Function types are only assignable to other function types and to the top type unknown. A function type is a type expression of the grammatical form 'fn' '(' ParameterList? ')' ('=>' MixedConstraint)?. Like with a function declaration, the return type is implicitly unknown if not specified. Function types observe strict assignability rules that obey contravariance over parameter assignability. A function type F1 is assignable to a function type F2 if:

  • F1 is identical to F2; OR
  • The parameters of F2 are assignable to the parameters of F1, AND the return type of F1 is assignable to the return type of F2.

Parameter assignability is determined by an algorithm that guarantees that each parameter in the source function (which is the target of parameter assignability) is satisfied by a corresponding parameter in the target function (which is the source of parameter assignability), given the following rules:

  • A required parameter with constraint T must be satisfied by a required parameter with constraint U, where U is assignable to T. A required parameter may not be satisfied by an optional parameter or an item of a rest parameter.
  • An optional parameter with constraint T may be satisfied by any parameter (required, optional, or rest item) of type U, but if it is, U must be assignable to T.
  • If the target parameter list terminates with a rest entry (...rest: T[]) of array type T[], then the type U of each subsequent parameter in the source parameter list (required, optional, or rest item) must be assignable to type T.

Notably:

  • Rest parameters cannot assign to required parameters, since a rest parameter is effectively optional: fn (x: valueof string) does NOT assign to fn(...rest: valueof string[]). TypeScript allows this, but it creates soundness problems that we will need to be aware of if we decide to relax this restriction in the future.

@witemple-msft witemple-msft force-pushed the witemple-msft/extern-fn branch from 302d35b to 09fa854 Compare February 12, 2026 19:46
Copy link
Copy Markdown
Member

@timotheeguerin timotheeguerin left a comment

Choose a reason for hiding this comment

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

Exiting!!

@witemple-msft
Copy link
Copy Markdown
Member Author

Companion PR is passing: Azure/typespec-azure#3865

@witemple-msft witemple-msft added this pull request to the merge queue Feb 25, 2026
Merged via the queue into microsoft:main with commit 14287e8 Feb 25, 2026
28 of 29 checks passed
@witemple-msft witemple-msft deleted the witemple-msft/extern-fn branch February 25, 2026 18:08
github-merge-queue bot pushed a commit to Azure/typespec-azure that referenced this pull request Feb 25, 2026
This is an integration PR for changes related to
microsoft/typespec#9060 ([compiler] Functions,
reborn).

- TCGC: loosened an exhaustiveness check around Value kinds in
`getValueTypeValue`.
- Generally: regenerated `generated-defs` which now exports decorators
as `_decs` rather than `_` and will generate function definitions if any
are declared.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler:core Issues for @typespec/compiler emitter:json-schema emitter:openapi3 Issues for @typespec/openapi3 emitter emitter:protobuf The protobuf emitter lib:http lib:openapi lib:rest lib:versioning meta:website TypeSpec.io updates spector Issues related to spector and the spec sets stale Mark a PR that hasn't been recently updated and will be closed. tspd Issues for the tspd tool ui:type-graph-viewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants