Skip to content

Commit

Permalink
[compiler] Named instantiation of template arguments (#2546)
Browse files Browse the repository at this point in the history
Implementation of named template arguments as described in #2340.

I added a new syntax node (TemplateArgument), and a cover grammar for
this node:

`<Expression> ('=' <Expression>)?`

Rather than ExpressionNode, the type of a template argument is now
`TemplateArgumentNode`.

If the `=` is parsed, we assert in the parser that the first Expression
must be a "bare identifier" (i.e. a TypeReference with `target:
Identifier`) and unwrap the identifier to produce a clean AST with
`name?: Identifier

Template arguments are evaluated in the order that they were declared,
not in the order they are specified in the instantiation. The new
template argument checker _replaces_ the previous one, and it performs
normalization of argument order and typechecking of template arguments
all in one pass.

TODO:
- [x] Review templates across core libraries to ensure that template
arguments have good names.
- [x] Documentation of new behavior in website.
- [x] Semantic/tmlanguage colorization of tokens. 
- [x] Validate that the AST printer produces the correct text for the
new template argument nodes and that the output is well-formatted.
- [x] Completion of template argument names in argument position.
- [x] Hover context on template argument names in instantiation.

Closes #2340

---------

Co-authored-by: Will Temple <[email protected]>
Co-authored-by: Mark Cowlishaw <[email protected]>
Co-authored-by: Timothee Guerin <[email protected]>
  • Loading branch information
4 people authored Dec 18, 2023
1 parent aae7166 commit cb92f49
Show file tree
Hide file tree
Showing 15 changed files with 865 additions and 112 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@typespec/compiler",
"comment": "Added support for named template arguments (#2340)",
"type": "none"
}
],
"packageName": "@typespec/compiler"
}
39 changes: 22 additions & 17 deletions docs/introduction/style-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,28 @@ The guidelines in this article are used in TypeSpec Core libraries. You can use

## Naming convention

| Type | Naming | Example |
| ---------------- | -------------------------------------------- | ------------------------------------------------ |
| scalar | camelCase | `scalar uuid extends string;` |
| model | PascalCase | `model Pet {}` |
| model property | camelCase | `model Pet {furColor: string}` |
| enum | PascalCase | `enum Direction {}` |
| enum member | camelCase | `enum Direction {up, down}` |
| namespace | PascalCase | `namespace Org.PetStore` |
| interface | PascalCase | `interface Stores {}` |
| operation | camelCase | `op listPets(): Pet[];` |
| operation params | camelCase | `op getPet(petId: string): Pet;` |
| unions | PascalCase | `union Pet {cat: Cat, dog: Dog}` |
| unions variants | camelCase | `union Pet {cat: Cat, dog: Dog}` |
| alias | camelCase or PascalCase depending on context | `alias myString = string` or `alias MyPet = Pet` |
| decorators | camelCase | `@format`, `@resourceCollection` |
| functions | camelCase | `addedAfter` |
| file name | kebab-case | `my-lib.tsp` |
| Type | Naming | Example |
| ------------------ | -------------------------------------------- | ------------------------------------------------ |
| scalar | camelCase | `scalar uuid extends string;` |
| model | PascalCase | `model Pet {}` |
| model property | camelCase | `model Pet {furColor: string}` |
| enum | PascalCase | `enum Direction {}` |
| enum member | camelCase | `enum Direction {up, down}` |
| namespace | PascalCase | `namespace Org.PetStore` |
| interface | PascalCase | `interface Stores {}` |
| operation | camelCase | `op listPets(): Pet[];` |
| operation params | camelCase | `op getPet(petId: string): Pet;` |
| unions | PascalCase | `union Pet {cat: Cat, dog: Dog}` |
| unions variants | camelCase | `union Pet {cat: Cat, dog: Dog}` |
| alias | camelCase or PascalCase depending on context | `alias myString = string` or `alias MyPet = Pet` |
| decorators | camelCase | `@format`, `@resourceCollection` |
| functions | camelCase | `addedAfter` |
| file name | kebab-case | `my-lib.tsp` |
| template parameter | PascalCase | `<ExampleParameter>` |

:::note
In some languages, particularly object-oriented programming languages, it's conventional to prefix certain names with a letter to indicate what kind of thing they are. For example, prefixing interface names with `I` (as in `IPet`) or prefixing template parameter names with `T` (as in `TResponse`). **This is not conventional in TypeSpec**.
:::

## Layout convention

Expand Down
54 changes: 48 additions & 6 deletions docs/language-basics/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ It is often useful to let the users of a model fill in certain details. Template

Templates can be used on:

- [alias](./alias.md)
- [aliases](./alias.md)
- [models](./models.md)
- [operations](./operations.md)
- [interfaces](./interfaces.md)
Expand All @@ -27,7 +27,7 @@ model DogPage {

## Default values

A template parameter can be given a default value with `= <value>`.
A template parameter can be given a default argument value with `= <value>`.

```typespec
model Page<Item = string> {
Expand All @@ -38,31 +38,73 @@ model Page<Item = string> {

## Parameter constraints

Template parameter can provide a constraint using the `extends` keyword. See [type relations](./type-relations.md) documentation for details on how validation works.
Template parameters can specify a constraint using the `extends` keyword. See the [type relations](./type-relations.md) documentation for details on how validation works.

```typespec
alias Foo<Type extends string> = Type;
```

now instantiating Foo with the wrong type will result in an error
Now, instantiating Foo with an argument that does not satisfy the constraint `string` will result in an error:

```typespec
alias Bar = Foo<123>;
^ Type '123' is not assignable to type 'TypeSpec.string'
```

Template constraints can be a model expression
A template parameter constraint can also be a model expression:

```typespec
// Expect Type to be a model with property name: string
alias Foo<Type extends {name: string}> = Type;
```

Template parameter default also need to respect the constraint
Template parameter defaults also need to respect the constraint:

```typespec
alias Foo<Type extends string = "Abc"> = Type
// Invalid
alias Bar<Type extends string = 123> = Type
^ Type '123' is not assignable to type 'TypeSpec.string'
```

Furthermore, all optional arguments must come at the end of the template. A required argument cannot follow an optional argument:

```typespec
// Invalid
alias Foo<T extends string = "Abc", U> = ...;
^ Required template arguments must not follow optional template arguments
```

## Named template arguments

Template arguments may also be specified by name. In that case, they can be specified out of order and optional arguments may be omitted. This can be useful when dealing with templates that have many defaultable arguments:

```typespec
alias Test<T, U extends numeric = int32, V extends string = "example"> = ...;
// Specify the argument V by name to skip argument U, since U is optional and we
// are okay with its default
alias Example1 = Test<unknown, V = "example1">;
// Even all three arguments can be specified out of order
alias Example2 = Test<
V = "example2",
T = unknown,
U = uint64
>;
```

However, once a template argument is specified by name, all subsequent arguments must also be specified by name:

```typespec
// Invalid
alias Example3 = Test<
V = "example3",
unknown,
^^^^^^^ Positional template arguments cannot follow named arguments in the same argument list.
>;
```

Since template arguments may be specified by name, the names of template parameters are part of the public API of a template. **Changing the name of a template parameter may break existing specifications that use the template.**

**Note**: Template arguments are evaluated in the order the parameters are defined in the template _definition_, not the order in which they are written in the template _instance_. Most of the time, this should not matter, but may be important in some cases where evaluating a template argument may invoke decorators with side effects.
Loading

0 comments on commit cb92f49

Please sign in to comment.