Skip to content
Merged
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
175 changes: 170 additions & 5 deletions client/src/data/templates.json
Original file line number Diff line number Diff line change
Expand Up @@ -19287,6 +19287,93 @@
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.prescribedHeatFlowRate": {
"expression": true,
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.use_C_flow": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.heatPort": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.C_flow": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.initialize_p": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.simplify_mWat_flow": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.m_flow_nominal": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.nPorts": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.m_flow_small": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.allowFlowReversal": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.V": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.ports": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.T": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.U": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.p": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.m": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.Xi": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.mXi": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.C": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.mC": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.energyDynamics": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.massDynamics": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.substanceDynamics": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.traceDynamics": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.p_start": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.T_start": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.X_start": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.C_start": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.C_nominal": {
"final": true
},
"Buildings.Fluid.HeatExchangers.HeaterCooler_u.vol.mSenFac": {
"final": true
}
},
"replaceable": false,
Expand Down Expand Up @@ -48586,6 +48673,24 @@
"expression": "ctl.stdVen",
"final": true
},
"Buildings.Templates.AirHandlersFans.VAVMultiZone.cfg.typ": {
"final": true
},
"Buildings.Templates.AirHandlersFans.VAVMultiZone.cfg.typFanSup": {
"final": true
},
"Buildings.Templates.AirHandlersFans.VAVMultiZone.cfg.typFanRet": {
"final": true
},
"Buildings.Templates.AirHandlersFans.VAVMultiZone.cfg.typFanRel": {
"final": true
},
"Buildings.Templates.AirHandlersFans.VAVMultiZone.cfg.have_souChiWat": {
"final": true
},
"Buildings.Templates.AirHandlersFans.VAVMultiZone.cfg.have_souHeaWat": {
"final": true
},
"Buildings.Templates.AirHandlersFans.VAVMultiZone.dat": {
"final": false,
"redeclare": "Buildings.Templates.AirHandlersFans.Data.VAVMultiZone"
Expand Down Expand Up @@ -52742,24 +52847,33 @@
},
"Buildings.Templates.ZoneEquipment.Interfaces.VAVBox.cfg.typCoiHea": {
"expression": "coiHea.typ",
"final": false
"final": true
},
"Buildings.Templates.ZoneEquipment.Interfaces.VAVBox.cfg.typValCoiHea": {
"expression": "coiHea.typVal",
"final": false
"final": true
},
"Buildings.Templates.ZoneEquipment.Interfaces.VAVBox.cfg.typDamVAV": {
"expression": "damVAV.typ",
"final": false
"final": true
},
"Buildings.Templates.ZoneEquipment.Interfaces.VAVBox.cfg.typCtl": {
"expression": "ctl.typ",
"final": false,
"final": true,
"recordBinding": true
},
"Buildings.Templates.ZoneEquipment.Interfaces.VAVBox.cfg.stdVen": {
"expression": "ctl.stdVen",
"final": false
"final": true
},
"Buildings.Templates.ZoneEquipment.Interfaces.VAVBox.cfg.typ": {
"final": true
},
"Buildings.Templates.ZoneEquipment.Interfaces.VAVBox.cfg.have_souChiWat": {
"final": true
},
"Buildings.Templates.ZoneEquipment.Interfaces.VAVBox.cfg.have_souHeaWat": {
"final": true
},
"Buildings.Templates.ZoneEquipment.Interfaces.VAVBox.dat": {
"final": false,
Expand Down Expand Up @@ -52883,6 +52997,57 @@
"final": true,
"redeclare": "Buildings.Templates.Components.Coils.None"
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.typ": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.typVal": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.have_sou": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.have_weaBus": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.dat": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.mAir_flow_nominal": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.dpAir_nominal": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.Q_flow_nominal": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.tau": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.energyDynamics": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.allowFlowReversalAir": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.allowFlowReversalLiq": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.port_aSou": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.port_bSou": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.busWea": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.bus": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.coiHea.datVal": {
"final": true
},
"Buildings.Templates.ZoneEquipment.VAVBoxCoolingOnly.ctl": {
"final": false,
"redeclare": "Buildings.Templates.ZoneEquipment.Components.Controls.G36VAVBoxCoolingOnly"
Expand Down
16 changes: 16 additions & 0 deletions client/tests/data/options-TestTemplate.json
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,22 @@
"TestPackage.Template.TestTemplate.redeclare_param_01.replaceable_param": {
"final": true,
"redeclare": "TestPackage.Component.SecondComponent"
},
"TestPackage.Template.TestTemplate.redeclare_param_01.replaceable_param.component_param": {
"expression": "\"From final redeclare\"",
"final": true
},
"TestPackage.Template.TestTemplate.redeclare_param_01.replaceable_param.is_another_param": {
"final": true
},
"TestPackage.Template.TestTemplate.redeclare_param_01.replaceable_param.inner_outer_param": {
"final": true
},
"TestPackage.Template.TestTemplate.redeclare_param_01.replaceable_param.container": {
"final": true
},
"TestPackage.Template.TestTemplate.redeclare_param_01.replaceable_param.icecream": {
"final": true
}
},
"replaceable": false,
Expand Down
Binary file modified client/tests/data/templates.json.gz
Binary file not shown.
63 changes: 63 additions & 0 deletions server/src/parser/modification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,53 @@ export function createModification(
* - `redeclare NewType myParam = someValue` -> redeclare="NewType", value="someValue"
* - `redeclare package Medium = NewMedium` -> redeclare="NewMedium", value=undefined
*/
/**
* A `final` redeclare locks the component to its redeclared type: nothing
* downstream can touch it again, so any bindings written inline in that same
* redeclare clause are just as unmodifiable even when they don't repeat the
* `final` keyword themselves (Buildings authors are inconsistent about this).
*/
function markModsFinal(mods: Modification[]): void {
mods.forEach((m) => {
m.final = true;
if (m.mods.length) {
markModsFinal(m.mods);
}
});
}

/**
* A `final` redeclare locks down every parameter of the redeclared type, not
* just the ones bound inline in the redeclare clause: marks the already
* present child mods final, then synthesizes an unbound `final` Modification
* for every other own/inherited parameter of `element` so those can't be
* modified downstream either.
*/
function finalizeAllParams(
element: Element | undefined,
childBasePath: string,
childMods: Modification[],
): void {
markModsFinal(childMods);

const boundNames = new Set(childMods.map((m) => m.name));
const getChildElements = (element as { getChildElements?: () => Element[] })
?.getChildElements;
const allParams =
typeof getChildElements === "function"
? getChildElements.call(element)
: [];

allParams.forEach((el) => {
const paramName = el.modelicaPath.split(".").pop() as string;
if (!boundNames.has(paramName)) {
childMods.push(
new Modification(childBasePath, paramName, undefined, [], true),
);
}
});
}

function unpackRedeclaration(props: ModificationProps) {
let { basePath, definition, baseType } = props;
let redeclaration = (definition as mj.RedeclareMod).element_redeclaration;
Expand Down Expand Up @@ -184,6 +231,14 @@ function unpackRedeclaration(props: ModificationProps) {
}
}

if (final) {
finalizeAllParams(
element,
[basePath, name].filter((s) => s).join("."),
childMods,
);
}

// The redeclared type is stored under 'redeclare' property
const redeclaredType = element.type;

Expand Down Expand Up @@ -226,6 +281,14 @@ function unpackRedeclaration(props: ModificationProps) {
);
}

