Make named union fields in structs syntactic sugar

Arv pointed out that there's really no difference between a named
union struct field and a struct field whose type is a struct that
contains only an anonymous union. This patch makes the parser replace
the TypeRef of UnionKind in each named union field with a TypeRef of
StructKind that contains only an anon union.

Fixes Issue #286
This commit is contained in:
Chris Masone
2015-09-14 15:49:27 -07:00
parent d72318d048
commit d0340a1160
4 changed files with 509 additions and 330 deletions

View File

@@ -38,7 +38,13 @@ Package <- _ dd:Definition+ _ EOF {
usings = append(usings, d)
case EnumKind, StructKind:
if _, present := named[d.Name]; present {
return nil, fmt.Errorf("Redefinition of " + d.Name)
return nil, fmt.Errorf("Redefinition of enum " + d.Name)
}
if d.Desc.Kind() == StructKind {
namedUnions := d.Desc.(StructDesc).GetNamedUnions()
for i, u := range namedUnions {
d.Desc.(StructDesc).Fields[i].T = makeStructTypeRef("", nil, u)
}
}
named[d.Name] = d
}
@@ -132,13 +138,13 @@ Type <- t:(PrimitiveType / CompoundType / Union / NamespaceIdent) {
}
}
CompoundType <- `List` `(` t:Type `)` {
CompoundType <- `List` _ `(` _ t:Type _ `)` _ {
return makeCompoundTypeRef(ListKind, []TypeRef{t.(TypeRef)}), nil
} / `Map` `(` k:Type `,` _ v:Type `)` {
} / `Map` _ `(` _ k:Type _ `,` _ v:Type _ `)` _ {
return makeCompoundTypeRef(MapKind, []TypeRef{k.(TypeRef), v.(TypeRef)}), nil
} / `Set` `(` t:Type `)` {
} / `Set` _ `(` _ t:Type _ `)` _ {
return makeCompoundTypeRef(SetKind, []TypeRef{t.(TypeRef)}), nil
} / `Ref` `(` t:Type `)` {
} / `Ref` _ `(` _ t:Type _ `)` _ {
return makeCompoundTypeRef(RefKind, []TypeRef{t.(TypeRef)}), nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -49,15 +49,25 @@ func (t TypeRef) Equals(other TypeRef) bool {
// The describe() methods generate text that should parse into the struct being described.
// TODO: Figure out a way that they can exist only in the test file.
func (t TypeRef) describe() string {
seg := []string{}
func (t TypeRef) describe() (out string) {
if t.Name != "" {
seg = append(seg, t.Name)
out += namespaceName(t.PkgRef, t.Name) + "\n"
}
if t.Desc != nil {
seg = append(seg, t.Desc.describe())
out += t.Desc.describe() + "\n"
}
return strings.Join(seg, "\n")
return
}
func namespaceName(pkgRef, name string) (out string) {
d.Chk.True(pkgRef == "" || (pkgRef != "" && name != ""), "If a TypeRef's PkgRef is set, so must Name be.")
if pkgRef != "" {
out = pkgRef + "."
}
if name != "" {
out += name
}
return
}
// TypeDesc describes a type of the kind returned by Kind(), e.g. Map, Int32, or a custom type.
@@ -180,11 +190,7 @@ func (c CompoundDesc) describe() string {
descElems := func() string {
out := make([]string, len(c.ElemTypes))
for i, e := range c.ElemTypes {
if e.Name != "" {
out[i] = e.Name
} else {
out[i] = e.Desc.describe()
}
out[i] = e.describe()
}
return strings.Join(out, ", ")
}
@@ -301,6 +307,17 @@ func (s StructDesc) Equals(other TypeDesc) bool {
return true
}
// GetNamedUnions returns a map of field index to TypeDesc for all named union fields in s.
func (s StructDesc) GetNamedUnions() map[int]*UnionDesc {
structs := map[int]*UnionDesc{}
for i, f := range s.Fields {
if u, ok := f.T.Desc.(UnionDesc); ok {
structs[i] = &u
}
}
return structs
}
func (s StructDesc) describe() (out string) {
out = ""
if len(s.Union.Choices) != 0 {

View File

@@ -16,6 +16,7 @@ const (
func TestParserTestSuite(t *testing.T) {
suite.Run(t, &ParserTestSuite{})
suite.Run(t, &ParsedResultTestSuite{})
}
type ParserTestSuite struct {
@@ -111,6 +112,58 @@ func (suite *ParserTestSuite) TestStructParse() {
}
}
func (suite *ParserTestSuite) TestEnum() {
enumTmpl := `enum %s { %s }`
name := "Enum"
ids := []string{"e1", "e2", "e4"}
enum := fmt.Sprintf(enumTmpl, name, strings.Join(ids, "\n"))
pkg := ParsePackage("", strings.NewReader(enum))
suite.Equal(name, pkg.NamedTypes[name].Name)
suite.EqualValues(ids, pkg.NamedTypes[name].Desc.(EnumDesc).IDs)
}
type ParsedResultTestSuite struct {
suite.Suite
primField Field
compoundField Field
compoundOfCompoundField Field
mapOfNamedTypeField Field
namedTypeField Field
namespacedTypeField Field
union UnionDesc
}
func (suite *ParsedResultTestSuite) SetupTest() {
suite.primField = Field{"a", makePrimitiveTypeRef("Int64")}
suite.compoundField = Field{"set", makeCompoundTypeRef(SetKind, []TypeRef{makePrimitiveTypeRef("String")})}
suite.compoundOfCompoundField = Field{
"listOfSet",
makeCompoundTypeRef(ListKind, []TypeRef{
makeCompoundTypeRef(SetKind, []TypeRef{makePrimitiveTypeRef("String")})})}
suite.mapOfNamedTypeField = Field{
"mapOfStructToOther",
makeCompoundTypeRef(MapKind, []TypeRef{
makeTypeRef("", "Struct"),
makeTypeRef("Elsewhere", "Other"),
}),
}
suite.namedTypeField = Field{"otherStruct", makeTypeRef("", "Other")}
suite.namespacedTypeField = Field{"namespacedStruct", makeTypeRef("Elsewhere", "Other")}
suite.union = UnionDesc{[]Field{
Field{"a", makePrimitiveTypeRef("Int32")},
Field{"n", makeTypeRef("NN", "Other")},
Field{"c", makePrimitiveTypeRef("UInt32")},
}}
}
func (suite *ParsedResultTestSuite) toText(s TypeRef) string {
suite.Equal(StructKind, s.Desc.Kind())
desc := s.Desc.(StructDesc)
return fmt.Sprintf(structTmpl, s.Name, desc.fieldsToString(), desc.unionToString())
}
func (s StructDesc) fieldsToString() (out string) {
for _, f := range s.Fields {
out += f.Name + " :" + f.T.describe() + "\n"
@@ -125,115 +178,144 @@ func (s StructDesc) unionToString() string {
return s.Union.describe()
}
func (suite *ParserTestSuite) TestStruct() {
toText := func(s TypeRef) string {
suite.Equal(StructKind, s.Desc.Kind())
desc := s.Desc.(StructDesc)
return fmt.Sprintf(structTmpl, s.Name, desc.fieldsToString(), desc.unionToString())
}
func (suite *ParsedResultTestSuite) checkStruct(pkg Package, str TypeRef) {
suite.Equal(StructKind, str.Desc.Kind())
strFields := str.Desc.(StructDesc).Fields
strUnion := str.Desc.(StructDesc).Union
checkStruct := func(pkg Package, str TypeRef) {
suite.Equal(StructKind, str.Desc.Kind())
strFields := str.Desc.(StructDesc).Fields
strUnion := str.Desc.(StructDesc).Union
typ := pkg.NamedTypes[str.Name]
typFields := typ.Desc.(StructDesc).Fields
typUnion := typ.Desc.(StructDesc).Union
typ := pkg.NamedTypes[str.Name]
typFields := typ.Desc.(StructDesc).Fields
typUnion := typ.Desc.(StructDesc).Union
suite.Equal(str.Name, typ.Name)
suite.Len(typFields, len(strFields))
for i := range typFields {
suite.EqualValues(strFields[i], typFields[i])
}
if strUnion != nil && suite.NotNil(typUnion) {
suite.Len(typUnion.Choices, len(strUnion.Choices))
for i := range typUnion.Choices {
suite.EqualValues(strUnion.Choices[i], typUnion.Choices[i])
suite.Equal(str.Name, typ.Name)
suite.Len(typFields, len(strFields))
for i, f := range strFields {
// Named unions are syntactic sugar for a struct Field that points to an anonymous struct containing an anonymous union.
// So, if the field in the input was of UnionKind...
if f.T.Desc != nil && f.T.Desc.Kind() == UnionKind {
// ...make sure the names are the same...
suite.Equal(f.Name, typFields[i].Name)
if tfd, ok := typFields[i].T.Desc.(StructDesc); ok {
// ...and that the IR has a TypeRef of StructKind with no fields, but an anonymous union.
suite.Len(tfd.Fields, 0)
suite.NotNil(tfd.Union)
suite.EqualValues(f.T.Desc, tfd.Union)
} else {
suite.Fail("Named unions must be parsed as anonymous structs containing an anonymous union.", "%#v", typFields[i])
}
} else {
suite.EqualValues(strUnion, typUnion, "If strUnion is nil, so should typUnion be.")
suite.EqualValues(f, typFields[i])
}
}
primField := Field{"a", makePrimitiveTypeRef("Int64")}
compoundField := Field{"set", makeCompoundTypeRef(SetKind, []TypeRef{makePrimitiveTypeRef("String")})}
compoundOfCompoundField := Field{
"listOfSet",
makeCompoundTypeRef(ListKind, []TypeRef{
makeCompoundTypeRef(SetKind, []TypeRef{makePrimitiveTypeRef("String")})})}
mapOfNamedTypeField := Field{
"mapOfStructToOther",
makeCompoundTypeRef(MapKind, []TypeRef{
makeTypeRef("", "Struct"),
makeTypeRef("Elsewhere", "Other"),
}),
}
namedTypeField := Field{"otherStruct", makeTypeRef("", "Other")}
namespacedTypeField := Field{"namespacedStruct", makeTypeRef("Elsewhere", "Other")}
union := UnionDesc{[]Field{
Field{"a", makePrimitiveTypeRef("Int32")},
Field{"n", makeTypeRef("NN", "Other")},
Field{"c", makePrimitiveTypeRef("UInt32")}}}
simpleRef := makeStructTypeRef("Simple", []Field{primField}, nil)
withAnonUnionRef := makeStructTypeRef("WithAnon", []Field{primField}, &union)
anonUnionFirstRef := makeStructTypeRef("WithAnonFirst", []Field{primField}, &union)
compoundRef := makeStructTypeRef("Compound", []Field{compoundField}, &union)
compoundOfCompoundRef := makeStructTypeRef("CofC", []Field{compoundOfCompoundField}, &union)
namedRef := makeStructTypeRef("Named", []Field{namedTypeField}, &union)
namespacedRef := makeStructTypeRef("Namespaced", []Field{namespacedTypeField}, &union)
mapFieldRef := makeStructTypeRef("MapStruct", []Field{mapOfNamedTypeField}, &union)
multiRef := makeStructTypeRef("Multi", []Field{
primField,
namedTypeField,
namespacedTypeField,
compoundField,
compoundOfCompoundField,
Field{"namedUnion", TypeRef{Desc: &union}},
}, &union)
withNamedUnionRef := makeStructTypeRef("NamedAndAnon", []Field{
Field{"namedUnion", TypeRef{Desc: &union}},
}, &union)
onlyNamedUnionRef := makeStructTypeRef("NamedUnionOnly", []Field{
Field{"namedUnion", TypeRef{Desc: &union}},
}, nil)
multiNamedUnionRef := makeStructTypeRef("TwoNamedAndAnon", []Field{
Field{"namedUnion1", TypeRef{Desc: &union}},
Field{"namedUnion2", TypeRef{Desc: &union}},
}, &union)
defns := []string{
toText(simpleRef),
toText(withAnonUnionRef),
/* Put anon union first*/
fmt.Sprintf(structTmpl, anonUnionFirstRef.Name, anonUnionFirstRef.Desc.(StructDesc).unionToString(), anonUnionFirstRef.Desc.(StructDesc).fieldsToString()),
toText(compoundRef),
toText(compoundOfCompoundRef),
toText(namedRef),
toText(namespacedRef),
toText(mapFieldRef),
toText(multiRef),
toText(withNamedUnionRef),
toText(onlyNamedUnionRef),
toText(multiNamedUnionRef),
if strUnion != nil && suite.NotNil(typUnion) {
suite.Len(typUnion.Choices, len(strUnion.Choices))
for i := range strUnion.Choices {
suite.EqualValues(strUnion.Choices[i], typUnion.Choices[i])
}
} else {
suite.EqualValues(strUnion, typUnion, "If strUnion is nil, so should typUnion be.")
}
pkgDef := strings.Join(defns, "\n")
}
func (suite *ParsedResultTestSuite) parseAndCheckStructs(structs ...TypeRef) {
pkgDef := ""
for _, s := range structs {
pkgDef += suite.toText(s) + "\n"
}
err := d.Try(func() {
pkg := ParsePackage("", strings.NewReader(pkgDef))
checkStruct(pkg, simpleRef)
for _, s := range structs {
suite.checkStruct(pkg, s)
}
})
suite.NoError(err, pkgDef)
}
func (suite *ParserTestSuite) TestEnum() {
enumTmpl := `enum %s { %s }`
name := "Enum"
ids := []string{"e1", "e2", "e4"}
enum := fmt.Sprintf(enumTmpl, name, strings.Join(ids, "\n"))
pkg := ParsePackage("", strings.NewReader(enum))
suite.Equal(name, pkg.NamedTypes[name].Name)
suite.EqualValues(ids, pkg.NamedTypes[name].Desc.(EnumDesc).IDs)
func (suite *ParsedResultTestSuite) TestPrimitiveField() {
suite.parseAndCheckStructs(makeStructTypeRef("Simple", []Field{suite.primField}, nil))
}
func (suite *ParsedResultTestSuite) TestAnonUnion() {
suite.parseAndCheckStructs(makeStructTypeRef("WithAnon", []Field{suite.primField}, &suite.union))
}
func (suite *ParsedResultTestSuite) TestAnonUnionFirst() {
anonUnionFirstRef := makeStructTypeRef("WithAnonFirst", []Field{suite.primField}, &suite.union)
pkgDef := fmt.Sprintf(structTmpl, anonUnionFirstRef.Name, anonUnionFirstRef.Desc.(StructDesc).unionToString(), anonUnionFirstRef.Desc.(StructDesc).fieldsToString())
err := d.Try(func() {
pkg := ParsePackage("", strings.NewReader(pkgDef))
suite.checkStruct(pkg, anonUnionFirstRef)
})
suite.NoError(err, pkgDef)
}
func (suite *ParsedResultTestSuite) TestCompoundField() {
suite.parseAndCheckStructs(makeStructTypeRef("Compound", []Field{suite.compoundField}, &suite.union))
}
func (suite *ParsedResultTestSuite) TestCompoundOfCompoundField() {
suite.parseAndCheckStructs(makeStructTypeRef("CofC", []Field{suite.compoundOfCompoundField}, &suite.union))
}
func (suite *ParsedResultTestSuite) TestNamedTypeField() {
suite.parseAndCheckStructs(makeStructTypeRef("Named", []Field{suite.namedTypeField}, &suite.union))
}
func (suite *ParsedResultTestSuite) TestNamespacedTypeField() {
suite.parseAndCheckStructs(makeStructTypeRef("Namespaced", []Field{suite.namespacedTypeField}, &suite.union))
}
func (suite *ParsedResultTestSuite) TestMapOfNamedTypeField() {
suite.parseAndCheckStructs(makeStructTypeRef("MapStruct", []Field{suite.mapOfNamedTypeField}, &suite.union))
}
func (suite *ParsedResultTestSuite) TestMultipleFields() {
suite.parseAndCheckStructs(makeStructTypeRef("Multi", []Field{
Field{suite.primField.Name, suite.primField.T},
Field{suite.namedTypeField.Name, suite.namedTypeField.T},
Field{suite.namespacedTypeField.Name, suite.namespacedTypeField.T},
Field{suite.compoundField.Name, suite.compoundField.T},
Field{suite.compoundOfCompoundField.Name, suite.compoundOfCompoundField.T},
Field{"namedUnion", TypeRef{Desc: &suite.union}},
}, &suite.union))
}
func (suite *ParsedResultTestSuite) TestNamedAndAnonUnion() {
suite.parseAndCheckStructs(makeStructTypeRef("NamedAndAnon", []Field{
Field{"namedUnion", TypeRef{Desc: &suite.union}},
}, &suite.union))
}
func (suite *ParsedResultTestSuite) TestNamedUnionOnly() {
suite.parseAndCheckStructs(makeStructTypeRef("NamedUnionOnly", []Field{
Field{"namedUnion", TypeRef{Desc: &suite.union}},
}, nil))
}
func (suite *ParsedResultTestSuite) TestTwoNamedAndAnonUnion() {
suite.parseAndCheckStructs(makeStructTypeRef("TwoNamedAndAnon", []Field{
Field{"namedUnion1", TypeRef{Desc: &suite.union}},
Field{"namedUnion2", TypeRef{Desc: &suite.union}},
}, &suite.union))
}
func (suite *ParsedResultTestSuite) TestMultipleStructs() {
defns := []TypeRef{
makeStructTypeRef("Simple", []Field{suite.primField}, nil),
makeStructTypeRef("Compound", []Field{suite.compoundField}, nil),
makeStructTypeRef("CompoundWithUnion", []Field{suite.compoundField}, &suite.union),
makeStructTypeRef("TwoNamedAndAnon", []Field{
Field{"namedUnion1", TypeRef{Desc: &suite.union}},
Field{"namedUnion2", TypeRef{Desc: &suite.union}},
}, &suite.union),
makeStructTypeRef("Multi", []Field{
Field{suite.primField.Name, suite.primField.T},
Field{suite.namespacedTypeField.Name, suite.namespacedTypeField.T},
Field{suite.compoundField.Name, suite.compoundField.T},
Field{"namedUnion", TypeRef{Desc: &suite.union}},
}, &suite.union),
}
suite.parseAndCheckStructs(defns...)
}