[fidl][dart] Support for bits declarations

Change-Id: I7db7c690ab0b0030be67ecb2836d8514bc72ff0a
diff --git a/bin/fidl_bindings_test/fidl/BUILD.gn b/bin/fidl_bindings_test/fidl/BUILD.gn
index bf7364f..75cd6f9 100644
--- a/bin/fidl_bindings_test/fidl/BUILD.gn
+++ b/bin/fidl_bindings_test/fidl/BUILD.gn
@@ -8,7 +8,7 @@
   name = "fidl.examples.bindingstest"
 
   sources = [
-    "bindings_test.fidl",
-    "conformance.fidl",
+    "bindings_test.test.fidl",
+    "conformance.test.fidl",
   ]
 }
diff --git a/bin/fidl_bindings_test/fidl/bindings_test.fidl b/bin/fidl_bindings_test/fidl/bindings_test.test.fidl
similarity index 93%
rename from bin/fidl_bindings_test/fidl/bindings_test.fidl
rename to bin/fidl_bindings_test/fidl/bindings_test.test.fidl
index 9324fc6..af5555b 100644
--- a/bin/fidl_bindings_test/fidl/bindings_test.fidl
+++ b/bin/fidl_bindings_test/fidl/bindings_test.test.fidl
@@ -28,6 +28,12 @@
     vector<uint8> baz;
 };
 
+bits ExampleBits {
+    MEMBER_A = 2;
+    MEMBER_B = 4;
+    MEMBER_C = 8;
+};
+
 [Discoverable]
 protocol TestServer {
     OneWayNoArgs();
@@ -49,6 +55,9 @@
     OneWayExampleXunion(ExampleXunion value);
     ReceivedOneWayExampleXunion() -> (ExampleXunion received);
 
+    OneWayExampleBits(ExampleBits value);
+    ReceivedOneWayExampleBits() -> (ExampleBits received);
+
     SendEmptyEvent();
     -> EmptyEvent();
 
diff --git a/bin/fidl_bindings_test/fidl/conformance.fidl b/bin/fidl_bindings_test/fidl/conformance.test.fidl
similarity index 100%
rename from bin/fidl_bindings_test/fidl/conformance.fidl
rename to bin/fidl_bindings_test/fidl/conformance.test.fidl
diff --git a/bin/fidl_bindings_test/server/lib/main.dart b/bin/fidl_bindings_test/server/lib/main.dart
index b43d11c..d78b859 100644
--- a/bin/fidl_bindings_test/server/lib/main.dart
+++ b/bin/fidl_bindings_test/server/lib/main.dart
@@ -78,6 +78,18 @@
     return _oneWayExampleXunion;
   }
 
+  ExampleBits _oneWayExampleBits;
+
+  @override
+  Future<void> oneWayExampleBits(ExampleBits value) async {
+    _oneWayExampleBits = value;
+  }
+
+  @override
+  Future<ExampleBits> receivedOneWayExampleBits() async {
+    return _oneWayExampleBits;
+  }
+
   @override
   Future<void> twoWayNoArgs() async {}
 
diff --git a/bin/fidl_bindings_test/test/test/oneway_test.dart b/bin/fidl_bindings_test/test/test/oneway_test.dart
index 7ce68ed..13c1dea 100644
--- a/bin/fidl_bindings_test/test/test/oneway_test.dart
+++ b/bin/fidl_bindings_test/test/test/oneway_test.dart
@@ -104,5 +104,19 @@
       expect(received.bar, equals(null));
       expect(received.baz, unorderedEquals(primes));
     });
+
+    test('bits with single bit', () async {
+      await server.proxy.oneWayExampleBits(ExampleBits.memberB);
+      final received = await server.proxy.receivedOneWayExampleBits();
+      expect(received, equals(ExampleBits.memberB));
+    });
+
+    test('bits with multiple bits', () async {
+      await server.proxy.oneWayExampleBits(
+        ExampleBits.memberB | ExampleBits.memberC);
+      final received = await server.proxy.receivedOneWayExampleBits();
+      expect(received, equals(
+        ExampleBits.memberB | ExampleBits.memberC));
+    });
   });
 }
