Skip to content

Commit eb8770f

Browse files
akoclaude
andcommitted
fix: resolve association/attribute and entity/enumeration ambiguity (#50)
Bug 1: CREATE/CHANGE OBJECT wrote associations into the Attribute field because the dot-contains heuristic failed for unqualified names like Child_Parent. New resolveMemberChange() queries the domain model to determine if a member is an association or attribute. Bug 2: DECLARE with entity types produced DataTypes$EnumerationType instead of DataTypes$ObjectType because the visitor cannot distinguish entities from enumerations. The executor now resolves this ambiguity by checking the domain model via isEntity(). Both fixes fall back gracefully when no reader is available. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cafddad commit eb8770f

File tree

1 file changed

+82
-18
lines changed

1 file changed

+82
-18
lines changed

mdl/executor/cmd_microflows_builder_actions.go

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,22 @@ import (
1616

1717
// addCreateVariableAction creates a DECLARE statement as a CreateVariableAction.
1818
func (fb *flowBuilder) addCreateVariableAction(s *ast.DeclareStmt) model.ID {
19+
// Resolve TypeEnumeration → TypeEntity ambiguity using the domain model
20+
declType := s.Type
21+
if declType.Kind == ast.TypeEnumeration && declType.EnumRef != nil && fb.reader != nil {
22+
if fb.isEntity(declType.EnumRef.Module, declType.EnumRef.Name) {
23+
declType = ast.DataType{Kind: ast.TypeEntity, EntityRef: declType.EnumRef}
24+
}
25+
}
26+
1927
// Register the variable as declared
20-
typeName := s.Type.Kind.String()
28+
typeName := declType.Kind.String()
2129
fb.declaredVars[s.Variable] = typeName
2230

2331
action := &microflows.CreateVariableAction{
2432
BaseElement: model.BaseElement{ID: model.ID(mpr.GenerateID())},
2533
VariableName: s.Variable,
26-
DataType: convertASTToMicroflowDataType(s.Type, nil), // nil resolver - DECLARE uses primitive types
34+
DataType: convertASTToMicroflowDataType(declType, nil),
2735
InitialValue: expressionToString(s.InitialValue),
2836
}
2937

@@ -102,14 +110,7 @@ func (fb *flowBuilder) addCreateObjectAction(s *ast.CreateObjectStmt) model.ID {
102110
Type: microflows.MemberChangeTypeSet,
103111
Value: expressionToString(change.Value),
104112
}
105-
// Check if this is an association (already qualified name with .) or an attribute
106-
if strings.Contains(change.Attribute, ".") {
107-
// Association: Module.AssociationName (already fully qualified)
108-
memberChange.AssociationQualifiedName = change.Attribute
109-
} else if entityQN != "" {
110-
// Attribute: Build qualified name as Module.Entity.Attribute
111-
memberChange.AttributeQualifiedName = entityQN + "." + change.Attribute
112-
}
113+
fb.resolveMemberChange(memberChange, change.Attribute, entityQN)
113114
action.InitialMembers = append(action.InitialMembers, memberChange)
114115
}
115116

@@ -258,14 +259,7 @@ func (fb *flowBuilder) addChangeObjectAction(s *ast.ChangeObjectStmt) model.ID {
258259
Type: microflows.MemberChangeTypeSet,
259260
Value: expressionToString(change.Value),
260261
}
261-
// Check if this is an association (already qualified name with .) or an attribute
262-
if strings.Contains(change.Attribute, ".") {
263-
// Association: Module.AssociationName (already fully qualified)
264-
memberChange.AssociationQualifiedName = change.Attribute
265-
} else if entityQN != "" {
266-
// Attribute: Build qualified name as Module.Entity.Attribute
267-
memberChange.AttributeQualifiedName = entityQN + "." + change.Attribute
268-
}
262+
fb.resolveMemberChange(memberChange, change.Attribute, entityQN)
269263
action.Changes = append(action.Changes, memberChange)
270264
}
271265

@@ -726,6 +720,76 @@ func (fb *flowBuilder) addRemoveFromListAction(s *ast.RemoveFromListStmt) model.
726720
return activity.ID
727721
}
728722

723+
// isEntity checks whether a qualified name refers to an entity in the domain model.
724+
func (fb *flowBuilder) isEntity(moduleName, entityName string) bool {
725+
if fb.reader == nil {
726+
return false
727+
}
728+
mod, err := fb.reader.GetModuleByName(moduleName)
729+
if err != nil || mod == nil {
730+
return false
731+
}
732+
dm, err := fb.reader.GetDomainModel(mod.ID)
733+
if err != nil || dm == nil {
734+
return false
735+
}
736+
for _, e := range dm.Entities {
737+
if e.Name == entityName {
738+
return true
739+
}
740+
}
741+
return false
742+
}
743+
744+
// resolveMemberChange determines whether a member name is an association or attribute
745+
// and sets the appropriate field on the MemberChange. It queries the domain model
746+
// to check if the name matches an association on the entity; if no reader is available,
747+
// it falls back to the dot-contains heuristic.
748+
func (fb *flowBuilder) resolveMemberChange(mc *microflows.MemberChange, memberName string, entityQN string) {
749+
if entityQN == "" {
750+
return
751+
}
752+
753+
// Split entity qualified name into module and entity
754+
parts := strings.SplitN(entityQN, ".", 2)
755+
if len(parts) != 2 {
756+
mc.AttributeQualifiedName = entityQN + "." + memberName
757+
return
758+
}
759+
moduleName := parts[0]
760+
761+
// Query domain model to check if this member is an association
762+
if fb.reader != nil {
763+
if mod, err := fb.reader.GetModuleByName(moduleName); err == nil && mod != nil {
764+
if dm, err := fb.reader.GetDomainModel(mod.ID); err == nil && dm != nil {
765+
for _, a := range dm.Associations {
766+
if a.Name == memberName {
767+
mc.AssociationQualifiedName = moduleName + "." + memberName
768+
return
769+
}
770+
}
771+
// Also check cross-associations
772+
for _, a := range dm.CrossAssociations {
773+
if a.Name == memberName {
774+
mc.AssociationQualifiedName = moduleName + "." + memberName
775+
return
776+
}
777+
}
778+
// Not an association — it's an attribute
779+
mc.AttributeQualifiedName = entityQN + "." + memberName
780+
return
781+
}
782+
}
783+
}
784+
785+
// Fallback: if already qualified (contains dot), treat as association
786+
if strings.Contains(memberName, ".") {
787+
mc.AssociationQualifiedName = memberName
788+
} else {
789+
mc.AttributeQualifiedName = entityQN + "." + memberName
790+
}
791+
}
792+
729793
// assocLookupResult holds resolved association metadata.
730794
type assocLookupResult struct {
731795
Type domainmodel.AssociationType

0 commit comments

Comments
 (0)