diff --git a/.chronus/changes/scalar-versioning-2024-2-26-9-56-30.md b/.chronus/changes/scalar-versioning-2024-2-26-9-56-30.md new file mode 100644 index 0000000000..09a0924452 --- /dev/null +++ b/.chronus/changes/scalar-versioning-2024-2-26-9-56-30.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@typespec/versioning" +--- + +Add support for versioning of scalars(Added, removed, renamed) \ No newline at end of file diff --git a/.chronus/changes/scalar-versioning-2024-2-26-9-56-6.md b/.chronus/changes/scalar-versioning-2024-2-26-9-56-6.md new file mode 100644 index 0000000000..21f0dd4800 --- /dev/null +++ b/.chronus/changes/scalar-versioning-2024-2-26-9-56-6.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@typespec/compiler" +--- + +Experimental projection: Add support for scalars \ No newline at end of file diff --git a/packages/compiler/src/core/binder.ts b/packages/compiler/src/core/binder.ts index 903d35f07f..bfbbe8df5c 100644 --- a/packages/compiler/src/core/binder.ts +++ b/packages/compiler/src/core/binder.ts @@ -383,6 +383,9 @@ export function createBinder(program: Program): Binder { case SyntaxKind.ProjectionModelPropertySelector: selectorString = "modelproperty"; break; + case SyntaxKind.ProjectionScalarSelector: + selectorString = "scalar"; + break; case SyntaxKind.ProjectionOperationSelector: selectorString = "op"; break; diff --git a/packages/compiler/src/core/checker.ts b/packages/compiler/src/core/checker.ts index 4e4e49ca8e..9547f57330 100644 --- a/packages/compiler/src/core/checker.ts +++ b/packages/compiler/src/core/checker.ts @@ -325,6 +325,7 @@ export function createChecker(program: Program): Checker { const projectionsByTypeKind = new Map([ ["Model", []], ["ModelProperty", []], + ["Scalar", []], ["Union", []], ["UnionVariant", []], ["Operation", []], @@ -4725,6 +4726,10 @@ export function createChecker(program: Program): Checker { projectionsByTypeKind.get("ModelProperty")!.push(node); type.nodeByKind.set("ModelProperty", node); break; + case SyntaxKind.ProjectionScalarSelector: + projectionsByTypeKind.get("Scalar")!.push(node); + type.nodeByKind.set("Scalar", node); + break; case SyntaxKind.ProjectionOperationSelector: projectionsByTypeKind.get("Operation")!.push(node); type.nodeByKind.set("Operation", node); diff --git a/packages/compiler/src/core/parser.ts b/packages/compiler/src/core/parser.ts index e9da33d870..46328c00a0 100644 --- a/packages/compiler/src/core/parser.ts +++ b/packages/compiler/src/core/parser.ts @@ -82,6 +82,7 @@ import { ProjectionNode, ProjectionOperationSelectorNode, ProjectionParameterDeclarationNode, + ProjectionScalarSelectorNode, ProjectionStatementItem, ProjectionStatementNode, ProjectionTupleExpressionNode, @@ -2377,6 +2378,7 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa | MemberExpressionNode | ProjectionInterfaceSelectorNode | ProjectionModelSelectorNode + | ProjectionScalarSelectorNode | ProjectionModelPropertySelectorNode | ProjectionOperationSelectorNode | ProjectionUnionSelectorNode @@ -2390,7 +2392,8 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa Token.OpKeyword, Token.InterfaceKeyword, Token.UnionKeyword, - Token.EnumKeyword + Token.EnumKeyword, + Token.ScalarKeyword ); switch (selectorTok) { @@ -2446,6 +2449,12 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa kind: SyntaxKind.ProjectionEnumSelector, ...finishNode(pos), }; + case Token.ScalarKeyword: + nextToken(); + return { + kind: SyntaxKind.ProjectionScalarSelector, + ...finishNode(pos), + }; default: // recovery: return a missing identifier to use as the selector // we don't need to emit a diagnostic here as the `expectTokenOneOf` above @@ -3374,6 +3383,7 @@ export function visitChildren(node: Node, cb: NodeCallback): T | undefined case SyntaxKind.EmptyStatement: case SyntaxKind.ProjectionModelSelector: case SyntaxKind.ProjectionModelPropertySelector: + case SyntaxKind.ProjectionScalarSelector: case SyntaxKind.ProjectionUnionSelector: case SyntaxKind.ProjectionUnionVariantSelector: case SyntaxKind.ProjectionInterfaceSelector: diff --git a/packages/compiler/src/core/projection-members.ts b/packages/compiler/src/core/projection-members.ts index 890af61627..c59e1ec102 100644 --- a/packages/compiler/src/core/projection-members.ts +++ b/packages/compiler/src/core/projection-members.ts @@ -137,6 +137,10 @@ export function createProjectionMembers(checker: Checker): { }); }, }, + Scalar: { + ...createBaseMembers(), + ...createNameableMembers(), + }, Union: { ...createBaseMembers(), ...createNameableMembers(), diff --git a/packages/compiler/src/core/types.ts b/packages/compiler/src/core/types.ts index 8dd2b8e58b..0f88c1e76e 100644 --- a/packages/compiler/src/core/types.ts +++ b/packages/compiler/src/core/types.ts @@ -789,6 +789,7 @@ export enum SyntaxKind { ProjectionParameterDeclaration, ProjectionModelSelector, ProjectionModelPropertySelector, + ProjectionScalarSelector, ProjectionOperationSelector, ProjectionUnionSelector, ProjectionUnionVariantSelector, @@ -902,6 +903,7 @@ export type Node = | ProjectionExpression | ProjectionModelSelectorNode | ProjectionModelPropertySelectorNode + | ProjectionScalarSelectorNode | ProjectionInterfaceSelectorNode | ProjectionOperationSelectorNode | ProjectionEnumSelectorNode @@ -1443,6 +1445,10 @@ export interface ProjectionModelSelectorNode extends BaseNode { readonly kind: SyntaxKind.ProjectionModelSelector; } +export interface ProjectionScalarSelectorNode extends BaseNode { + readonly kind: SyntaxKind.ProjectionScalarSelector; +} + export interface ProjectionModelPropertySelectorNode extends BaseNode { readonly kind: SyntaxKind.ProjectionModelPropertySelector; } @@ -1592,6 +1598,7 @@ export interface ProjectionStatementNode extends BaseNode, DeclarationNode { readonly selector: | ProjectionModelSelectorNode | ProjectionModelPropertySelectorNode + | ProjectionScalarSelectorNode | ProjectionInterfaceSelectorNode | ProjectionOperationSelectorNode | ProjectionUnionSelectorNode diff --git a/packages/compiler/src/formatter/print/printer.ts b/packages/compiler/src/formatter/print/printer.ts index dd62367aaa..1437a7e43e 100644 --- a/packages/compiler/src/formatter/print/printer.ts +++ b/packages/compiler/src/formatter/print/printer.ts @@ -255,6 +255,8 @@ export function printNode( return "model"; case SyntaxKind.ProjectionModelPropertySelector: return "modelproperty"; + case SyntaxKind.ProjectionScalarSelector: + return "scalar"; case SyntaxKind.ProjectionOperationSelector: return "op"; case SyntaxKind.ProjectionUnionSelector: diff --git a/packages/compiler/test/parser.test.ts b/packages/compiler/test/parser.test.ts index c202858733..c154e1bc1f 100644 --- a/packages/compiler/test/parser.test.ts +++ b/packages/compiler/test/parser.test.ts @@ -824,7 +824,7 @@ describe("compiler: parser", () => { describe("projections", () => { describe("selectors", () => { - const selectors = ["model", "op", "interface", "union", "someId"]; + const selectors = ["model", "op", "interface", "union", "scalar", "someId"]; const codes = selectors.map((s) => `projection ${s}#tag { }`); parseEach(codes); }); @@ -892,7 +892,10 @@ describe("compiler: parser", () => { describe("recovery", () => { parseErrorEach([ - [`projection `, [/identifier, 'model', 'op', 'interface', 'union', or 'enum' expected/]], + [ + `projection `, + [/identifier, 'model', 'op', 'interface', 'union', 'enum', or 'scalar' expected./], + ], [`projection x `, [/'#' expected/]], [`projection x#`, [/Identifier expected/]], [`projection x#f`, [/'{' expected/]], diff --git a/packages/versioning/lib/versioning.tsp b/packages/versioning/lib/versioning.tsp index 12d7c5b028..e9a0c5f1cb 100644 --- a/packages/versioning/lib/versioning.tsp +++ b/packages/versioning/lib/versioning.tsp @@ -237,3 +237,30 @@ projection enummember#v { }; } } + +#suppress "projections-are-experimental" +projection scalar#v { + pre to(version) { + if !existsAtVersion(self, version) { + return never; + }; + } + to(version) { + if hasDifferentNameAtVersion(self, version) { + self::rename(getNameAtVersion(self, version)); + }; + if hasDifferentReturnTypeAtVersion(self, version) { + self::changeReturnType(getReturnTypeBeforeVersion(self, version)); + }; + } + pre from(version) { + if !existsAtVersion(self, version) { + return never; + }; + } + from(version) { + if hasDifferentNameAtVersion(self, version) { + self::rename(self::projectionBase::name); + }; + } +} diff --git a/packages/versioning/src/versioning.ts b/packages/versioning/src/versioning.ts index 13d1994e2b..9eefb0d044 100644 --- a/packages/versioning/src/versioning.ts +++ b/packages/versioning/src/versioning.ts @@ -707,6 +707,7 @@ export function getVersions(p: Program, t: Type): [Namespace, VersionMap] | [] { t.kind === "Interface" || t.kind === "Model" || t.kind === "Union" || + t.kind === "Scalar" || t.kind === "Enum" ) { if (t.namespace) { diff --git a/packages/versioning/test/versioning.test.ts b/packages/versioning/test/versioning.test.ts index 177a4c4e77..f955bc4430 100644 --- a/packages/versioning/test/versioning.test.ts +++ b/packages/versioning/test/versioning.test.ts @@ -1829,6 +1829,59 @@ describe("versioning: logic", () => { } }); + describe("scalars", () => { + it("can be renamed", async () => { + const { + projections: [v1, v2], + } = await versionedScalar( + ["v1", "v2"], + ` + @renamedFrom(Versions.v2, "oldTest") + scalar test;` + ); + + strictEqual(v1.name, "oldTest"); + strictEqual(v2.name, "test"); + }); + + it("can be added", async () => { + const { + projections: [v1, v2], + } = await versionedScalar(["v1", "v2"], `@added(Versions.v2) scalar test;`); + strictEqual(v1.kind, "Intrinsic"); + strictEqual((v1 as any as IntrinsicType).name, "never"); + strictEqual(v2.kind, "Scalar"); + }); + + it("can be removed", async () => { + const { + projections: [v1, v2], + } = await versionedScalar(["v1", "v2"], `@removed(Versions.v2) scalar test;`); + + strictEqual(v1.kind, "Scalar"); + strictEqual(v2.kind, "Intrinsic"); + strictEqual((v2 as any as IntrinsicType).name, "never"); + }); + + async function versionedScalar(versions: string[], scalarCode: string) { + const { test } = (await runner.compile(` + @versioned(Versions) + namespace MyService; + + enum Versions { ${versions.map((t) => JSON.stringify(t)).join(" , ")} } + + @test ${scalarCode} + `)) as { test: Scalar }; + + return { + source: test, + projections: versions.map((v) => { + return project(test, v); + }), + }; + } + }); + function assertModelProjectsTo(types: [Model, string][], target: Model) { types.forEach(([m, version]) => { const projection = project(m, version, "from");