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

Fix: Various issue around parent types and sourceProperty in checker and projections #1115

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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,20 @@
{
"changes": [
{
"packageName": "@cadl-lang/compiler",
"comment": "Fix: Property included via `model is` were not referencing the right model parent.",
"type": "patch"
},
{
"packageName": "@cadl-lang/compiler",
"comment": "Fix: Projected types point to projected parent type for Model properties, Union variants.",
"type": "patch"
},
{
"packageName": "@cadl-lang/compiler",
"comment": "Fix: Projected model property sourceProperty point to projected property",
"type": "patch"
}
],
"packageName": "@cadl-lang/compiler"
}
5 changes: 3 additions & 2 deletions packages/compiler/core/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1921,8 +1921,9 @@ export function createChecker(program: Program): Checker {
for (const prop of isBase.properties.values()) {
type.properties.set(
prop.name,
finishType({
...prop,
cloneType(prop, {
sourceProperty: prop,
model: type,
})
);
}
Expand Down
27 changes: 22 additions & 5 deletions packages/compiler/core/projector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
getParentTemplateNode,
isNeverIndexer,
isProjectedProgram,
isTemplateDeclaration,
isTemplateInstance,
ProjectedProgram,
} from "./index.js";
Expand Down Expand Up @@ -163,7 +164,7 @@ export function createProjector(
}
}
}
function projectNamespace(ns: Namespace, projectSubNamespace: boolean = true): Type {
function projectNamespace(ns: Namespace, projectSubNamespace: boolean = true): Namespace {
const alreadyProjected = projectedTypes.get(ns) as Namespace;
if (alreadyProjected) {
if (projectSubNamespace) {
Expand Down Expand Up @@ -201,7 +202,7 @@ export function createProjector(
}

projectedNamespaces.push(ns);
return applyProjection(ns, projectedNs);
return applyProjection(ns, projectedNs) as Namespace;
}

/**
Expand Down Expand Up @@ -255,6 +256,10 @@ export function createProjector(
}

function projectModel(model: Model): Type {
if (model.name === "Foo") {
timotheeguerin marked this conversation as resolved.
Show resolved Hide resolved
console.log("Project model", model.name, isTemplateDeclaration(model));
console.log("");
}
const properties = new Map<string, ModelProperty>();
let templateArguments: Type[] | undefined;

Expand All @@ -279,7 +284,7 @@ export function createProjector(
projectedModel.indexer = { key: neverType, value: undefined };
} else {
projectedModel.indexer = {
key: projectModel(model.indexer.key),
key: projectModel(model.indexer.key) as Model,
value: projectType(model.indexer.value),
};
}
Expand Down Expand Up @@ -324,11 +329,21 @@ export function createProjector(
const projectedType = projectType(prop.type);
const projectedDecs = projectDecorators(prop.decorators);

const projectedProp = shallowClone(prop, {
const projectedProp: ModelProperty = shallowClone(prop, {
type: projectedType,
decorators: projectedDecs,
});

if (prop.model) {
const parentModel = projectType(prop.model) as Model;
nguerrera marked this conversation as resolved.
Show resolved Hide resolved
projectedProp.model = parentModel;
}

if (prop.sourceProperty) {
const sourceProperty = projectType(prop.sourceProperty) as ModelProperty;
projectedProp.sourceProperty = sourceProperty;
}

if (shouldFinishType(prop)) {
finishTypeForProgram(projectedProgram, projectedProp);
}
Expand Down Expand Up @@ -410,6 +425,8 @@ export function createProjector(
decorators: projectedDecs,
});

const parentUnion = projectType(variant.union) as Union;
projectedVariant.union = parentUnion;
finishTypeForProgram(projectedProgram, projectedVariant);
return projectedVariant;
}
Expand Down Expand Up @@ -557,7 +574,7 @@ export function createProjector(
return projectedType;
}

function shallowClone<T extends Type>(type: T, additionalProps: Partial<T>) {
function shallowClone<T extends Type>(type: T, additionalProps: Partial<T>): T {
const scopeProps: any = {};
if ("namespace" in type && type.namespace !== undefined) {
scopeProps.namespace = projectedNamespaceScope();
Expand Down
116 changes: 66 additions & 50 deletions packages/compiler/test/checker/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,65 +162,81 @@ describe("compiler: models", () => {
]);
});

it("provides parent model of properties", async () => {
testHost.addCadlFile(
"main.cadl",
`
@test
describe("link model with its properties", () => {
it("provides parent model of properties", async () => {
testHost.addCadlFile(
"main.cadl",
`
@test
model A {
pA: int32;
}

@test
model B {
pB: int32;

}
`
);

const { A, B } = (await testHost.compile("./")) as { A: Model; B: Model };

strictEqual(A.properties.get("pA")?.model, A);
strictEqual(B.properties.get("pB")?.model, B);
});

it("property merged via intersection", async () => {
testHost.addCadlFile(
"main.cadl",
`
model A {
pA: int32;
a: string;
}

@test
model B {
pB: int32;

b: string;
}

@test
model C {
pC: int32;
}
@test model Test {prop: A & B}
`
);
const { Test } = (await testHost.compile("main.cadl")) as { Test: Model };
const AB = Test.properties.get("prop")?.type;

strictEqual(AB?.kind, "Model" as const);
strictEqual(AB.properties.get("a")?.model, AB);
strictEqual(AB.properties.get("b")?.model, AB);
});

@test
model D {
...A,
pD: B & C;
it("property copied via spread", async () => {
testHost.addCadlFile(
"main.cadl",
`
model Foo {
prop: string;
}

@test model Test {...Foo}
`
);
);
const { Test } = (await testHost.compile("main.cadl")) as { Test: Model };
strictEqual(Test.properties.get("prop")?.model, Test);
});

it("property copied via `is`", async () => {
testHost.addCadlFile(
"main.cadl",
`
model Foo {
prop: string;
}

const { A, B, C, D } = await testHost.compile("./");

strictEqual(A.kind, "Model" as const);
strictEqual(A.properties.size, 1);
const pA = A.properties.get("pA");
strictEqual(pA?.model, A);

strictEqual(B.kind, "Model" as const);
strictEqual(B.properties.size, 1);
const pB = B.properties.get("pB");
strictEqual(pB?.model, B);

strictEqual(C.kind, "Model" as const);
strictEqual(C.properties.size, 1);
const pC = C.properties.get("pC");
strictEqual(pC?.model, C);

strictEqual(D.kind, "Model" as const);
strictEqual(D.properties.size, 2);
const pA_of_D = D.properties.get("pA");
const pD = D.properties.get("pD");
strictEqual(pA_of_D?.model, D);
strictEqual(pD?.model, D);

const BC = pD.type;
strictEqual(BC.kind, "Model" as const);
strictEqual(BC.properties.size, 2);
const pB_of_BC = BC.properties.get("pB");
const pC_of_BC = BC.properties.get("pC");
strictEqual(pB_of_BC?.model, BC);
strictEqual(pC_of_BC?.model, BC);
@test model Test is Foo;
`
);
const { Test } = (await testHost.compile("main.cadl")) as { Test: Model };
strictEqual(Test.properties.get("prop")?.model, Test);
});
});

describe("with extends", () => {
Expand Down
71 changes: 68 additions & 3 deletions packages/compiler/test/checker/projection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,37 @@ describe("compiler: projections", () => {
});

describe("models", () => {
it("link projected model to projected properties", async () => {
const code = `
@test model Foo {
name: string;
}
#suppress "projections-are-experimental"
projection model#test {to {}}`;
const result = (await testProjection(code)) as Model;
ok(result.projectionBase);
strictEqual(result.properties.get("name")?.model, result);
});

it("link projected property with sourceProperty", async () => {
const code = `
model Bar {
name: string;
}
@test model Foo {
...Bar
}
#suppress "projections-are-experimental"
projection model#test {to {}}`;
const Foo = (await testProjection(code)) as Model;
ok(Foo.projectionBase);
ok(Foo.properties.get("name")?.sourceProperty);
strictEqual(
Foo.properties.get("name")?.sourceProperty,
Foo.namespace!.models.get("Bar")?.properties.get("name")
);
});

it("works for versioning", async () => {
const addedOnKey = Symbol("addedOn");
const removedOnKey = Symbol("removedOn");
Expand Down Expand Up @@ -512,6 +543,18 @@ describe("compiler: projections", () => {
strictEqual((variant.type as Model).name, typeName);
}

it("link projected model to projected properties", async () => {
const code = `
@test union Foo {
one: {};
}
#suppress "projections-are-experimental"
projection model#test {to {}}`;
const result = (await testProjection(code)) as Union;
ok(result.projectionBase);
strictEqual(result.variants.get("one")?.union, result);
});

it("can rename itself", async () => {
const code = `
${unionCode}
Expand Down Expand Up @@ -638,6 +681,18 @@ describe("compiler: projections", () => {
${projectionCode(body)}
`;

it("link projected interfaces to its projected operations", async () => {
const code = `
@test interface Foo {
op test(): string;
}
#suppress "projections-are-experimental"
projection interface#test {to {}}`;
const result = (await testProjection(code)) as Interface;
ok(result.projectionBase);
strictEqual(result.operations.get("test")?.interface, result);
});

it("can rename itself", async () => {
const code = `
${interfaceCode}
Expand Down Expand Up @@ -692,6 +747,14 @@ describe("compiler: projections", () => {
${projectionCode(body)}
`;

it("link projected enum to projected members", async () => {
const code = defaultCode("");
const result = (await testProjection(code)) as Enum;
ok(result.projectionBase);
strictEqual(result.members.get("one")?.enum, result);
strictEqual(result.members.get("two")?.enum, result);
});

it("can rename itself", async () => {
const code = `
${enumCode}
Expand Down Expand Up @@ -852,7 +915,9 @@ describe("compiler: projections", () => {
let run = 0;

testHost.addJsFile("mark.js", {
$mark: () => run++,
$mark: () => {
run++;
},
});

testHost.addCadlFile(
Expand Down Expand Up @@ -889,7 +954,7 @@ describe("compiler: projections", () => {
prop: string;
}

model Instance is Foo<string>;
model Instance {prop: Foo<string>};
`,
1
);
Expand All @@ -902,7 +967,7 @@ describe("compiler: projections", () => {
@mark(T)
prop: string;
}
model Instance is Foo<string>;
model Instance {prop: Foo<string>};
`,
1
);
Expand Down