diff --git a/bin/fidl_bindings_test/test/test/tostring_test.dart b/bin/fidl_bindings_test/test/test/tostring_test.dart
new file mode 100644
index 0000000..94e5a6d
--- /dev/null
+++ b/bin/fidl_bindings_test/test/test/tostring_test.dart
@@ -0,0 +1,20 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:test/test.dart';
+import 'package:fidl_fidl_examples_bindingstest/fidl_async.dart';
+
+void main() {
+  print('toString-test');
+  group('bits', () {
+    test('single bit', () {
+      expect(ExampleBits.memberC.toString(), equals(r'ExampleBits.memberC'));
+    });
+    test('multiple bits', () {
+      expect(
+        (ExampleBits.memberC | ExampleBits.memberA).toString(),
+        equals(r'ExampleBits.memberA | ExampleBits.memberC'));
+    });
+  });
+}
diff --git a/bin/fidlgen_dart/backend/generator.go b/bin/fidlgen_dart/backend/generator.go
index 6163457..ab14300 100644
--- a/bin/fidlgen_dart/backend/generator.go
+++ b/bin/fidlgen_dart/backend/generator.go
@@ -61,6 +61,7 @@
 	tmpls := template.New("DartTemplates")
 	template.Must(tmpls.Parse(templates.Const))
 	template.Must(tmpls.Parse(templates.Enum))
+	template.Must(tmpls.Parse(templates.Bits))
 	template.Must(tmpls.Parse(templates.Interface))
 	template.Must(tmpls.Parse(templates.Library))
 	template.Must(tmpls.Parse(templates.Struct))
diff --git a/bin/fidlgen_dart/backend/ir/ir.go b/bin/fidlgen_dart/backend/ir/ir.go
index 72769ff..3f1c37f 100644
--- a/bin/fidlgen_dart/backend/ir/ir.go
+++ b/bin/fidlgen_dart/backend/ir/ir.go
@@ -54,6 +54,22 @@
 	Documented
 }
 
