Skip to content

Conversation

Copy link

Copilot AI commented Nov 22, 2025

Relative path components (.., .) in group labels were not being resolved against parent path context, causing widgets that should be shared across groups to be duplicated.

import("stdfaust.lib");

// This form worked (string literal with explicit path)
freq = vslider("../h:FREQ/Freq", 200, 200, 1000, 0.01);

// This form did not work (relative path in group)
freq = hgroup("../FREQ", vslider("Freq", 200, 200, 1000, 0.01));

process = hgroup("V1", os.osc(freq)), hgroup("V2", os.osc(freq));

Previously, hgroup("../FREQ", ...) treated "../FREQ" as a literal group name. Now both forms resolve identically, enabling proper widget sharing.

Changes

  • compiler/propagate/labels.cpp: Added normalizeGroupPath() that parses group labels for relative path components, resolves them against current path using existing concatPath() logic, and encodes the final name with group type (h/v/t). Handles edge cases: multiple .. levels, going beyond root, namespace prefixes.

  • compiler/propagate/propagate.cpp: Updated isBoxVGroup, isBoxHGroup, isBoxTGroup handlers to call normalizeGroupPath() instead of directly consing labels onto path.

  • tests/codegen-tests/: Added regression tests verifying both forms produce identical UI.

Fixes #1063

Original prompt

Problem

Relative paths used inside groups are not being resolved correctly by the label propagation pass. Example reported in issue #1063:

import("stdfaust.lib");

freq = vslider("../h:FREQ/Freq",200,200,1000,0.01);              // works (freq is shared by V1 et V2)
//freq = hgroup("../FREQ", vslider("Freq",200,200,1000,0.01));   // does not work 

process = hgroup("V1", os.osc(freq)), hgroup("V2", os.osc(freq));

In this repro the explicit path in vslider works ("../h:FREQ/Freq") but using hgroup("../FREQ", vslider("Freq",...)) does not, indicating that relative ".." segments are not being resolved against the group parent path correctly. The bug appears to be in normalizePath in compiler/propagate/labels.cpp or in the caller logic that joins parent and child paths before normalization.

Goal

Implement a correct and robust path normalization/joining strategy so relative labels (./, ../, and plain relative names) are resolved against the parent/group path, handle namespace prefixes (like "h:") properly, and add a regression unit test that reproduces and guards against future regressions.

Scope of changes

  • Modify compiler/propagate/labels.cpp to ensure normalization happens after joining base (parent) and child paths. Improve normalizePath so it:

    • Accepts a joined path or (optionally) a base and relative path.
    • Properly splits path segments and handles '.' and '..' segments.
    • Preserves namespace prefixes like 'h:' (if present at the head of a segment) — normalization should treat prefixes as part of segment names, not path separators.
    • Safely handles attempts to go above the root (leading '..'s): preserve them or follow project conventions (prefer preserving leading '..' if parent is empty).
  • Update all callers in the propagate/labels pass to join base and child path first (e.g., parent + '/' + child) and call normalizePath on the joined path, rather than normalizing child in isolation.

  • Add a regression test under tests/ (or the project test harness) that compiles the minimal repro and asserts that the resolved label for the vslider inside hgroup("../FREQ", vslider("Freq", ...)) matches the expected canonical path used by the working string literal case. The test should check that both forms resolve to the same resolved label and that grouping sharing works as the working case demonstrates.

Implementation details (actionable)

  1. In compiler/propagate/labels.cpp:

    • Locate normalizePath implementation and its callers in the file.
    • Add a small helper joinPaths(parent, child) that returns the concatenation with a single separator and handles empty parent or child.
    • Change call sites so that when a node has a parent path and a relative child label, we call normalizePath(joinPaths(parentPath, childLabel)).
    • Implement normalizePath to:
      • Split by '/'. For each segment:
        • If segment == '' or segment == '.', skip it (but be careful to preserve leading empty for absolute paths if project uses them).
        • If segment == '..' then pop the last segment from the result stack if available, otherwise push '..' to the front (preserve leading .. if we have nothing to pop).
        • Otherwise push the segment.
      • Rejoin using '/'.
      • Make sure namespace prefixes that include ':' are part of the segment and not split by ':' (do not split on ':').
    • Add unit tests (see below) that exercise edge cases: multiple '../', './', mixing namespace-prefixed segments ("h:FREQ"), empty parent, absolute vs relative.
  2. Tests

    • Add a regression test file tests/labels/relative_group_paths.dsp (or appropriate C++ unit test using existing test framework) that compiles the example from the issue and asserts matching resolved labels.
    • If the project has an existing unit test runner for propagation passes, add a new test that runs the label propagation pass and inspects the output AST or serialized label names.
  3. Build and run test suite to ensure no regressions.

Expected behavior

  • After changes, both forms in the repro should produce the same resolved label for the slider inside the group, and label-sharing across groups should behave as expected.

Notes and constraints

  • I will not change public behavior for labels that are already absolute. Absolute paths should continue to be normalized independently.
  • If there is an existing canonical format for labels (for instance, always lowercased or always keeping 'h:' prefix in a certain position), the implementation will adhere to it; otherwise normalization will only touch path separators and '.'/'..' handling.

