diff --git a/lang/interpret_test/TestAstFunc1/metaparams0.graph b/lang/interpret_test/TestAstFunc1/metaparams0.graph deleted file mode 100644 index b9fc05d2..00000000 --- a/lang/interpret_test/TestAstFunc1/metaparams0.graph +++ /dev/null @@ -1,43 +0,0 @@ -Edge: bool(false) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # noop -Edge: bool(false) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # rewatch -Edge: bool(true) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # autoedge -Edge: bool(true) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # autogroup -Edge: bool(true) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # realize -Edge: bool(true) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # reverse -Edge: bool(true) -> var(b) # var:b -Edge: bool(true) -> var(b) # var:b -Edge: float(4.2) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # limit -Edge: int(-1) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # retry -Edge: int(0) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # delay -Edge: int(3) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # burst -Edge: int(5) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # poll -Edge: list(str("foo:1"), str("bar:3")) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) # sema -Edge: str("bar:3") -> list(str("foo:1"), str("bar:3")) # 1 -Edge: str("foo:1") -> list(str("foo:1"), str("bar:3")) # 0 -Edge: str("hello world") -> call:fmt.printf(str("hello world")) # format -Vertex: bool(false) -Vertex: bool(false) -Vertex: bool(false) -Vertex: bool(false) -Vertex: bool(true) -Vertex: bool(true) -Vertex: bool(true) -Vertex: bool(true) -Vertex: bool(true) -Vertex: bool(true) -Vertex: bool(true) -Vertex: call:fmt.printf(str("hello world")) -Vertex: float(4.2) -Vertex: int(-1) -Vertex: int(0) -Vertex: int(3) -Vertex: int(42) -Vertex: int(5) -Vertex: list(str("foo:1"), str("bar:3")) -Vertex: str("bar:3") -Vertex: str("foo:1") -Vertex: str("greeting") -Vertex: str("hello world") -Vertex: struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str("foo:1"), str("bar:3")); rewatch: bool(false); realize: bool(true); reverse: bool(true); autoedge: bool(true); autogroup: bool(true)) -Vertex: var(b) -Vertex: var(b) diff --git a/lang/interpret_test/TestAstFunc1/metaparams0/main.mcl b/lang/interpret_test/TestAstFunc1/metaparams0/main.mcl deleted file mode 100644 index 167f10d4..00000000 --- a/lang/interpret_test/TestAstFunc1/metaparams0/main.mcl +++ /dev/null @@ -1,27 +0,0 @@ -import "fmt" - -$b = true - -test "greeting" { - anotherstr => fmt.printf("hello world"), - - Meta => $b ?: struct{ - noop => false, - retry => -1, - delay => 0, - poll => 5, - limit => 4.2, - burst => 3, - sema => ["foo:1", "bar:3",], - rewatch => false, - realize => true, - reverse => true, - autoedge => true, - autogroup => true, - }, - Meta:noop => false, - Meta:noop => true, # duplicates allowed atm, but not recommended! - Meta:poll => $b ?: 42, - Meta:autoedge => true, - Meta:autogroup => false, -} diff --git a/lang/interpret_test/TestAstFunc1/resdupefields0.graph b/lang/interpret_test/TestAstFunc1/resdupefields0.graph new file mode 100644 index 00000000..409de4a8 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/resdupefields0.graph @@ -0,0 +1 @@ +# err: err4: resource has duplicate meta entry of: noop diff --git a/lang/interpret_test/TestAstFunc1/resdupefields0/main.mcl b/lang/interpret_test/TestAstFunc1/resdupefields0/main.mcl new file mode 100644 index 00000000..552c77b0 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/resdupefields0/main.mcl @@ -0,0 +1,22 @@ +test "test" { + anotherstr => "test", + + Meta => true ?: struct{ + noop => false, + retry => -1, + delay => 0, + poll => 5, + limit => 4.2, + burst => 3, + sema => ["foo:1", "bar:3",], + rewatch => false, + realize => true, + reverse => true, + autoedge => true, + autogroup => true, + }, + Meta:noop => false, + #Meta:poll => $b ?: 42, + #Meta:autoedge => true, + #Meta:autogroup => false, +} diff --git a/lang/interpret_test/TestAstFunc1/resdupefields1.graph b/lang/interpret_test/TestAstFunc1/resdupefields1.graph new file mode 100644 index 00000000..3f2b7980 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/resdupefields1.graph @@ -0,0 +1 @@ +# err: errInit: resource has duplicate field of: anotherstr diff --git a/lang/interpret_test/TestAstFunc1/resdupefields1/main.mcl b/lang/interpret_test/TestAstFunc1/resdupefields1/main.mcl new file mode 100644 index 00000000..2897afa4 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/resdupefields1/main.mcl @@ -0,0 +1,4 @@ +test "test" { + anotherstr => "hello", + anotherstr => "hello", # values aren't checked in dupe checks +} diff --git a/lang/interpret_test/TestAstFunc1/resdupefields2.graph b/lang/interpret_test/TestAstFunc1/resdupefields2.graph new file mode 100644 index 00000000..3f2b7980 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/resdupefields2.graph @@ -0,0 +1 @@ +# err: errInit: resource has duplicate field of: anotherstr diff --git a/lang/interpret_test/TestAstFunc1/resdupefields2/main.mcl b/lang/interpret_test/TestAstFunc1/resdupefields2/main.mcl new file mode 100644 index 00000000..7bbd3f4b --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/resdupefields2/main.mcl @@ -0,0 +1,4 @@ +test "test" { + anotherstr => "hello", + anotherstr => "hello world", # values aren't checked in dupe checks +} diff --git a/lang/interpret_test/TestAstFunc1/resdupefields3.graph b/lang/interpret_test/TestAstFunc1/resdupefields3.graph new file mode 100644 index 00000000..4f0478cc --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/resdupefields3.graph @@ -0,0 +1 @@ +# err: errInit: resource has duplicate meta entry of: noop diff --git a/lang/interpret_test/TestAstFunc1/resdupefields3/main.mcl b/lang/interpret_test/TestAstFunc1/resdupefields3/main.mcl new file mode 100644 index 00000000..968f9da4 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/resdupefields3/main.mcl @@ -0,0 +1,5 @@ +test "test" { + anotherstr => "test", + Meta:noop => false, + Meta:noop => false, +} diff --git a/lang/interpret_test/TestAstFunc1/resdupefields4.graph b/lang/interpret_test/TestAstFunc1/resdupefields4.graph new file mode 100644 index 00000000..4f0478cc --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/resdupefields4.graph @@ -0,0 +1 @@ +# err: errInit: resource has duplicate meta entry of: noop diff --git a/lang/interpret_test/TestAstFunc1/resdupefields4/main.mcl b/lang/interpret_test/TestAstFunc1/resdupefields4/main.mcl new file mode 100644 index 00000000..24035500 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/resdupefields4/main.mcl @@ -0,0 +1,5 @@ +test "test" { + anotherstr => "test", + Meta:noop => false, + Meta:noop => true, +} diff --git a/lang/interpret_test/TestAstFunc1/resdupefields5.graph b/lang/interpret_test/TestAstFunc1/resdupefields5.graph new file mode 100644 index 00000000..e69de29b diff --git a/lang/interpret_test/TestAstFunc1/resdupefields5/main.mcl b/lang/interpret_test/TestAstFunc1/resdupefields5/main.mcl new file mode 100644 index 00000000..371a9a72 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/resdupefields5/main.mcl @@ -0,0 +1,22 @@ +# XXX: should error at graph unification, but we have a type unification bug +#test "test" { +# anotherstr => "test", +# +# Meta => true ?: struct{ +# noop => false, +# retry => -1, +# delay => 0, +# poll => 5, +# limit => 4.2, +# burst => 3, +# sema => ["foo:1", "bar:3",], +# rewatch => false, +# realize => true, +# reverse => true, +# autoedge => true, +# autogroup => true, +# }, +# Meta => true ?: struct{ +# noop => false, +# }, +#} diff --git a/lang/interpret_test/TestAstFunc1/resdupefields6.graph b/lang/interpret_test/TestAstFunc1/resdupefields6.graph new file mode 100644 index 00000000..e69de29b diff --git a/lang/interpret_test/TestAstFunc1/resdupefields6/main.mcl b/lang/interpret_test/TestAstFunc1/resdupefields6/main.mcl new file mode 100644 index 00000000..42121f17 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/resdupefields6/main.mcl @@ -0,0 +1,21 @@ +# XXX: should work, but we have a type unification bug +#test "test" { +# anotherstr => "test", +# +# Meta => true ?: struct{ +# retry => -1, +# delay => 0, +# realize => true, +# autogroup => true, +# }, +# Meta => true ?: struct{ +# poll => 5, +# limit => 4.2, +# burst => 3, +# sema => ["foo:1", "bar:3",], +# rewatch => false, +# reverse => true, +# }, +# Meta:noop => true, +# Meta:autoedge => true, +#} diff --git a/lang/lang_test.go b/lang/lang_test.go index 2cef61d3..21879756 100644 --- a/lang/lang_test.go +++ b/lang/lang_test.go @@ -222,7 +222,6 @@ func TestInterpret4(t *testing.T) { int64 => 42, boolptr => true, # comment 3 - stringptr => "the actual field name is: StringPtr", # comment 4 int8ptr => 99, # comment 5 comment => "☺\thello\nwo\"rld\\2", # must escape these } @@ -240,8 +239,6 @@ func TestInterpret4(t *testing.T) { x.Int64 = 42 b := true x.BoolPtr = &b - stringptr := "the actual field name is: StringPtr" - x.StringPtr = &stringptr int8ptr := int8(99) x.Int8Ptr = &int8ptr x.Comment = "☺\thello\nwo\"rld\\2" // must escape the escaped chars diff --git a/lang/structs.go b/lang/structs.go index 6ce94a8d..1078d9a6 100644 --- a/lang/structs.go +++ b/lang/structs.go @@ -310,7 +310,32 @@ func (obj *StmtRes) Init(data *interfaces.Data) error { if err := obj.Name.Init(data); err != nil { return err } + fieldNames := make(map[string]struct{}) + metaNames := make(map[string]struct{}) for _, x := range obj.Contents { + + // Duplicate checking for identical field names. + if line, ok := x.(*StmtResField); ok { + // Was the field already seen in this resource? + if _, exists := fieldNames[line.Field]; exists { + return fmt.Errorf("resource has duplicate field of: %s", line.Field) + } + fieldNames[line.Field] = struct{}{} + } + + // NOTE: you can have as many *StmtResEdge lines as you want =D + + if line, ok := x.(*StmtResMeta); ok { + // Was the meta entry already seen in this resource? + // Ignore the generic MetaField struct field for now. + // You're allowed to have more than one Meta field, but + // they can't contain the same field twice. + if _, exists := metaNames[line.Property]; exists && line.Property != MetaField { + return fmt.Errorf("resource has duplicate meta entry of: %s", line.Property) + } + metaNames[line.Property] = struct{}{} + } + if err := x.Init(data); err != nil { return err } @@ -518,7 +543,47 @@ func (obj *StmtRes) Unify() ([]interfaces.Invariant, error) { // to the resources created, but rather, once all the values (expressions) with // no outgoing edges have produced at least a single value, then the resources // know they're able to be built. +// +// This runs right after type unification. For this particular resource, we can +// do some additional static analysis, but only after unification has been done. +// Since I don't think it's worth extending the Stmt API for this, we can do the +// checks here at the beginning, and error out if something was invalid. In this +// particular case, the issue is one of catching duplicate meta fields. func (obj *StmtRes) Graph() (*pgraph.Graph, error) { + metaNames := make(map[string]struct{}) + for _, x := range obj.Contents { + line, ok := x.(*StmtResMeta) + if !ok { + continue + } + + properties := []string{line.Property} // "noop" or "Meta" or... + if line.Property == MetaField { + // If this is the generic MetaField struct field, then + // we lookup the type signature to see which fields are + // defined. You're allowed to have more than one Meta + // field, but they can't contain the same field twice. + + typ, err := line.MetaExpr.Type() // must be known now + if err != nil { + // programming error in type unification + return nil, errwrap.Wrapf(err, "unknown resource meta type") + } + if t := typ.Kind; t != types.KindStruct { + return nil, fmt.Errorf("unexpected resource meta kind of: %s", t) + } + properties = typ.Ord // list of field names in this struct + } + + for _, property := range properties { + // Was the meta entry already seen in this resource? + if _, exists := metaNames[property]; exists { + return nil, fmt.Errorf("resource has duplicate meta entry of: %s", property) + } + metaNames[property] = struct{}{} + } + } + graph, err := pgraph.NewGraph("res") if err != nil { return nil, errwrap.Wrapf(err, "could not create graph")