if (final) {
finalizeAllParams(
aliasedType,
[basePath, name].filter((s) => s).join("."),
childMods,
);
}

return new Modification(
basePath,
name,
Expand Down
7 changes: 5 additions & 2 deletions server/src/parser/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,11 @@ export function flattenModifiers(
modList
.filter((m) => m !== undefined || m !== null)
.map((mod) => {
// Include modifiers with defined value, OR redeclare modifiers
if (mod?.value !== undefined || mod?.redeclare) {
// Include modifiers with a defined value, a redeclare, or a `final`
// flag (a `final` redeclare synthesizes unbound entries for every
// parameter of the redeclared type; those must survive with no value
// so downstream still sees them as locked)
if (mod?.value !== undefined || mod?.redeclare || mod?.final) {
mods[mod.modelicaPath] = {
expression: mod.value,
final: mod.final,
Expand Down
35 changes: 35 additions & 0 deletions server/tests/integration/parser/modifiers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,41 @@ describe("Modifications", () => {
expect(mod.expression).toBeUndefined();
});

/**
* Regression test: `redeclare final Type comp(field=value)` locks `comp`
* to its redeclared type, so nothing downstream can override `field`
* either — it must be treated as final even though only the redeclare
* itself (not `field`) carries the `final` keyword in the Modelica source.
*/
it("Propagates 'final' from a redeclare onto its own inline bindings", () => {
const path = "TestPackage.Template.TestTemplate.redeclare_param_01";
const nestedModPath = `${path}.replaceable_param.component_param`;
const option = tOptions[path];
const mod = option.modifiers[nestedModPath];

expect(mod).toBeDefined();
expect(mod.expression).toEqual('"From final redeclare"');
expect(mod.final).toBeTruthy();
});

/**
* Regression test: `redeclare final Type comp(...)` must lock down every
* parameter of `Type`, not just the ones bound inline in the redeclare
* clause — `is_another_param` has no binding in the redeclare clause but
* still belongs to the redeclared `SecondComponent` type, so it must be
* final too.
*/
it("Propagates 'final' from a redeclare onto every parameter of the redeclared type", () => {
const path = "TestPackage.Template.TestTemplate.redeclare_param_01";
const unboundModPath = `${path}.replaceable_param.is_another_param`;
const option = tOptions[path];
const mod = option.modifiers[unboundModPath];

expect(mod).toBeDefined();
expect(mod.final).toBeTruthy();
expect(mod.expression).toBeUndefined();
});

/**
* Regression test for https://github.com/lbl-srg/ctrl-flow-dev/issues/418
* Element-level modifiers on a replaceable must be captured alongside
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ model TestTemplate "Test Template"

// redclare modifier params
TestPackage.Component.FourthComponent redeclare_param_01(
redeclare final TestPackage.Component.SecondComponent replaceable_param
redeclare final TestPackage.Component.SecondComponent replaceable_param(
component_param="From final redeclare")
)
"First Param to test component redeclares"
annotation(Dialog(enable=true));
Expand Down