+// Bits represents a bits declaration.
+type Bits struct {
+	Name       string
+	Members    []BitsMember
+	TypeSymbol string
+	TypeExpr   string
+	Documented
+}
+
+// BitsMember represents a member of a bits declaration.
+type BitsMember struct {
+	Name  string
+	Value string
+	Documented
+}
+
 // Union represents a union declaration.
 type Union struct {
 	Name       string
@@ -201,6 +217,7 @@
 	Imports     []Import
 	Consts      []Const
 	Enums       []Enum
+	Bits        []Bits
 	Interfaces  []Interface
 	Structs     []Struct
 	Tables      []Table
@@ -211,8 +228,10 @@
 type context map[string]bool
 
 var (
+	// Name of a bits member
+	bitsMemberContext = make(context)
 	// Name of an enum member
-	enumMemberContext context = make(context)
+	enumMemberContext = make(context)
 	// Name of a struct member
 	structMemberContext = make(context)
 	// Name of a table member
@@ -233,9 +252,12 @@
 )
 
 func init() {
-	var allContexts = []context{enumMemberContext, structMemberContext, tableMemberContext,
-		unionMemberContext, unionMemberTagContext, constantContext, declarationContext,
-		methodContext, parameterContext}
+	var allContexts = []context{
+		enumMemberContext, structMemberContext, tableMemberContext,
+		unionMemberContext, unionMemberTagContext, constantContext,
+		declarationContext, methodContext, parameterContext,
+		bitsMemberContext,
+	}
 
 	var reservedWords = map[string][]context{
 		"assert":       allContexts,
@@ -251,7 +273,7 @@
 		"default":      allContexts,
 		"do":           allContexts,
 		"double":       []context{structMemberContext, tableMemberContext},
-		"dynamic":      []context{enumMemberContext, methodContext, unionMemberContext, constantContext, tableMemberContext, structMemberContext},
+		"dynamic":      []context{bitsMemberContext, enumMemberContext, methodContext, unionMemberContext, constantContext, tableMemberContext, structMemberContext},
 		"else":         allContexts,
 		"enum":         allContexts,
 		"extends":      allContexts,
@@ -259,13 +281,13 @@
 		"final":        allContexts,
 		"finally":      allContexts,
 		"for":          allContexts,
-		"hashCode":     []context{methodContext, enumMemberContext, unionMemberContext, structMemberContext, tableMemberContext},
+		"hashCode":     []context{methodContext, bitsMemberContext, enumMemberContext, unionMemberContext, structMemberContext, tableMemberContext},
 		"noSuchMethod": []context{methodContext, enumMemberContext, unionMemberContext, structMemberContext, tableMemberContext},
 		"runtimeType":  []context{methodContext, enumMemberContext, unionMemberContext, structMemberContext, tableMemberContext},
 		"index":        []context{unionMemberTagContext},
 		"if":           allContexts,
 		"in":           allContexts,
-		"int":          []context{enumMemberContext, methodContext, unionMemberContext, constantContext, tableMemberContext, structMemberContext},
+		"int":          []context{bitsMemberContext, enumMemberContext, methodContext, unionMemberContext, constantContext, tableMemberContext, structMemberContext},
 		"is":           allContexts,
 		"List":         []context{declarationContext},
 		"Map":          []context{declarationContext},
@@ -282,7 +304,7 @@
 		"switch":       allContexts,
 		"this":         allContexts,
 		"throw":        allContexts,
-		"toString":     []context{methodContext, enumMemberContext, structMemberContext, tableMemberContext, unionMemberContext},
+		"toString":     []context{methodContext, bitsMemberContext, enumMemberContext, structMemberContext, tableMemberContext, unionMemberContext},
 		"true":         allContexts,
 		"try":          allContexts,
 		"values":       []context{unionMemberTagContext},
@@ -690,6 +712,8 @@
 			fallthrough
 		case types.EnumDeclType:
 			fallthrough
+		case types.BitsDeclType:
+			fallthrough
 		case types.StructDeclType:
 			fallthrough
 		case types.TableDeclType:
@@ -774,6 +798,29 @@
 	return e
 }
 
+func (c *compiler) compileBits(val types.Bits) Bits {
+	ci := types.ParseCompoundIdentifier(val.Name)
+	n := c.compileUpperCamelCompoundIdentifier(ci, "", declarationContext)
+	if val.Type.Kind != types.PrimitiveType {
+		panic("unexpected, only primitives are allowed for bits declarations")
+	}
+	subtype := val.Type.PrimitiveSubtype
+	b := Bits{
+		Name:       n,
+		TypeSymbol: c.typeSymbolForCompoundIdentifier(ci),
+		TypeExpr:   fmt.Sprintf("$fidl.BitsType<%s>(type: %s, ctor: %s._ctor)", n, typeExprForPrimitiveSubtype(subtype), n),
+		Documented: docString(val),
+	}
+	for _, v := range val.Members {
+		b.Members = append(b.Members, BitsMember{
+			Name:       c.compileLowerCamelIdentifier(v.Name, bitsMemberContext),
+			Value:      c.compileConstant(v.Value, nil),
+			Documented: docString(v),
+		})
+	}
+	return b
+}
+
 func (c *compiler) compileParameter(paramName types.Identifier, paramType types.Type, offset int) Parameter {
 	var (
 		t         = c.compileType(paramType)
@@ -1138,6 +1185,10 @@
 		root.Enums = append(root.Enums, c.compileEnum(v))
 	}
 
+	for _, v := range r.Bits {
+		root.Bits = append(root.Bits, c.compileBits(v))
+	}
+
 	for _, v := range r.Interfaces {
 		root.Interfaces = append(root.Interfaces, c.compileInterface(v))
 	}
diff --git a/bin/fidlgen_dart/backend/templates/bits.tmpl.go b/bin/fidlgen_dart/backend/templates/bits.tmpl.go
new file mode 100644
index 0000000..03d620c
--- /dev/null
+++ b/bin/fidlgen_dart/backend/templates/bits.tmpl.go
@@ -0,0 +1,53 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package templates
+
+// Bits is the template for bits declarations.
+const Bits = `
+{{- define "BitsDeclaration" -}}
+{{- range .Doc }}
+///{{ . -}}
+{{- end }}
+class {{ .Name }} extends $fidl.Bits {
+{{- range .Members }}
+  {{- range .Doc }}
+  ///{{ . -}}
+  {{- end }}
+  static const {{ $.Name }} {{ .Name }} = {{ $.Name }}._({{ .Value }});
+{{- end }}
+
+  const {{ .Name }}._(this.$value);
+
+  {{ .Name }} operator |({{ .Name }} other) {
+    return {{ .Name }}._($value | other.$value);
+  }
+
+  @override
+  final int $value;
+
+  @override
+  String toString() {
+    if ($value == null) {
+      return null;
+    }
+    List<String> parts = [];
+{{- range .Members }}
+    if ($value & {{ .Value }} != 0) {
+      parts.add(r'{{ $.Name }}.{{ .Name }}');
+	}
+{{- end }}
+    if (parts.isEmpty) {
+      return r'{{ $.Name }}.$none';
+    } else {
+      return parts.join(" | ");
+    }
+  }
+
+  static {{ .Name }} _ctor(int v) => {{ .Name }}._(v);
+}
+
+const $fidl.BitsType<{{ .Name }}> {{ .TypeSymbol }} = {{ .TypeExpr }};
+{{ end }}
+`
diff --git a/bin/fidlgen_dart/backend/templates/library.tmpl.go b/bin/fidlgen_dart/backend/templates/library.tmpl.go
index f7a410b..ffe05ec 100644
--- a/bin/fidlgen_dart/backend/templates/library.tmpl.go
+++ b/bin/fidlgen_dart/backend/templates/library.tmpl.go
@@ -59,6 +59,9 @@
 {{ range $enum := .Enums -}}
 {{ template "EnumDeclaration" $enum }}
 {{ end -}}
+{{ range $bits := .Bits -}}
+{{ template "BitsDeclaration" $bits }}
+{{ end -}}
 {{ range $union := .Unions -}}
 {{ template "UnionDeclaration" $union }}
 {{ end -}}
@@ -143,6 +146,9 @@
 {{ range $enum := .Enums -}}
 {{ template "EnumDeclaration" $enum }}
 {{ end -}}
+{{ range $bits := .Bits -}}
+{{ template "BitsDeclaration" $bits }}
+{{ end -}}
 {{ range $union := .Unions -}}
 {{ template "UnionDeclaration" $union }}
 {{ end -}}
diff --git a/bin/fidlgen_dart/goldens/bits.test.test.fidl.json_async.dart.golden b/bin/fidlgen_dart/goldens/bits.test.test.fidl.json_async.dart.golden
index e541d9b..613eabd 100644
--- a/bin/fidlgen_dart/goldens/bits.test.test.fidl.json_async.dart.golden
+++ b/bin/fidlgen_dart/goldens/bits.test.test.fidl.json_async.dart.golden
@@ -45,5 +45,43 @@
 // ignore_for_file: unnecessary_lambdas
 // ignore_for_file: comment_references
 
+class MyBits extends $fidl.Bits {
+  static const MyBits myFirstBit = MyBits._(1);
+  static const MyBits myOtherBit = MyBits._(2);
+
+  const MyBits._(this.$value);
+
+  MyBits operator |(MyBits other) {
+    return MyBits._($value | other.$value);
+  }
+
+  @override
+  final int $value;
+
+  @override
+  String toString() {
+    if ($value == null) {
+      return null;
+    }
+    List<String> parts = [];
+    if ($value & 1 != 0) {
+      parts.add(r'MyBits.myFirstBit');
+    }
+    if ($value & 2 != 0) {
+      parts.add(r'MyBits.myOtherBit');
+    }
+    if (parts.isEmpty) {
+      return r'MyBits.$none';
+    } else {
+      return parts.join(" | ");
+    }
+  }
+
+  static MyBits _ctor(int v) => MyBits._(v);
+}
+
+const $fidl.BitsType<MyBits> kMyBits_Type =
+    $fidl.BitsType<MyBits>(type: $fidl.Uint32Type(), ctor: MyBits._ctor);
+
 // ignore: unused_element, avoid_private_typedef_functions
 typedef _VoidCallback = void Function();
diff --git a/bin/fidlgen_dart/goldens/bits.test.test.fidl.json_sync.dart.golden b/bin/fidlgen_dart/goldens/bits.test.test.fidl.json_sync.dart.golden
index 2750514..96f4d7d 100644
--- a/bin/fidlgen_dart/goldens/bits.test.test.fidl.json_sync.dart.golden
+++ b/bin/fidlgen_dart/goldens/bits.test.test.fidl.json_sync.dart.golden
@@ -39,3 +39,41 @@
 // ignore_for_file: always_put_required_named_parameters_first
 // ignore_for_file: prefer_generic_function_type_aliases
 // ignore_for_file: prefer_equal_for_default_values
+
+class MyBits extends $fidl.Bits {
+  static const MyBits myFirstBit = MyBits._(1);
+  static const MyBits myOtherBit = MyBits._(2);
+
+  const MyBits._(this.$value);
+
+  MyBits operator |(MyBits other) {
+    return MyBits._($value | other.$value);
+  }
+
+  @override
+  final int $value;
+
+  @override
+  String toString() {
+    if ($value == null) {
+      return null;
+    }
+    List<String> parts = [];
+    if ($value & 1 != 0) {
+      parts.add(r'MyBits.myFirstBit');
+    }
+    if ($value & 2 != 0) {
+      parts.add(r'MyBits.myOtherBit');
+    }
+    if (parts.isEmpty) {
+      return r'MyBits.$none';
+    } else {
+      return parts.join(" | ");
+    }
+  }
+
+  static MyBits _ctor(int v) => MyBits._(v);
+}
+
+const $fidl.BitsType<MyBits> kMyBits_Type =
+    $fidl.BitsType<MyBits>(type: $fidl.Uint32Type(), ctor: MyBits._ctor);
diff --git a/bin/fidlgen_dart/goldens/tables.test.fidl.json b/bin/fidlgen_dart/goldens/tables.test.fidl.json
index 4a72da7..a61086a 100644
--- a/bin/fidlgen_dart/goldens/tables.test.fidl.json
+++ b/bin/fidlgen_dart/goldens/tables.test.fidl.json
@@ -49,15 +49,30 @@
         },
         {
           "ordinal": 2,
-          "reserved": true
+          "reserved": true,
+          "location": {
+            "filename": "garnet/go/src/fidl/compiler/backend/typestest/tables.test.fidl",
+            "line": 8,
+            "column": 5
+          }
         },
         {
           "ordinal": 3,
-          "reserved": true
+          "reserved": true,
+          "location": {
+            "filename": "garnet/go/src/fidl/compiler/backend/typestest/tables.test.fidl",
+            "line": 9,
+            "column": 5
+          }
         },
         {
           "ordinal": 4,
-          "reserved": true
+          "reserved": true,
+          "location": {
+            "filename": "garnet/go/src/fidl/compiler/backend/typestest/tables.test.fidl",
+            "line": 10,
+            "column": 5
+          }
         },
         {
           "ordinal": 5,
@@ -111,7 +126,12 @@
         },
         {
           "ordinal": 2,
-          "reserved": true
+          "reserved": true,
+          "location": {
+            "filename": "garnet/go/src/fidl/compiler/backend/typestest/tables.test.fidl",
+            "line": 16,
+            "column": 5
+          }
         }
       ],
       "size": 16,
@@ -147,15 +167,30 @@
         },
         {
           "ordinal": 2,
-          "reserved": true
+          "reserved": true,
+          "location": {
+            "filename": "garnet/go/src/fidl/compiler/backend/typestest/tables.test.fidl",
+            "line": 21,
+            "column": 5
+          }
         },
         {
           "ordinal": 3,
-          "reserved": true
+          "reserved": true,
+          "location": {
+            "filename": "garnet/go/src/fidl/compiler/backend/typestest/tables.test.fidl",
+            "line": 22,
+            "column": 5
+          }
         },
         {
           "ordinal": 4,
-          "reserved": true
+          "reserved": true,
+          "location": {
+            "filename": "garnet/go/src/fidl/compiler/backend/typestest/tables.test.fidl",
+            "line": 23,
+            "column": 5
+          }
         },
         {
           "ordinal": 5,
@@ -195,7 +230,12 @@
         },
         {
           "ordinal": 7,
-          "reserved": true
+          "reserved": true,
+          "location": {
+            "filename": "garnet/go/src/fidl/compiler/backend/typestest/tables.test.fidl",
+            "line": 26,
+            "column": 5
+          }
         }
       ],
       "size": 16,
