Skip to content

Commit c47615a

Browse files
[raycicmd] Implicit dimension matching for array-to-array depends_on
Implement ($) resolution: when an array step depends on another array step via ($), each expanded variant depends only on the target variant with matching values on shared dimensions. Guards: - ($) from a non-array step errors (must use (*)) - ($) with no overlapping dimensions errors (must use (*)) - Value mismatch in implicit matching produces a clear error Topic: implicit-dim-matching Relative: bracket-depends-on Labels: draft Signed-off-by: andrew <andrew@anyscale.com>
1 parent fb306e4 commit c47615a

3 files changed

Lines changed: 734 additions & 8 deletions

File tree

raycicmd/array_depends.go

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,13 @@ func parseArrayDependsOnList(arr []any) ([]*arraySelector, error) {
169169
//
170170
// Selector semantics:
171171
// - No parens (literal): pass through if non-array; error if array
172-
// - ($): implicit dimension matching (not yet supported)
172+
// - ($): implicit dimension matching via overlapping dims
173173
// - (*): all variants
174174
// - (key=val): explicit filter
175175
func resolveDependsOn(
176176
dependsOn any,
177177
configs map[string]*arrayConfig,
178+
currentElem *arrayElement,
178179
) ([]string, error) {
179180
selectors, err := parseArrayDependsOn(dependsOn)
180181
if err != nil {
@@ -196,6 +197,8 @@ func resolveDependsOn(
196197
continue
197198
}
198199

200+
origMode := sel.mode
201+
199202
switch sel.mode {
200203
case selectorLiteral:
201204
return nil, fmt.Errorf(
@@ -205,17 +208,38 @@ func resolveDependsOn(
205208
)
206209

207210
case selectorImplicit:
208-
return nil, fmt.Errorf(
209-
"($) on %q: implicit matching not yet supported; use %s(*) or %s(key=val)",
210-
sel.key, sel.key, sel.key,
211-
)
211+
if currentElem == nil {
212+
return nil, fmt.Errorf(
213+
"($) on %q can only be used from an array "+
214+
"step; use %s(*) for non-array steps",
215+
sel.key, sel.key,
216+
)
217+
}
218+
f := implicitDimFilter(currentElem, cfg)
219+
if f == nil {
220+
return nil, fmt.Errorf(
221+
"($) on %q: no overlapping dimensions; "+
222+
"use %s(*) to depend on all variants",
223+
sel.key, sel.key,
224+
)
225+
}
226+
sel = &arraySelector{
227+
key: sel.key, mode: selectorFilter, filter: f,
228+
}
212229

213230
case selectorMatchAll, selectorFilter:
214231
// use sel as-is
215232
}
216233

217234
matches, err := resolveArraySelector(sel, cfg)
218235
if err != nil {
236+
if origMode == selectorImplicit {
237+
return nil, fmt.Errorf(
238+
"implicit dimension matching on %q: %w; "+
239+
"use %s(*) to depend on all variants",
240+
sel.key, err, sel.key,
241+
)
242+
}
219243
return nil, err
220244
}
221245
result = append(result, matches...)
@@ -224,6 +248,26 @@ func resolveDependsOn(
224248
return result, nil
225249
}
226250

251+
// implicitDimFilter builds a selector filter from the overlapping
252+
// dimensions between the current element and the target config.
253+
// Returns nil if there is no overlap.
254+
func implicitDimFilter(
255+
current *arrayElement, target *arrayConfig,
256+
) map[string][]string {
257+
var filter map[string][]string
258+
for dim := range target.dims {
259+
val, ok := current.values[dim]
260+
if !ok {
261+
continue
262+
}
263+
if filter == nil {
264+
filter = make(map[string][]string)
265+
}
266+
filter[dim] = []string{val}
267+
}
268+
return filter
269+
}
270+
227271
// resolveArraySelector resolves a selector against an array config
228272
// to concrete step keys, using the final element list (post-adjustments).
229273
func resolveArraySelector(sel *arraySelector, cfg *arrayConfig) ([]string, error) {

raycicmd/array_expand.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import (
1010
// depends_on references to point to the expanded keys.
1111
func expandArraySteps(gs []*pipelineGroup) error {
1212
configs := make(map[string]*arrayConfig)
13+
// elems maps each expanded step key to its arrayElement,
14+
// used for implicit dimension matching in Pass 2.
15+
elems := make(map[string]*arrayElement)
1316

1417
// Pass 1: expand array steps into resolvedSteps.
1518
for _, g := range gs {
16-
if err := g.buildResolvedSteps(configs); err != nil {
19+
if err := g.buildResolvedSteps(configs, elems); err != nil {
1720
return fmt.Errorf("expand arrays: %w", err)
1821
}
1922
}
@@ -25,8 +28,9 @@ func expandArraySteps(gs []*pipelineGroup) error {
2528
if !ok {
2629
continue
2730
}
31+
currentElem := elems[stepKey(rs.src)]
2832
resolved, err := resolveDependsOn(
29-
dependsOn, configs,
33+
dependsOn, configs, currentElem,
3034
)
3135
if err != nil {
3236
return fmt.Errorf(
@@ -65,6 +69,7 @@ func expandArraySteps(gs []*pipelineGroup) error {
6569
// array steps into multiple entries.
6670
func (g *pipelineGroup) buildResolvedSteps(
6771
configs map[string]*arrayConfig,
72+
elems map[string]*arrayElement,
6873
) error {
6974
var result []*resolvedStep
7075

@@ -95,8 +100,10 @@ func (g *pipelineGroup) buildResolvedSteps(
95100
if err != nil {
96101
return err
97102
}
98-
for _, es := range expanded {
103+
cfg := configs[baseKey]
104+
for j, es := range expanded {
99105
result = append(result, &resolvedStep{src: es})
106+
elems[stepKey(es)] = cfg.elements[j]
100107
}
101108
}
102109

0 commit comments

Comments
 (0)