lang: Catch duplicate resource fields or meta entries statically
This teaches the compiler to catch entries with duplicate fields, and duplicate meta entries, because it could be ambiguous to determine which should take precedence. For example, if you specified `content` to a file resource twice, this should error. This is known statically, so we can catch it. If you specified two `Meta:noop` entries, this can also be caught. The interesting part happens when you specify one `Meta:noop` entry, and one `Meta` entry which happens to contain a noop field in the struct. For this, we actually have to wait until type unification is finished, and catch the error there. This is because after type unification we will know the precise type of the struct being passed to `Meta`, and so we can look at its field names, even if their values aren't yet known because the graph hasn't run yet.
This commit is contained in:
@@ -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)
|
||||
@@ -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,
|
||||
}
|
||||
1
lang/interpret_test/TestAstFunc1/resdupefields0.graph
Normal file
1
lang/interpret_test/TestAstFunc1/resdupefields0.graph
Normal file
@@ -0,0 +1 @@
|
||||
# err: err4: resource has duplicate meta entry of: noop
|
||||
22
lang/interpret_test/TestAstFunc1/resdupefields0/main.mcl
Normal file
22
lang/interpret_test/TestAstFunc1/resdupefields0/main.mcl
Normal file
@@ -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,
|
||||
}
|
||||
1
lang/interpret_test/TestAstFunc1/resdupefields1.graph
Normal file
1
lang/interpret_test/TestAstFunc1/resdupefields1.graph
Normal file
@@ -0,0 +1 @@
|
||||
# err: errInit: resource has duplicate field of: anotherstr
|
||||
4
lang/interpret_test/TestAstFunc1/resdupefields1/main.mcl
Normal file
4
lang/interpret_test/TestAstFunc1/resdupefields1/main.mcl
Normal file
@@ -0,0 +1,4 @@
|
||||
test "test" {
|
||||
anotherstr => "hello",
|
||||
anotherstr => "hello", # values aren't checked in dupe checks
|
||||
}
|
||||
1
lang/interpret_test/TestAstFunc1/resdupefields2.graph
Normal file
1
lang/interpret_test/TestAstFunc1/resdupefields2.graph
Normal file
@@ -0,0 +1 @@
|
||||
# err: errInit: resource has duplicate field of: anotherstr
|
||||
4
lang/interpret_test/TestAstFunc1/resdupefields2/main.mcl
Normal file
4
lang/interpret_test/TestAstFunc1/resdupefields2/main.mcl
Normal file
@@ -0,0 +1,4 @@
|
||||
test "test" {
|
||||
anotherstr => "hello",
|
||||
anotherstr => "hello world", # values aren't checked in dupe checks
|
||||
}
|
||||
1
lang/interpret_test/TestAstFunc1/resdupefields3.graph
Normal file
1
lang/interpret_test/TestAstFunc1/resdupefields3.graph
Normal file
@@ -0,0 +1 @@
|
||||
# err: errInit: resource has duplicate meta entry of: noop
|
||||
5
lang/interpret_test/TestAstFunc1/resdupefields3/main.mcl
Normal file
5
lang/interpret_test/TestAstFunc1/resdupefields3/main.mcl
Normal file
@@ -0,0 +1,5 @@
|
||||
test "test" {
|
||||
anotherstr => "test",
|
||||
Meta:noop => false,
|
||||
Meta:noop => false,
|
||||
}
|
||||
1
lang/interpret_test/TestAstFunc1/resdupefields4.graph
Normal file
1
lang/interpret_test/TestAstFunc1/resdupefields4.graph
Normal file
@@ -0,0 +1 @@
|
||||
# err: errInit: resource has duplicate meta entry of: noop
|
||||
5
lang/interpret_test/TestAstFunc1/resdupefields4/main.mcl
Normal file
5
lang/interpret_test/TestAstFunc1/resdupefields4/main.mcl
Normal file
@@ -0,0 +1,5 @@
|
||||
test "test" {
|
||||
anotherstr => "test",
|
||||
Meta:noop => false,
|
||||
Meta:noop => true,
|
||||
}
|
||||
22
lang/interpret_test/TestAstFunc1/resdupefields5/main.mcl
Normal file
22
lang/interpret_test/TestAstFunc1/resdupefields5/main.mcl
Normal file
@@ -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,
|
||||
# },
|
||||
#}
|
||||
21
lang/interpret_test/TestAstFunc1/resdupefields6/main.mcl
Normal file
21
lang/interpret_test/TestAstFunc1/resdupefields6/main.mcl
Normal file
@@ -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,
|
||||
#}
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user