Skip to content
Open
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
21 changes: 21 additions & 0 deletions internal/reference/reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,27 @@ func (r *Resolver[T, F]) Alias(name string, a T) error {
return nil
}

// UpdateAlias updates an existing alias in any scope with a new value. This is used to update
// source aliases with iteration-specific values while maintaining the persistent scope structure.
// The alias must already exist in some scope.
func (r *Resolver[T, F]) UpdateAlias(name string, a T) error {
if len(r.aliases) == 0 {
return errors.New("internal error - no scope available for alias update")
}

aKey := aliasKey{r.currLib, name}

// Search from innermost to outermost scope to find the alias
for i := len(r.aliases) - 1; i >= 0; i-- {
if _, exists := r.aliases[i][aKey]; exists {
r.aliases[i][aKey] = a
return nil
}
}

return fmt.Errorf("alias %s does not exist in any scope", name)
}

// PublicDefs returns the public definitions stored in the reference resolver.
func (r *Resolver[T, F]) PublicDefs() (map[result.LibKey]map[string]T, error) {
pDefs := make(map[result.LibKey]map[string]T)
Expand Down
43 changes: 43 additions & 0 deletions internal/reference/reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,49 @@ func buildLibrary(t *testing.T, r *Resolver[model.IExpression, model.IExpression
}
}

func TestUpdateAlias(t *testing.T) {
r := NewResolver[string, string]()
r.SetCurrentUnnamed()
r.EnterScope()

// Create initial alias
if err := r.Alias("A", "initial_value"); err != nil {
t.Fatalf("Alias(A) returned unexpected error: %v", err)
}

// Verify initial value
got, err := r.ResolveLocal("A")
if err != nil {
t.Errorf("ResolveLocal(A) returned unexpected error: %v", err)
}
if got != "initial_value" {
t.Errorf("ResolveLocal(A) = %v, want %v", got, "initial_value")
}

// Update alias
if err := r.UpdateAlias("A", "updated_value"); err != nil {
t.Fatalf("UpdateAlias(A) returned unexpected error: %v", err)
}

// Verify updated value
got, err = r.ResolveLocal("A")
if err != nil {
t.Errorf("ResolveLocal(A) returned unexpected error: %v", err)
}
if got != "updated_value" {
t.Errorf("ResolveLocal(A) = %v, want %v", got, "updated_value")
}

// Test error case - update non-existent alias
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind making this it's own test case?

err = r.UpdateAlias("NonExistent", "value")
if err == nil {
t.Errorf("UpdateAlias(NonExistent) expected error but got success")
}
if !strings.Contains(err.Error(), "does not exist") {
t.Errorf("UpdateAlias error should contain 'does not exist', got: %v", err)
}
}

func newFHIRModelInfo(t *testing.T) *modelinfo.ModelInfos {
t.Helper()
fhirMIBytes, err := embeddata.ModelInfos.ReadFile("third_party/cqframework/fhir-modelinfo-4.0.1.xml")
Expand Down
5 changes: 5 additions & 0 deletions interpreter/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@ func (i *interpreter) evalAliasRef(a *model.AliasRef) (result.Value, error) {
}

func (i *interpreter) evalIdentifierRef(r *model.IdentifierRef) (result.Value, error) {
// First try to resolve as a local reference (alias, variable, etc.)
if val, err := i.refs.ResolveLocal(r.Name); err == nil {
return val, nil
}

obj, err := i.refs.ScopedStruct()
if err != nil {
return result.Value{}, err
Expand Down
Loading