Skip to content

Commit c6621a9

Browse files
committed
extract recursive type validation engine
1 parent 4f3c2d6 commit c6621a9

3 files changed

Lines changed: 179 additions & 173 deletions

File tree

docs/architecture.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ Key files:
4444
- `vibes/execution_members_array_query.go` (array query/enumeration member methods)
4545
- `vibes/execution_members_array_transforms.go` (array mutation/transform member methods)
4646
- `vibes/execution_members_array_grouping.go` (array sort/group/tally member methods)
47-
- `vibes/execution_types.go` (type-checking and declared-type formatting helpers)
47+
- `vibes/execution_types.go` (type mismatch and declared-type formatting helpers)
48+
- `vibes/execution_types_validation.go` (runtime value-vs-type validation with recursion guards)
4849
- `vibes/execution_types_value_format.go` (runtime value-type formatting helpers)
4950
- `vibes/execution_values.go` (value conversion, sorting, and flattening helpers)
5051
- `vibes/execution_values_arithmetic.go` (value arithmetic and comparison operators)

vibes/execution_types.go

Lines changed: 0 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package vibes
33
import (
44
"errors"
55
"fmt"
6-
"reflect"
76
"sort"
87
"strings"
98
)
@@ -47,177 +46,6 @@ func formatReturnTypeMismatch(fnName string, err error) string {
4746
return fmt.Sprintf("return type check failed for %s: %s", fnName, err.Error())
4847
}
4948