diff --git a/public/dart/fidl/BUILD.gn b/public/dart/fidl/BUILD.gn
index a9139f6..e2fa12c 100644
--- a/public/dart/fidl/BUILD.gn
+++ b/public/dart/fidl/BUILD.gn
@@ -13,6 +13,7 @@
 
   sources = [
     "fidl.dart",
+    "src/bits.dart",
     "src/codec.dart",
     "src/enum.dart",
     "src/error.dart",
diff --git a/public/dart/fidl/lib/fidl.dart b/public/dart/fidl/lib/fidl.dart
index 90bf1b0..d569fe0 100644
--- a/public/dart/fidl/lib/fidl.dart
+++ b/public/dart/fidl/lib/fidl.dart
@@ -7,6 +7,7 @@
 /// classes implemented by generated FIDL code. It is often used directly in
 /// author code to retrieve type definitions (e.g. InterfaceHandle,
 /// InterfaceRequest, etc.) for interacting with certain FIDL services.
+export 'src/bits.dart';
 export 'src/codec.dart';
 export 'src/enum.dart';
 export 'src/error.dart';
diff --git a/public/dart/fidl/lib/src/bits.dart b/public/dart/fidl/lib/src/bits.dart
new file mode 100644
index 0000000..6ac12e5
--- /dev/null
+++ b/public/dart/fidl/lib/src/bits.dart
@@ -0,0 +1,26 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: public_member_api_docs
+
+// Bits represents a bit field, i.e. no bit set, one bit set, or multiple bit
+// set.
+abstract class Bits {
+  const Bits();
+
+  int get $value;
+
+  @override
+  bool operator ==(dynamic other) {
+    if (other is Bits) {
+      return $value == other.$value;
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode => $value.hashCode;
+}
+
+typedef BitsFactory<T> = T Function(int value);
diff --git a/public/dart/fidl/lib/src/types.dart b/public/dart/fidl/lib/src/types.dart
index 7c732ec..4bf9b55 100644
--- a/public/dart/fidl/lib/src/types.dart
+++ b/public/dart/fidl/lib/src/types.dart
@@ -7,6 +7,7 @@
 
 import 'package:zircon/zircon.dart';
 
+import 'bits.dart';
 import 'codec.dart';
 import 'enum.dart';
 import 'error.dart';
@@ -963,6 +964,29 @@
   }
 }
 
+class BitsType<T extends Bits> extends FidlType<T> {
+  const BitsType({
+    this.type,
+    this.ctor,
+  });
+
+  final FidlType<int> type;
+  final BitsFactory<T> ctor;
+
+  @override
+  int get encodedSize => type.encodedSize;
+
+  @override
+  void encode(Encoder encoder, T value, int offset) {
+    type.encode(encoder, value.$value, offset);
+  }
+
+  @override
+  T decode(Decoder decoder, int offset) {
+    return ctor(type.decode(decoder, offset));
+  }
+}
+
 class MethodType extends FidlType<Null> {
   const MethodType({
     this.request,