mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-31 03:18:43 -06:00
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:
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user