50-
type typeValidationVisit struct {
51-
valueKind ValueKind
52-
valueID uintptr
53-
ty *TypeExpr
54-
}
55-
56-
type typeValidationState struct {
57-
active map[typeValidationVisit]struct{}
58-
}
59-
60-
func valueMatchesType(val Value, ty *TypeExpr) (bool, error) {
61-
state := typeValidationState{
62-
active: make(map[typeValidationVisit]struct{}),
63-
}
64-
return state.matches(val, ty)
65-
}
66-
67-
func (s *typeValidationState) matches(val Value, ty *TypeExpr) (bool, error) {
68-
if visit, ok := typeValidationVisitFor(val, ty); ok {
69-
if _, seen := s.active[visit]; seen {
70-
// Recursive value/type pair already being validated higher in the stack.
71-
return true, nil
72-
}
73-
s.active[visit] = struct{}{}
74-
defer delete(s.active, visit)
75-
}
76-
77-
if ty.Nullable && val.Kind() == KindNil {
78-
return true, nil
79-
}
80-
switch ty.Kind {
81-
case TypeAny:
82-
return true, nil
83-
case TypeInt:
84-
return val.Kind() == KindInt, nil
85-
case TypeFloat:
86-
return val.Kind() == KindFloat, nil
87-
case TypeNumber:
88-
return val.Kind() == KindInt || val.Kind() == KindFloat, nil
89-
case TypeString:
90-
return val.Kind() == KindString, nil
91-
case TypeBool:
92-
return val.Kind() == KindBool, nil
93-
case TypeNil:
94-
return val.Kind() == KindNil, nil
95-
case TypeDuration:
96-
return val.Kind() == KindDuration, nil
97-
case TypeTime:
98-
return val.Kind() == KindTime, nil
99-
case TypeMoney:
100-
return val.Kind() == KindMoney, nil
101-
case TypeArray:
102-
if val.Kind() != KindArray {
103-
return false, nil
104-
}
105-
if len(ty.TypeArgs) == 0 {
106-
return true, nil
107-
}
108-
if len(ty.TypeArgs) != 1 {
109-
return false, fmt.Errorf("array type expects exactly 1 type argument")
110-
}
111-
elemType := ty.TypeArgs[0]
112-
for _, elem := range val.Array() {
113-
matches, err := s.matches(elem, elemType)
114-
if err != nil {
115-
return false, err
116-
}
117-
if !matches {
118-
return false, nil
119-
}
120-
}
121-
return true, nil
122-
case TypeHash:
123-
if val.Kind() != KindHash && val.Kind() != KindObject {
124-
return false, nil
125-
}
126-
if len(ty.TypeArgs) == 0 {
127-
return true, nil
128-
}
129-
if len(ty.TypeArgs) != 2 {
130-
return false, fmt.Errorf("hash type expects exactly 2 type arguments")
131-
}
132-
keyType := ty.TypeArgs[0]
133-
valueType := ty.TypeArgs[1]
134-
for key, value := range val.Hash() {
135-
keyMatches, err := s.matches(NewString(key), keyType)
136-
if err != nil {
137-
return false, err
138-
}
139-
if !keyMatches {
140-
return false, nil
141-
}
142-
valueMatches, err := s.matches(value, valueType)
143-
if err != nil {
144-
return false, err
145-
}
146-
if !valueMatches {
147-
return false, nil
148-
}
149-
}
150-
return true, nil
151-
case TypeFunction:
152-
return val.Kind() == KindFunction, nil
153-
case TypeShape:
154-
if val.Kind() != KindHash && val.Kind() != KindObject {
155-
return false, nil
156-
}
157-
entries := val.Hash()
158-
if len(ty.Shape) == 0 {
159-
return len(entries) == 0, nil
160-
}
161-
for field, fieldType := range ty.Shape {
162-
fieldVal, ok := entries[field]
163-
if !ok {
164-
return false, nil
165-
}
166-
matches, err := s.matches(fieldVal, fieldType)
167-
if err != nil {
168-
return false, err
169-
}
170-
if !matches {
171-
return false, nil
172-
}
173-
}
174-
for field := range entries {
175-
if _, ok := ty.Shape[field]; !ok {
176-
return false, nil
177-
}
178-
}
179-
return true, nil
180-
case TypeUnion:
181-
for _, option := range ty.Union {
182-
matches, err := s.matches(val, option)
183-
if err != nil {
184-
return false, err
185-
}
186-
if matches {
187-
return true, nil
188-
}
189-
}
190-
return false, nil
191-
default:
192-
return false, fmt.Errorf("unknown type %s", ty.Name)
193-
}
194-
}
195-
196-
func typeValidationVisitFor(val Value, ty *TypeExpr) (typeValidationVisit, bool) {
197-
if ty == nil {
198-
return typeValidationVisit{}, false
199-
}
200-
201-
var valueID uintptr
202-
switch val.Kind() {
203-
case KindArray:
204-
valueID = reflect.ValueOf(val.Array()).Pointer()
205-
case KindHash, KindObject:
206-
valueID = reflect.ValueOf(val.Hash()).Pointer()
207-
default:
208-
return typeValidationVisit{}, false
209-
}
210-
if valueID == 0 {
211-
return typeValidationVisit{}, false
212-
}
213-
214-
return typeValidationVisit{
215-
valueKind: val.Kind(),
216-
valueID: valueID,
217-
ty: ty,
218-
}, true
219-
}
220-
22149
func formatTypeExpr(ty *TypeExpr) string {
22250
if ty == nil {
22351
return "unknown"
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package vibes
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
)
7+
8+
type typeValidationVisit struct {
9+
valueKind ValueKind
10+
valueID uintptr
11+
ty *TypeExpr
12+
}
13+
14+
type typeValidationState struct {
15+
active map[typeValidationVisit]struct{}
16+
}
17+
18+
func valueMatchesType(val Value, ty *TypeExpr) (bool, error) {
19+
state := typeValidationState{
20+
active: make(map[typeValidationVisit]struct{}),
21+
}
22+
return state.matches(val, ty)
23+
}
24+
25+
func (s *typeValidationState) matches(val Value, ty *TypeExpr) (bool, error) {
26+
if visit, ok := typeValidationVisitFor(val, ty); ok {
27+
if _, seen := s.active[visit]; seen {
28+
// Recursive value/type pair already being validated higher in the stack.
29+
return true, nil
30+
}
31+
s.active[visit] = struct{}{}
32+
defer delete(s.active, visit)
33+
}
34+
35+
if ty.Nullable && val.Kind() == KindNil {
36+
return true, nil
37+
}
38+
switch ty.Kind {
39+
case TypeAny:
40+
return true, nil
41+
case TypeInt:
42+
return val.Kind() == KindInt, nil
43+
case TypeFloat:
44+
return val.Kind() == KindFloat, nil
45+
case TypeNumber:
46+
return val.Kind() == KindInt || val.Kind() == KindFloat, nil
47+
case TypeString:
48+
return val.Kind() == KindString, nil
49+
case TypeBool:
50+
return val.Kind() == KindBool, nil
51+
case TypeNil:
52+
return val.Kind() == KindNil, nil
53+
case TypeDuration:
54+
return val.Kind() == KindDuration, nil
55+
case TypeTime:
56+
return val.Kind() == KindTime, nil
57+
case TypeMoney:
58+
return val.Kind() == KindMoney, nil
59+
case TypeArray:
60+
if val.Kind() != KindArray {
61+
return false, nil
62+
}
63+
if len(ty.TypeArgs) == 0 {
64+
return true, nil
65+
}
66+
if len(ty.TypeArgs) != 1 {
67+
return false, fmt.Errorf("array type expects exactly 1 type argument")
68+
}
69+
elemType := ty.TypeArgs[0]
70+
for _, elem := range val.Array() {
71+
matches, err := s.matches(elem, elemType)
72+
if err != nil {
73+
return false, err
74+
}
75+
if !matches {
76+
return false, nil
77+
}
78+
}
79+
return true, nil
80+
case TypeHash:
81+
if val.Kind() != KindHash && val.Kind() != KindObject {
82+
return false, nil
83+
}
84+
if len(ty.TypeArgs) == 0 {
85+
return true, nil
86+
}
87+
if len(ty.TypeArgs) != 2 {
88+
return false, fmt.Errorf("hash type expects exactly 2 type arguments")
89+
}
90+
keyType := ty.TypeArgs[0]
91+
valueType := ty.TypeArgs[1]
92+
for key, value := range val.Hash() {
93+
keyMatches, err := s.matches(NewString(key), keyType)
94+
if err != nil {
95+
return false, err
96+
}
97+
if !keyMatches {
98+
return false, nil
99+
}
100+
valueMatches, err := s.matches(value, valueType)
101+
if err != nil {
102+
return false, err
103+
}
104+
if !valueMatches {
105+
return false, nil
106+
}
107+
}
108+
return true, nil
109+
case TypeFunction:
110+
return val.Kind() == KindFunction, nil
111+
case TypeShape:
112+
if val.Kind() != KindHash && val.Kind() != KindObject {
113+
return false, nil
114+
}
115+
entries := val.Hash()
116+
if len(ty.Shape) == 0 {
117+
return len(entries) == 0, nil
118+
}
119+
for field, fieldType := range ty.Shape {
120+
fieldVal, ok := entries[field]
121+
if !ok {
122+
return false, nil
123+
}
124+
matches, err := s.matches(fieldVal, fieldType)
125+
if err != nil {
126+
return false, err
127+
}
128+
if !matches {
129+
return false, nil
130+
}
131+
}
132+
for field := range entries {
133+
if _, ok := ty.Shape[field]; !ok {
134+
return false, nil
135+
}
136+
}
137+
return true, nil
138+
case TypeUnion:
139+
for _, option := range ty.Union {
140+
matches, err := s.matches(val, option)
141+
if err != nil {
142+
return false, err
143+
}
144+
if matches {
145+
return true, nil
146+
}
147+
}
148+
return false, nil
149+
default:
150+
return false, fmt.Errorf("unknown type %s", ty.Name)
151+
}
152+
}
153+
154+
func typeValidationVisitFor(val Value, ty *TypeExpr) (typeValidationVisit, bool) {
155+
if ty == nil {
156+
return typeValidationVisit{}, false
157+
}
158+
159+
var valueID uintptr
160+
switch val.Kind() {
161+
case KindArray:
162+
valueID = reflect.ValueOf(val.Array()).Pointer()
163+
case KindHash, KindObject:
164+
valueID = reflect.ValueOf(val.Hash()).Pointer()
165+
default:
166+
return typeValidationVisit{}, false
167+
}
168+
if valueID == 0 {
169+
return typeValidationVisit{}, false
170+
}
171+
172+
return typeValidationVisit{
173+
valueKind: val.Kind(),
174+
valueID: valueID,
175+
ty: ty,
176+
}, true
177+
}

0 commit comments

Comments
 (0)