Skip to content

Commit 4841326

Browse files
fix(cddl2py): topologically sort all definitions to prevent NameError at runtime
The existing ordering only reordered groups by mixin (base-class) dependencies. Variable aliases, array type aliases, dict-pattern groups, and extra_items keyword arguments were emitted in CDDL schema order, causing NameError when a referenced type was defined later in the file. Extend getHardDependencies to collect eagerly-evaluated references from all assignment kinds (variables, arrays, groups) and add a getTypeReferences helper that recursively extracts user-defined type names from any PropertyType node. Cycle detection in the topological sort continues to handle circular references, and quoting remains as a fallback for types not present in the input.
1 parent d9e0b3f commit 4841326

6 files changed

Lines changed: 541 additions & 364 deletions

File tree

packages/cddl2py/src/index.ts

Lines changed: 131 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -803,14 +803,51 @@ function orderAssignments (assignments: Assignment[]): Assignment[] {
803803
}
804804

805805
function getHardDependencies (assignment: Assignment, assignmentsByName: Map<string, Assignment>): string[] {
806+
if (isVariable(assignment)) {
807+
const propTypes = Array.isArray(assignment.PropertyType)
808+
? assignment.PropertyType
809+
: [assignment.PropertyType]
810+
const deps = new Set<string>()
811+
for (const t of propTypes) {
812+
for (const ref of getTypeReferences(t)) {
813+
if (assignmentsByName.has(ref)) {
814+
deps.add(ref)
815+
}
816+
}
817+
}
818+
return [...deps]
819+
}
820+
821+
if (isCDDLArray(assignment)) {
822+
const arr = assignment as CDDLArray
823+
const deps = new Set<string>()
824+
for (const val of arr.Values) {
825+
const properties = Array.isArray(val) ? val : [val]
826+
for (const prop of properties) {
827+
const types = Array.isArray(prop.Type) ? prop.Type : [prop.Type]
828+
for (const t of types) {
829+
for (const ref of getTypeReferences(t)) {
830+
if (assignmentsByName.has(ref)) {
831+
deps.add(ref)
832+
}
833+
}
834+
}
835+
}
836+
}
837+
return [...deps]
838+
}
839+
806840
if (!isGroup(assignment)) {
807841
return []
808842
}
809843

810844
const deps = new Set<string>()
811-
for (const propertyOrChoice of assignment.Properties) {
812-
const properties = Array.isArray(propertyOrChoice) ? propertyOrChoice : [propertyOrChoice]
813-
for (const property of properties) {
845+
const properties = assignment.Properties
846+
const hasChoices = properties.some(p => Array.isArray(p))
847+
848+
for (const propertyOrChoice of properties) {
849+
const props = Array.isArray(propertyOrChoice) ? propertyOrChoice : [propertyOrChoice]
850+
for (const property of props) {
814851
if (!isUnNamedProperty(property)) {
815852
continue
816853
}
@@ -821,9 +858,100 @@ function getHardDependencies (assignment: Assignment, assignmentsByName: Map<str
821858
}
822859
}
823860

861+
if (!hasChoices) {
862+
const props = properties as Property[]
863+
864+
if (props.length === 1 && Object.keys(NATIVE_TYPE_MAP).includes(props[0].Name)) {
865+
const propType = Array.isArray(props[0].Type) ? props[0].Type : [props[0].Type]
866+
for (const t of propType) {
867+
for (const ref of getTypeReferences(t)) {
868+
if (assignmentsByName.has(ref)) {
869+
deps.add(ref)
870+
}
871+
}
872+
}
873+
}
874+
875+
for (const prop of props) {
876+
if (isExtensibleRecordProperty(prop)) {
877+
const cddlTypes = Array.isArray(prop.Type) ? prop.Type : [prop.Type]
878+
for (const t of cddlTypes) {
879+
for (const ref of getTypeReferences(t)) {
880+
if (assignmentsByName.has(ref)) {
881+
deps.add(ref)
882+
}
883+
}
884+
}
885+
}
886+
}
887+
}
888+
824889
return [...deps]
825890
}
826891

892+
function getTypeReferences (t: PropertyType): string[] {
893+
if (typeof t === 'string') {
894+
return []
895+
}
896+
897+
if (isNamedGroupReference(t)) {
898+
return [pascalCase((t as unknown as PropertyReference).Value as string)]
899+
}
900+
901+
if (isPropertyReference(t)) {
902+
const ref = t as PropertyReference
903+
if ((ref.Type === 'group' || ref.Type === 'group_array') && typeof ref.Value === 'string') {
904+
return [pascalCase(ref.Value)]
905+
}
906+
if (ref.Type === 'tag') {
907+
const tag = ref.Value as Tag
908+
if (!NATIVE_TYPE_MAP[tag.TypePart]) {
909+
return [pascalCase(tag.TypePart)]
910+
}
911+
}
912+
return []
913+
}
914+
915+
if (isNativeTypeWithOperator(t)) {
916+
if (isNamedGroupReference(t.Type)) {
917+
return [pascalCase((t.Type as unknown as PropertyReference).Value as string)]
918+
}
919+
return []
920+
}
921+
922+
if (isGroup(t) && !isNamedGroupReference(t) && (t as unknown as Group).Properties) {
923+
const refs: string[] = []
924+
const group = t as unknown as Group
925+
for (const prop of group.Properties) {
926+
const subProps = Array.isArray(prop) ? prop : [prop]
927+
for (const p of subProps) {
928+
const types = Array.isArray(p.Type) ? p.Type : [p.Type]
929+
for (const subType of types) {
930+
refs.push(...getTypeReferences(subType))
931+
}
932+
}
933+
}
934+
return refs
935+
}
936+
937+
if (isCDDLArray(t)) {
938+
const refs: string[] = []
939+
const arr = t as unknown as CDDLArray
940+
for (const val of arr.Values) {
941+
const subProps = Array.isArray(val) ? val : [val]
942+
for (const prop of subProps) {
943+
const types = Array.isArray(prop.Type) ? prop.Type : [prop.Type]
944+
for (const subType of types) {
945+
refs.push(...getTypeReferences(subType))
946+
}
947+
}
948+
}
949+
return refs
950+
}
951+
952+
return []
953+
}
954+
827955
function getMixinDependencies (type: Property['Type'], assignmentsByName: Map<string, Assignment>): string[] {
828956
const deps = new Set<string>()
829957
const values = Array.isArray(type) ? type : [type]

packages/cddl2py/tests/__snapshots__/complex_types.test.ts.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ from __future__ import annotations
88
from typing import Literal, Union
99
from pydantic import BaseModel
1010
11-
LocalValue = Union["ArrayLocalValue", "DateLocalValue", "MapLocalValue", "ObjectLocalValue", "RegExpLocalValue", "SetLocalValue"]
12-
1311
class ArrayLocalValue(BaseModel):
1412
type: Literal["array"]
1513
value: ListLocalValue
@@ -34,6 +32,8 @@ class SetLocalValue(BaseModel):
3432
type: Literal["set"]
3533
value: ListLocalValue
3634
35+
LocalValue = Union[ArrayLocalValue, DateLocalValue, MapLocalValue, ObjectLocalValue, RegExpLocalValue, SetLocalValue]
36+
3737
MappingLocalValue = list[Union[LocalValue, str]]
3838
3939
ListLocalValue = list[LocalValue]
@@ -48,8 +48,6 @@ from __future__ import annotations
4848
from typing import Literal, Union
4949
from typing_extensions import TypedDict
5050
51-
LocalValue = Union["ArrayLocalValue", "DateLocalValue", "MapLocalValue", "ObjectLocalValue", "RegExpLocalValue", "SetLocalValue"]
52-
5351
class ArrayLocalValue(TypedDict):
5452
type: Literal["array"]
5553
value: ListLocalValue
@@ -74,6 +72,8 @@ class SetLocalValue(TypedDict):
7472
type: Literal["set"]
7573
value: ListLocalValue
7674
75+
LocalValue = Union[ArrayLocalValue, DateLocalValue, MapLocalValue, ObjectLocalValue, RegExpLocalValue, SetLocalValue]
76+
7777
MappingLocalValue = list[Union[LocalValue, str]]
7878
7979
ListLocalValue = list[LocalValue]

0 commit comments

Comments
 (0)