Files likely to change

  • compiler/propagate/labels.cpp
  • tests/... (add new regression test file)

Deliverables

  • A new branch with the implementation and tests.
  • A pull request opened against grame-cncm/faust with base branch 'master-dev' containing:
    • The code changes to normalize/join paths correctly.
    • The regression test reproducing the issue.
    • A ...

This pull request was created as a result of the following prompt from Copilot chat.

Problem

Relative paths used inside groups are not being resolved correctly by the label propagation pass. Example reported in issue #1063:

import("stdfaust.lib");

freq = vslider("../h:FREQ/Freq",200,200,1000,0.01);              // works (freq is shared by V1 et V2)
//freq = hgroup("../FREQ", vslider("Freq",200,200,1000,0.01));   // does not work 

process = hgroup("V1", os.osc(freq)), hgroup("V2", os.osc(freq));

In this repro the explicit path in vslider works ("../h:FREQ/Freq") but using hgroup("../FREQ", vslider("Freq",...)) does not, indicating that relative ".." segments are not being resolved against the group parent path correctly. The bug appears to be in normalizePath in compiler/propagate/labels.cpp or in the caller logic that joins parent and child paths before normalization.

Goal

Implement a correct and robust path normalization/joining strategy so relative labels (./, ../, and plain relative names) are resolved against the parent/group path, handle namespace prefixes (like "h:") properly, and add a regression unit test that reproduces and guards against future regressions.

Scope of changes

  • Modify compiler/propagate/labels.cpp to ensure normalization happens after joining base (parent) and child paths. Improve normalizePath so it:

    • Accepts a joined path or (optionally) a base and relative path.
    • Properly splits path segments and handles '.' and '..' segments.
    • Preserves namespace prefixes like 'h:' (if present at the head of a segment) — normalization should treat prefixes as part of segment names, not path separators.
    • Safely handles attempts to go above the root (leading '..'s): preserve them or follow project conventions (prefer preserving leading '..' if parent is empty).
  • Update all callers in the propagate/labels pass to join base and child path first (e.g., parent + '/' + child) and call normalizePath on the joined path, rather than normalizing child in isolation.

  • Add a regression test under tests/ (or the project test harness) that compiles the minimal repro and asserts that the resolved label for the vslider inside hgroup("../FREQ", vslider("Freq", ...)) matches the expected canonical path used by the working string literal case. The test should check that both forms resolve to the same resolved label and that grouping sharing works as the working case demonstrates.

Implementation details (actionable)

  1. In compiler/propagate/labels.cpp:

    • Locate normalizePath implementation and its callers in the file.
    • Add a small helper joinPaths(parent, child) that returns the concatenation with a single separator and handles empty parent or child.
    • Change call sites so that when a node has a parent path and a relative child label, we call normalizePath(joinPaths(parentPath, childLabel)).
    • Implement normalizePath to:
      • Split by '/'. For each segment:
        • If segment == '' or segment == '.', skip it (but be careful to preserve leading empty for absolute paths if project uses them).
        • If segment == '..' then pop the last segment from the result stack if available, otherwise push '..' to the front (preserve leading .. if we have nothing to pop).
        • Otherwise push the segment.
      • Rejoin using '/'.
      • Make sure namespace prefixes that include ':' are part of the segment and not split by ':' (do not split on ':').
    • Add unit tests (see below) that exercise edge cases: multiple '../', './', mixing namespace-prefixed segments ("h:FREQ"), empty parent, absolute vs relative.
  2. Tests

    • Add a regression test file tests/labels/relative_group_paths.dsp (or appropriate C++ unit test using existing test framework) that compiles the example from the issue and asserts matching resolved labels.
    • If the project has an existing unit test runner for propagation passes, add a new test that runs the label propagation pass and inspects the output AST or serialized label names.
  3. Build and run test suite to ensure no regressions.

Expected behavior

  • After changes, both forms in the repro should produce the same resolved label for the slider inside the group, and label-sharing across groups should behave as expected.

Notes and constraints

  • I will not change public behavior for labels that are already absolute. Absolute paths should continue to be normalized independently.
  • If there is an existing canonical format for labels (for instance, always lowercased or always keeping 'h:' prefix in a certain position), the implementation will adhere to it; otherwise normalization will only touch path separators and '.'/'..' handling.

Files likely to change

  • compiler/propagate/labels.cpp
  • tests/... (add new regression test file)

Deliverables

  • A new branch with the implementation and tests.
  • A pull request opened against grame-cncm/faust with base branch 'master-dev' containing:

Please proceed to implement the changes and open the PR against master-dev. I will include the implementation, tests, and PR description in the branch and open the pull request.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI self-assigned this Nov 22, 2025
Copilot AI changed the title [WIP] Fix path resolution for labels in label propagation pass Fix relative path resolution in group labels (issue #1063) Nov 22, 2025
Copilot AI requested a review from sletz November 22, 2025 13:02
@sletz sletz marked this pull request as ready for review November 23, 2025 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Relative path in groups are not working

2 participants