Implement typed module contracts and conformance bindings
This commit is contained in:
@@ -14,7 +14,7 @@ export async function scaffoldComponent(
|
||||
throw Error("Component scaffold requires a PascalCase name, ID and QX declaration");
|
||||
const source = `package Draft id "draft" revision "draft@1" { ${spec.declaration} }`;
|
||||
const syntax = parseQx(source);
|
||||
const declarations = [...walkSyntax(syntax.root)].filter((node) => node.kind === "componentDecl");
|
||||
const declarations = [...walkSyntax(syntax.root)].filter((node) => node.kind === "moduleExport");
|
||||
const pkg = syntax.root.children.find((node) => node.kind === "packageResourceDecl");
|
||||
if (
|
||||
syntax.diagnostics.length ||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,8 +1,8 @@
|
||||
EDIT=1
|
||||
COMPONENT=2
|
||||
PRESENTATION=3
|
||||
SUBJECT=4
|
||||
RUNTIME=5
|
||||
MODULE=2
|
||||
CONTRACT=3
|
||||
PRESENTATION=4
|
||||
SUBJECT=5
|
||||
FOR=6
|
||||
CREATION=7
|
||||
INITIALIZE=8
|
||||
@@ -152,10 +152,10 @@ LINE_COMMENT=151
|
||||
BLOCK_COMMENT=152
|
||||
WS=153
|
||||
'edit'=1
|
||||
'component'=2
|
||||
'presentation'=3
|
||||
'subject'=4
|
||||
'runtime'=5
|
||||
'module'=2
|
||||
'contract'=3
|
||||
'presentation'=4
|
||||
'subject'=5
|
||||
'for'=6
|
||||
'creation'=7
|
||||
'initialize'=8
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,8 +1,8 @@
|
||||
EDIT=1
|
||||
COMPONENT=2
|
||||
PRESENTATION=3
|
||||
SUBJECT=4
|
||||
RUNTIME=5
|
||||
MODULE=2
|
||||
CONTRACT=3
|
||||
PRESENTATION=4
|
||||
SUBJECT=5
|
||||
FOR=6
|
||||
CREATION=7
|
||||
INITIALIZE=8
|
||||
@@ -152,10 +152,10 @@ LINE_COMMENT=151
|
||||
BLOCK_COMMENT=152
|
||||
WS=153
|
||||
'edit'=1
|
||||
'component'=2
|
||||
'presentation'=3
|
||||
'subject'=4
|
||||
'runtime'=5
|
||||
'module'=2
|
||||
'contract'=3
|
||||
'presentation'=4
|
||||
'subject'=5
|
||||
'for'=6
|
||||
'creation'=7
|
||||
'initialize'=8
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,7 @@ import { TypeArgumentsContext } from "./QuixosCapabilityParser.js";
|
||||
import { TypeArgumentContext } from "./QuixosCapabilityParser.js";
|
||||
import { TypeAliasDeclContext } from "./QuixosCapabilityParser.js";
|
||||
import { InterfaceMemberContext } from "./QuixosCapabilityParser.js";
|
||||
import { ModuleMemberContext } from "./QuixosCapabilityParser.js";
|
||||
import { OperationMemberContext } from "./QuixosCapabilityParser.js";
|
||||
import { ValueMemberContext } from "./QuixosCapabilityParser.js";
|
||||
import { ValueMemberOperationContext } from "./QuixosCapabilityParser.js";
|
||||
@@ -29,7 +30,7 @@ import { RelationshipOperationContext } from "./QuixosCapabilityParser.js";
|
||||
import { TargetConstraintContext } from "./QuixosCapabilityParser.js";
|
||||
import { PackageResourceDeclContext } from "./QuixosCapabilityParser.js";
|
||||
import { PackageExportContext } from "./QuixosCapabilityParser.js";
|
||||
import { ComponentDeclContext } from "./QuixosCapabilityParser.js";
|
||||
import { ModuleExportContext } from "./QuixosCapabilityParser.js";
|
||||
import { PresentationDeclContext } from "./QuixosCapabilityParser.js";
|
||||
import { PackageQueryContext } from "./QuixosCapabilityParser.js";
|
||||
import { PackageQuerySpecializationContext } from "./QuixosCapabilityParser.js";
|
||||
@@ -56,6 +57,7 @@ import { EdgeDeclContext } from "./QuixosCapabilityParser.js";
|
||||
import { EdgeEndpointContext } from "./QuixosCapabilityParser.js";
|
||||
import { ConformanceDeclContext } from "./QuixosCapabilityParser.js";
|
||||
import { ConformanceItemContext } from "./QuixosCapabilityParser.js";
|
||||
import { ModuleBindingDeclContext } from "./QuixosCapabilityParser.js";
|
||||
import { InitializationDeclContext } from "./QuixosCapabilityParser.js";
|
||||
import { InitializationFieldContext } from "./QuixosCapabilityParser.js";
|
||||
import { CreationOwnerMappingContext } from "./QuixosCapabilityParser.js";
|
||||
@@ -199,6 +201,12 @@ export class QuixosCapabilityVisitor<Result> extends AbstractParseTreeVisitor<Re
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitInterfaceMember?: (ctx: InterfaceMemberContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosCapabilityParser.moduleMember`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitModuleMember?: (ctx: ModuleMemberContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosCapabilityParser.operationMember`.
|
||||
* @param ctx the parse tree
|
||||
@@ -254,11 +262,11 @@ export class QuixosCapabilityVisitor<Result> extends AbstractParseTreeVisitor<Re
|
||||
*/
|
||||
visitPackageExport?: (ctx: PackageExportContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosCapabilityParser.componentDecl`.
|
||||
* Visit a parse tree produced by `QuixosCapabilityParser.moduleExport`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitComponentDecl?: (ctx: ComponentDeclContext) => Result;
|
||||
visitModuleExport?: (ctx: ModuleExportContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosCapabilityParser.presentationDecl`.
|
||||
* @param ctx the parse tree
|
||||
@@ -415,6 +423,12 @@ export class QuixosCapabilityVisitor<Result> extends AbstractParseTreeVisitor<Re
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitConformanceItem?: (ctx: ConformanceItemContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosCapabilityParser.moduleBindingDecl`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitModuleBindingDecl?: (ctx: ModuleBindingDeclContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosCapabilityParser.initializationDecl`.
|
||||
* @param ctx the parse tree
|
||||
|
||||
@@ -642,7 +642,19 @@ const lowerInterface = (
|
||||
source: SourceRevision,
|
||||
): InterfaceRevision => {
|
||||
const alias = identifier(context.identifier());
|
||||
const members = context.interfaceMember().flatMap((entry) => {
|
||||
const members = context.interfaceMember().flatMap<InterfaceMember>((entry) => {
|
||||
const module = entry.moduleMember();
|
||||
if (module)
|
||||
return [
|
||||
{
|
||||
kind: "module" as const,
|
||||
id: capabilityId.member(stringValue(module.stringLiteral(0))),
|
||||
displayName: identifier(module.identifier()),
|
||||
contract: stringValue(module.stringLiteral(1)),
|
||||
...(module.targetConstraint() ? { subject: state.types.target(module.targetConstraint()!) } : {}),
|
||||
operations: [],
|
||||
},
|
||||
];
|
||||
const value = entry.valueMember();
|
||||
if (value) {
|
||||
return [lowerValueMember(state, value)];
|
||||
@@ -745,6 +757,16 @@ const lowerInterfaceTemplate = (
|
||||
const members: InterfaceMember<ValueTypeExpression, ObjectTypeExpression>[] = context
|
||||
.interfaceMember()
|
||||
.map((entry) => {
|
||||
const module = entry.moduleMember();
|
||||
if (module)
|
||||
return {
|
||||
kind: "module" as const,
|
||||
id: capabilityId.member(stringValue(module.stringLiteral(0))),
|
||||
displayName: identifier(module.identifier()),
|
||||
contract: stringValue(module.stringLiteral(1)),
|
||||
...(module.targetConstraint() ? { subject: types.targetExpression(module.targetConstraint()!) } : {}),
|
||||
operations: [],
|
||||
};
|
||||
const value = entry.valueMember();
|
||||
if (value) {
|
||||
const type = types.expression(value.valueType());
|
||||
@@ -1333,23 +1355,26 @@ const lowerPackage = (
|
||||
}),
|
||||
);
|
||||
const genericExports: GenericPackageExport[] = [];
|
||||
const components: NonNullable<PackageRevision["components"]> = [];
|
||||
const modules: NonNullable<PackageRevision["modules"]> = [];
|
||||
const queries: QueryDeclaration[] = [];
|
||||
const queryTemplates: QueryTemplate[] = [];
|
||||
const exports = context.packageExport().flatMap((exportContext): PackageExport[] => {
|
||||
const component = exportContext.componentDecl();
|
||||
if (component) {
|
||||
const runtime = identifier(component.identifier(1));
|
||||
if (runtime !== "react")
|
||||
loweringIssue(state, component, "unsupported-component-runtime", `Unsupported component runtime ${runtime}`);
|
||||
components.push({
|
||||
id: stringValue(component.stringLiteral()),
|
||||
displayName: identifier(component.identifier(0)),
|
||||
subject: new TypeSubstitution(state.types.environment()).application(
|
||||
state.types.interface(component.interfaceType()),
|
||||
),
|
||||
runtime: "react",
|
||||
});
|
||||
const module = exportContext.moduleExport();
|
||||
if (module) {
|
||||
const interfaceRevisionId = new TypeSubstitution(state.types.environment()).application(
|
||||
state.types.interface(module.interfaceType()),
|
||||
);
|
||||
const contract = state.types.definitions.get(interfaceRevisionId);
|
||||
const member = contract?.members.find((m) => m.displayName === identifier(module.identifier(1)));
|
||||
if (member?.kind !== "module")
|
||||
loweringIssue(state, module, "invalid-module-export", "Module export must implement an imported module member");
|
||||
else
|
||||
modules.push({
|
||||
id: stringValue(module.stringLiteral()),
|
||||
displayName: identifier(module.identifier(0)),
|
||||
interfaceRevisionId,
|
||||
memberId: member.id,
|
||||
});
|
||||
return [];
|
||||
}
|
||||
if (exportContext.packageQuerySpecialization()) return [];
|
||||
@@ -1611,7 +1636,7 @@ const lowerPackage = (
|
||||
displayName: alias,
|
||||
source,
|
||||
exports,
|
||||
...(components.length ? { components } : {}),
|
||||
...(modules.length ? { modules } : {}),
|
||||
...(queries.length ? { queries } : {}),
|
||||
...(queryTemplates.length ? { queryTemplates } : {}),
|
||||
...(queries.some((query) => query.argumentRequirements?.length)
|
||||
@@ -1931,6 +1956,25 @@ const lowerConformance = (
|
||||
state.types.self = previousSelf;
|
||||
}
|
||||
}
|
||||
const moduleBindings: NonNullable<WorkspaceRevision["conformances"][number]["moduleBindings"]> = [];
|
||||
for (const item of context.conformanceItem()) {
|
||||
const binding = item.moduleBindingDecl();
|
||||
if (!binding) continue;
|
||||
const memberName = identifier(binding.identifier(0));
|
||||
const member = interfaceSymbol.definition?.members.find((m) => m.displayName === memberName);
|
||||
const pkg = requireSymbol(state, state.packages, identifier(binding.identifier(1)), binding, "package");
|
||||
const module = pkg?.definition?.modules?.find((m) => m.displayName === identifier(binding.identifier(2)));
|
||||
if (member?.kind !== "module" || !module || !pkg) {
|
||||
loweringIssue(
|
||||
state,
|
||||
binding,
|
||||
"invalid-module-binding",
|
||||
"Expected a module member bound to an imported module export",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
moduleBindings.push({ memberId: member.id, packageRevisionId: pkg.revisionId, moduleId: module.id });
|
||||
}
|
||||
const operationBindings = context
|
||||
.conformanceItem()
|
||||
.flatMap<WorkspaceRevision["conformances"][number]["operationBindings"][number]>((item) => {
|
||||
@@ -2182,6 +2226,7 @@ const lowerConformance = (
|
||||
...(context.INTEGER() ? { semanticMajor: Number(context.INTEGER()!.getText()) } : {}),
|
||||
privateAttachments,
|
||||
operationBindings,
|
||||
...(moduleBindings.length ? { moduleBindings } : {}),
|
||||
relationshipMaterializations,
|
||||
};
|
||||
};
|
||||
@@ -2420,7 +2465,7 @@ const lowerWorkspace = (
|
||||
const packageName = identifier(presentation.identifier(1));
|
||||
const componentName = identifier(presentation.identifier(2));
|
||||
const pkg = requireSymbol(state, state.packages, packageName, presentation, "package");
|
||||
const component = pkg?.definition?.components?.find((entry) => entry.displayName === componentName);
|
||||
const component = pkg?.definition?.modules?.find((entry) => entry.displayName === componentName);
|
||||
if (!component) {
|
||||
loweringIssue(state, presentation, "unknown-component", `Unknown component ${packageName}.${componentName}`);
|
||||
return [];
|
||||
@@ -2692,7 +2737,7 @@ const compileCapabilityResourceSourceInternal = (
|
||||
externalInterfaces,
|
||||
revision: lowerPackage(state, context, options.source),
|
||||
};
|
||||
if (resource.revision.components?.length) {
|
||||
if (resource.revision.modules?.length) {
|
||||
resource.revision.clientInterfaces = Object.fromEntries(
|
||||
imports.flatMap((entry) => {
|
||||
if (entry.kind !== "interface") return [];
|
||||
|
||||
Reference in New Issue
Block a user