Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow missing property for opExample #4499

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: fix
packages:
- "@typespec/compiler"
---

`@opExample` allow partial example to support visibility scenarios
6 changes: 6 additions & 0 deletions packages/compiler/src/core/type-relation-checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,9 @@ function wrapUnassignableErrors(
errors: readonly TypeRelationError[],
): readonly TypeRelationError[] {
const error = createUnassignableDiagnostic(source, target, source);
if (errors.length === 1) {
error.code = errors[0].code;
}
error.children = errors;
return [error];
}
Expand All @@ -983,6 +986,9 @@ function wrapUnassignablePropertyErrors(
},
skipIfFirst: true,
});
if (errors.length === 1) {
error.code = errors[0].code;
}
error.children = errors;
return [error];
}
Expand Down
10 changes: 8 additions & 2 deletions packages/compiler/src/lib/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1510,7 +1510,7 @@ export const $opExample: OpExampleDecorator = (
context: DecoratorContext,
target: Operation,
_example: unknown,
options?: unknown, // TODO: change `options?: ExampleOptions` when tspd supports it
options?: ExampleOptions,
) => {
const decorator = target.decorators.find(
(d) => d.decorator === $opExample && d.node === context.decoratorTarget,
Expand Down Expand Up @@ -1567,7 +1567,13 @@ function checkExampleValid(
diagnosticTarget,
);
if (!assignable) {
program.reportDiagnostics(diagnostics);
// We exclude missing-property diagnostic because some properties might not be present in certain protocol due to visibility.
Copy link
Member

Choose a reason for hiding this comment

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

This seems like a pretty crude approach. This will allow any opExample invocations that have missing properties to work, but we don't really want that behavior since it will allow missing properties that actually should be there because they're required and present in the visibility transform.

I think we really ought to run the visibility transform and then try to assign the value to the transformed type.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah if you feel positive that with the new visibility proposal this will just work then I think we should do that as well.
I did it this way because I planned to do that at the time of examples but forgot and there wasn't any solution for visibility at the time.

What do you think is the timeline for visibility, wondering if maybe we should just not do that as a warning to unblock those users for now at least.

const filtered = diagnostics.filter((x) => x.code !== "missing-property");
if (filtered.length === 0) {
return true;
} else {
program.reportDiagnostics(filtered);
}
}
return assignable;
}
Expand Down
21 changes: 20 additions & 1 deletion packages/compiler/test/decorators/examples.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ok } from "assert";
import { describe, expect, it } from "vitest";
import { Operation, getExamples, getOpExamples, serializeValueAsJson } from "../../src/index.js";
import { expectDiagnostics } from "../../src/testing/expect.js";
import { expectDiagnosticEmpty, expectDiagnostics } from "../../src/testing/expect.js";
import { createTestRunner } from "../../src/testing/test-host.js";

async function getExamplesFor(code: string) {
Expand Down Expand Up @@ -247,6 +247,25 @@ describe("@opExample", () => {
code: "unassignable",
});
});

it("allow missing properties", async () => {
const diagnostics = await diagnoseCode(`
model Pet { @visibility("create") password: string; name: string; }
@opExample( #{ returnType: #{ name: "Fluffy" } } )
op read(): Pet;
`);
expectDiagnosticEmpty(diagnostics);
});

it("allow missing properties (nested)", async () => {
const diagnostics = await diagnoseCode(`
model Pet { nested: { @visibility("create") password: string; name: string; } }
const pet = #{ name: "Fluffy" };
@opExample(#{ returnType: #{ nested: pet } })
op read(): Pet;
`);
expectDiagnosticEmpty(diagnostics);
});
});

describe("json serialization of examples", () => {
Expand Down
Loading