Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into nibhati-fixHeaders
Browse files Browse the repository at this point in the history
  • Loading branch information
nisha-bhatia committed Jan 8, 2025
2 parents e93d74f + 4293d23 commit be2df87
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@typespec/compiler"
---

Widen type signature of `isTemplateDeclaration` and `isTemplateDeclarationOrInstance` to accept any type and assert the return type.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
changeKind: fix
packages:
- "@typespec/compiler"
---

Enum-driven visibility decorators and projections now interact correctly.

Projections now project EnumValue values to preserve consistency with projected Enum/EnumMember types using a best-effort
strategy.
27 changes: 24 additions & 3 deletions packages/compiler/src/core/projector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,28 @@ export function createProjector(
type: Type | Value | IndeterminateEntity,
): Type | Value | IndeterminateEntity {
if (isValue(type)) {
return type;
if (type.valueKind === "EnumValue") {
// This is a hack. We project the enum itself, so we need to project enum values in order to prevent incoherence
// between projections that reference enum values and the enum/members themselves. If we project the enummember
// in the value and it results in something other than an enum member, we will give up and just return the value
// as is. Otherwise we'll return the projected member.

const projectedType = projectType(type.type);
const projectedMember = projectType(type.value);

if (projectedMember.kind === "EnumMember") {
return {
entityKind: "Value",
valueKind: "EnumValue",
type: projectedType,
value: projectedMember,
};
} else {
return type;
}
} else {
return type;
}
}
if (type.entityKind === "Indeterminate") {
return { entityKind: "Indeterminate", type: projectType(type.type) as any };
Expand Down Expand Up @@ -571,8 +592,8 @@ export function createProjector(
const args: DecoratorArgument[] = [];
for (const arg of dec.args) {
const jsValue =
typeof arg.jsValue === "object" && arg.jsValue !== null && "kind" in arg.jsValue
? projectType(arg.jsValue as any)
typeof arg.jsValue === "object" && arg.jsValue !== null && "entityKind" in arg.jsValue
? projectType(arg.jsValue as Type | Value)
: arg.jsValue;
args.push({ ...arg, value: projectType(arg.value), jsValue });
}
Expand Down
8 changes: 5 additions & 3 deletions packages/compiler/src/core/type-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export function isDeclaredType(type: Type): boolean {
* Resolve if the type is a template type declaration(Non initialized template type).
*/
export function isTemplateDeclaration(
type: TemplatedType,
type: Type,
): type is TemplatedType & { node: TemplateDeclarationNode } {
if (type.node === undefined) {
return false;
Expand All @@ -129,14 +129,16 @@ export function isTemplateDeclaration(
return (
node.templateParameters &&
node.templateParameters.length > 0 &&
type.templateMapper === undefined
(type as TemplatedType).templateMapper === undefined
);
}

/**
* Resolve if the type was created from a template type or is a template type declaration.
*/
export function isTemplateDeclarationOrInstance(type: TemplatedType): boolean {
export function isTemplateDeclarationOrInstance(
type: Type,
): type is TemplatedType & { node: TemplateDeclarationNode } {
if (type.node === undefined) {
return false;
}
Expand Down
18 changes: 16 additions & 2 deletions packages/compiler/src/core/visibility/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { compilerAssert } from "../diagnostics.js";
import type { Program } from "../program.js";
import { isProjectedProgram } from "../projected-program.js";
import type { Enum, EnumMember } from "../types.js";

/**
Expand Down Expand Up @@ -30,9 +31,22 @@ export function getLifecycleVisibilityEnum(program: Program): Enum {

compilerAssert(type!.kind === "Enum", "Expected `TypeSpec.Visibility.Lifecycle` to be an enum");

LIFECYCLE_ENUM_CACHE.set(program, type);
if (isProjectedProgram(program)) {
const projectedType = program.projector.projectType(type);

return type;
compilerAssert(
projectedType.entityKind === "Type" && projectedType.kind === "Enum",
"Expected `TypeSpec.Visibility.Lifecycle` to be an Enum (projected)",
);

LIFECYCLE_ENUM_CACHE.set(program, projectedType);

return projectedType;
} else {
LIFECYCLE_ENUM_CACHE.set(program, type);

return type;
}
}

/**
Expand Down
95 changes: 95 additions & 0 deletions packages/compiler/test/visibility.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
isVisible,
Model,
ModelProperty,
projectProgram,
removeVisibilityModifiers,
resetVisibilityModifiersForClass,
sealVisibilityModifiers,
Expand Down Expand Up @@ -1010,7 +1011,101 @@ describe("compiler: visibility core", () => {
strictEqual(idRead.type, idReadCreate.type);
});
});

describe("projection compatibility", () => {
it("allows @defaultVisibility to be used on enums that are projected", async () => {
const { Example, Bar } = (await runner.compile(`
@defaultVisibility(Example.A)
@test enum Example {
A,
B,
}
@test
model Bar {
@visibility(Example.B)
x: string;
}
#suppress "projections-are-experimental"
projection Bar#test {
to {
}
}
`)) as { Example: Enum; Bar: Model };

const projected = projectProgram(runner.program, [
{
projectionName: "test",
arguments: [],
},
]);

const visibility = getVisibilityForClass(runner.program, Bar.properties.get("x")!, Example);

strictEqual(visibility.size, 1);
ok(visibility.has(Example.members.get("B")!));

const ProjectedBar = projected.projector.projectType(Bar) as Model;
const ProjectedExample = projected.projector.projectType(Example) as Enum;

const projectedX = ProjectedBar.properties.get("x")!;

strictEqual(projectedX.decorators.length, 1);

const projectedVisibility = getVisibilityForClass(projected, projectedX, ProjectedExample);

strictEqual(projectedVisibility.size, 1);
ok(projectedVisibility.has(ProjectedExample.members.get("B")!));
});

it("correctly makes projected properties invisible", async () => {
const { Example } = (await runner.compile(`
@test
model Example {
@invisible(Lifecycle)
x: string;
}
#suppress "projections-are-experimental"
projection Example#test {
to {
}
}
`)) as { Example: Model };

const projected = projectProgram(runner.program, [
{
projectionName: "test",
arguments: [],
},
]);

const visibility = getVisibilityForClass(
runner.program,
Example.properties.get("x")!,
getLifecycleVisibilityEnum(runner.program),
);

strictEqual(visibility.size, 0);

const projectedExample = projected.projector.projectType(Example) as Model;

const projectedX = projectedExample.properties.get("x")!;

strictEqual(projectedX.decorators.length, 1);

const projectedVisibility = getVisibilityForClass(
projected,
projectedX,
getLifecycleVisibilityEnum(projected),
);

strictEqual(projectedVisibility.size, 0);
});
});
});

function validateCreateOrUpdateTransform(
props: {
c: ModelProperty | undefined;
Expand Down
11 changes: 7 additions & 4 deletions packages/http-client-python/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@

To observe the downstream effects of changes in this `@typespec/http-client-python` package on `@azure-tools/typespec-python`, we require a green PR to the [autorest.python](https://github.com/Azure/autorest.python) repo to ensure this.

To make the downstream PR, follow these steps:
The creation of this downstream PR is semi-automatic

1. Create your intended PR to the [microsoft/typespec](https://github.com/microsoft/typespec) repo for `@typespec/http-client-python`
2. After the above CI passes, you get the url of a private package in CI.
![alt text](image.png)
a. Click on the section that says `5 published; 1 consumed`, like in the above picture
b. Follow `Published artifacts -> build_artifacts_python -> packages -> typespec-http-client-python-x.x.x.tgz`.
c. Go to the right side, click the three dots, and click `Copy download url`.
3. Create a _draft_ PR in [autorest.python](https://github.com/Azure/autorest.python), updating the version of the `@typespec/http-client-python` in the `package.json` files to be the downloaded URL
4. Run `pnpm install`, and follow that repo's [CONTRIBUTING.md](https://github.com/Azure/autorest.python/blob/main/CONTRIBUTING.md) for your second PR
3. Run [this](https://dev.azure.com/azure-sdk/internal/_build/results?buildId=4278466&view=results) pipeline with the following variables
a. `PULL-REQUEST-URL` equaling the url of the PR created in step 1
b. `ARTIFACTS-URL` equaling the url you get in step 2
4. Step 3 will create a PR in [autorest.python](https://github.com/Azure/autorest.python). If you need to make any changes to code in the autorest.python repo, follow that repo's [CONTRIBUTING.md](https://github.com/Azure/autorest.python/blob/main/CONTRIBUTING.md)
5. Once the PR to [autorest.python](https://github.com/Azure/autorest.python) passes, you can merge and release the original PR
6. When the change to `@typespec/http-client-python` has been released, update your [autorest.python](https://github.com/Azure/autorest.python) repo to use the released version of the `@typespec/http-client-python` package

When the change to `@typespec/http-client-python` has been released, update your [autorest.python](https://github.com/Azure/autorest.python) repo to use the released version of the `@typespec/http-client-python` package. You will need to run `pnpm install` to make sure the dependency map is correctly set up. You are now able to release the autorest emitters with your original change.

0 comments on commit be2df87

Please sign in to comment.