Merge changes from internal repo. (#70)

* Ensure generated extension class names don't conflict with message
  class names.

* Function will soon be a reserved keyword, so don't generate classes
  with that name.

* Strong mode tweaks.
diff --git a/Makefile b/Makefile
index bd370d4..77713a9 100644
--- a/Makefile
+++ b/Makefile
@@ -19,6 +19,7 @@
 	dart_options \
 	descriptor_2_5_opensource \
 	enum_extension \
+	ExtensionNameConflict \
 	map_api \
 	map_api2 \
 	mixins \
diff --git a/lib/file_generator.dart b/lib/file_generator.dart
index a424744..631d35d 100644
--- a/lib/file_generator.dart
+++ b/lib/file_generator.dart
@@ -166,11 +166,6 @@
     return index == -1 ? fileName : fileName.substring(0, index);
   }
 
-  String _generateClassName(Uri protoFilePath) {
-    String s = _fileNameWithoutExtension(protoFilePath).replaceAll('-', '_');
-    return '${s[0].toUpperCase()}${s.substring(1)}';
-  }
-
   /// Generates all the Dart files for this .proto file.
   List<CodeGeneratorResponse_File> generateFiles(OutputConfiguration config) {
     if (!_linked) throw new StateError("not linked");
@@ -208,7 +203,7 @@
     // name derived from the file name.
     if (!extensionGenerators.isEmpty) {
       // TODO(antonm): do not generate a class.
-      String className = _generateClassName(protoFileUri);
+      String className = extensionClassName(descriptor);
       out.addBlock('class $className {', '}\n', () {
         for (ExtensionGenerator x in extensionGenerators) {
           x.generate(out);
@@ -233,6 +228,11 @@
       [OutputConfiguration config = const DefaultOutputConfiguration()]) {
     _writeLibraryHeading(out);
 
+    // Make sure any other symbols in dart:core don't cause name conflicts with
+    // protobuf classes that have the same name.
+    out.println("// ignore: UNUSED_SHOWN_NAME\n"
+        "import 'dart:core' show int, bool, double, String, List, override;");
+
     // We only add the dart:async import if there are services in the
     // FileDescriptorProto.
     if (descriptor.service.isNotEmpty) {
@@ -347,6 +347,10 @@
     _writeLibraryHeading(out, "pbenum");
 
     if (enumCount > 0) {
+      // Make sure any other symbols in dart:core don't cause name conflicts
+      // with enums that have the same name.
+      out.println("// ignore: UNUSED_SHOWN_NAME\n"
+          "import 'dart:core' show int, dynamic, String, List, Map;");
       out.println("import 'package:protobuf/protobuf.dart';");
       out.println();
     }
diff --git a/lib/message_generator.dart b/lib/message_generator.dart
index 7e1d2c1..9531dbc 100644
--- a/lib/message_generator.dart
+++ b/lib/message_generator.dart
@@ -24,8 +24,14 @@
     return mixin;
   }
 
+  /// The name of the Dart class to generate.
   final String classname;
+
+  /// The fully-qualified name of the message type.
+  ///
+  /// (Used as a unique key and in error messages, not in Dart code.)
   final String fqname;
+
   final PbMixin mixin;
 
   final ProtobufContainer _parent;
@@ -41,9 +47,7 @@
       Map<String, PbMixin> declaredMixins, PbMixin defaultMixin)
       : _descriptor = descriptor,
         _parent = parent,
-        classname = (parent.classname == '')
-            ? descriptor.name
-            : '${parent.classname}_${descriptor.name}',
+        classname = messageClassName(descriptor, parent: parent.classname),
         fqname = (parent == null || parent.fqname == null)
             ? descriptor.name
             : (parent.fqname == '.'
diff --git a/lib/names.dart b/lib/names.dart
index b73665f..b25b03c 100644
--- a/lib/names.dart
+++ b/lib/names.dart
@@ -49,6 +49,41 @@
   return _unusedMemberNames(descriptor, null, existingNames).fieldName;
 }
 
+/// Chooses the name of the Dart class holding top-level extensions.
+String extensionClassName(FileDescriptorProto descriptor) {
+  var taken = new Set<String>();
+  for (var messageType in descriptor.messageType) {
+    taken.add(messageClassName(messageType));
+  }
+
+  String s = _fileNameWithoutExtension(descriptor).replaceAll('-', '_');
+  String candidate = '${s[0].toUpperCase()}${s.substring(1)}';
+
+  if (!taken.contains(candidate)) {
+    return candidate;
+  }
+
+  // Found a conflict; try again.
+  candidate = "${candidate}Ext";
+  if (!taken.contains(candidate)) {
+    return candidate;
+  }
+
+  // Next, try numbers.
+  int suffix = 2;
+  while (taken.contains("$candidate$suffix")) {
+    suffix++;
+  }
+  return "$candidate$suffix";
+}
+
+String _fileNameWithoutExtension(FileDescriptorProto descriptor) {
+  Uri path = new Uri.file(descriptor.name);
+  String fileName = path.pathSegments.last;
+  int dot = fileName.lastIndexOf(".");
+  return dot == -1 ? fileName : fileName.substring(0, dot);
+}
+
 // Exception thrown when a field has an invalid 'dart_name' option.
 class DartNameOptionException implements Exception {
   final String message;
@@ -56,6 +91,24 @@
   String toString() => "$message";
 }
 
+/// Chooses the name of the Dart class to generate for a protobuf message.
+///
+/// For a nested message, [parent] should be provided
+/// with the name of the Dart class for the immediate parent.
+String messageClassName(DescriptorProto descriptor, {String parent: ''}) {
+  var name = descriptor.name;
+  if (parent != '') {
+    name = '${parent}_${descriptor.name}';
+  }
+  if (name == 'Function') {
+    name = 'Function_'; // Avoid reserved word.
+  } else if (name.startsWith('Function_')) {
+    // Avoid any further name conflicts due to 'Function' rename (unlikely).
+    name = name + '_';
+  }
+  return name;
+}
+
 /// Chooses the GeneratedMessage member names for each field.
 ///
 /// Additional names to avoid can be supplied using [reserved].
diff --git a/lib/service_generator.dart b/lib/service_generator.dart
index e061240..75d8e0c 100644
--- a/lib/service_generator.dart
+++ b/lib/service_generator.dart
@@ -177,7 +177,7 @@
   }
 
   /// Hook for generating members added in subclasses.
-  void _generateMoreClassMembers(out) {}
+  void _generateMoreClassMembers(IndentingWriter out) {}
 
   void generate(IndentingWriter out) {
     out.addBlock(
@@ -189,7 +189,7 @@
       _generateDispatchMethod(out);
       _generateMoreClassMembers(out);
       out.println("Map<String, dynamic> get \$json => $jsonConstant;");
-      out.println("Map<String, dynamic> get \$messageJson =>"
+      out.println("Map<String, Map<String, dynamic>> get \$messageJson =>"
           " $messageJsonConstant;");
     });
     out.println();
diff --git a/pubspec.yaml b/pubspec.yaml
index 4acb45c..217bfad 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: protoc_plugin
-version: 0.7.0-dev
+version: 0.7.1
 author: Dart Team <misc@dartlang.org>
 description: Protoc compiler plugin to generate Dart code
 homepage: https://github.com/dart-lang/dart-protoc-plugin
@@ -8,7 +8,7 @@
 dependencies:
   fixnum: ^0.10.5
   path: ^1.0.0
-  protobuf: ^0.5.2
+  protobuf: ^0.5.4
 dev_dependencies:
   browser: any
   test: ^0.12.0
diff --git a/test/all_tests.dart b/test/all_tests.dart
index 3e6cbf9..e44667e 100755
--- a/test/all_tests.dart
+++ b/test/all_tests.dart
@@ -19,6 +19,7 @@
 import 'message_generator_test.dart' as message_generator;
 import 'message_test.dart' as message;
 import 'mixin_test.dart' as mixin;
+import 'names_test.dart' as names;
 import 'protoc_options_test.dart' as protoc_options;
 import 'repeated_field_test.dart' as repeated_field;
 import 'service_test.dart' as service;
@@ -42,6 +43,7 @@
   message_generator.main();
   message.main();
   mixin.main();
+  names.main();
   protoc_options.main();
   repeated_field.main();
   service.main();
diff --git a/test/extension_test.dart b/test/extension_test.dart
index fb56da7..8829733 100644
--- a/test/extension_test.dart
+++ b/test/extension_test.dart
@@ -11,6 +11,7 @@
 import '../out/protos/enum_extension.pb.dart';
 import '../out/protos/nested_extension.pb.dart';
 import '../out/protos/non_nested_extension.pb.dart';
+import '../out/protos/ExtensionNameConflict.pb.dart';
 
 import 'test_util.dart';
 
diff --git a/test/file_generator_test.dart b/test/file_generator_test.dart
index 0a9dbfd..50eb849 100644
--- a/test/file_generator_test.dart
+++ b/test/file_generator_test.dart
@@ -78,6 +78,8 @@
 ///
 library test;
 
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
 import 'package:protobuf/protobuf.dart';
 
 class PhoneNumber extends GeneratedMessage {
@@ -165,6 +167,8 @@
 ///
 library test;
 
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
 export 'test.pbenum.dart';
 
 ''';
@@ -175,6 +179,8 @@
 ///
 library test_pbenum;
 
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, dynamic, String, List, Map;
 import 'package:protobuf/protobuf.dart';
 
 class PhoneType extends ProtobufEnum {
@@ -248,6 +254,8 @@
 ///
 library pb_library_test;
 
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
 import 'package:protobuf/protobuf.dart';
 
 ''';
@@ -271,6 +279,8 @@
 ///
 library test;
 
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
 import 'package:fixnum/fixnum.dart';
 import 'package:protobuf/protobuf.dart';
 
@@ -304,6 +314,8 @@
 ///
 library test;
 
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
 import 'dart:async';
 
 import 'package:protobuf/protobuf.dart';
@@ -377,7 +389,7 @@
   }
 
   Map<String, dynamic> get $json => Test$json;
-  Map<String, dynamic> get $messageJson => Test$messageJson;
+  Map<String, Map<String, dynamic>> get $messageJson => Test$messageJson;
 }
 
 ''';
@@ -416,6 +428,8 @@
 ///
 library test;
 
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
 import 'package:protobuf/protobuf.dart';
 
 import 'package1.pb.dart' as p1;
diff --git a/test/indenting_writer_test.dart b/test/indenting_writer_test.dart
index 5bbfeba..61f0427 100755
--- a/test/indenting_writer_test.dart
+++ b/test/indenting_writer_test.dart
@@ -17,9 +17,7 @@
       out.println('second;');
     });
 
-    expect(
-        out.toString(),
-        '''
+    expect(out.toString(), '''
 class test {
   first;
 
diff --git a/test/json_test.dart b/test/json_test.dart
index 155421f..b698eac 100755
--- a/test/json_test.dart
+++ b/test/json_test.dart
@@ -13,20 +13,20 @@
 import 'test_util.dart';
 
 void main() {
-  final String TEST_ALL_TYPES_JSON = '{"1":101,"2":102,"3":103,"4":104,'
-      '"5":105,"6":106,"7":107,"8":108,"9":109,"10":110,"11":111.0,'
+  final String TEST_ALL_TYPES_JSON = '{"1":101,"2":"102","3":103,"4":"104",'
+      '"5":105,"6":"106","7":107,"8":"108","9":109,"10":"110","11":111.0,'
       '"12":112.0,"13":true,"14":"115","15":"MTE2","16":{"17":117},'
       '"18":{"1":118},"19":{"1":119},"20":{"1":120},"21":3,"22":6,"23":9,'
-      '"24":"124","25":"125","31":[201,301],"32":[202,302],'
-      '"33":[203,303],"34":[204,304],"35":[205,305],"36":[206,306],'
-      '"37":[207,307],"38":[208,308],"39":[209,309],"40":[210,310],'
+      '"24":"124","25":"125","31":[201,301],"32":["202","302"],'
+      '"33":[203,303],"34":["204","304"],"35":[205,305],"36":["206","306"],'
+      '"37":[207,307],"38":["208","308"],"39":[209,309],"40":["210","310"],'
       '"41":[211.0,311.0],"42":[212.0,312.0],"43":[true,false],'
       '"44":["215","315"],"45":["MjE2","MzE2"],"46":[{"47":217},{"47":317}],'
       '"48":[{"1":218},{"1":318}],"49":[{"1":219},{"1":319}],'
       '"50":[{"1":220},{"1":320}],"51":[2,3],"52":[5,6],"53":[8,9],'
-      '"54":["224","324"],"55":["225","325"],"61":401,"62":402,"63":403,'
-      '"64":404,"65":405,"66":406,"67":407,"68":408,"69":409,'
-      '"70":410,"71":411.0,"72":412.0,"73":false,"74":"415","75":"NDE2",'
+      '"54":["224","324"],"55":["225","325"],"61":401,"62":"402","63":403,'
+      '"64":"404","65":405,"66":"406","67":407,"68":"408","69":409,'
+      '"70":"410","71":411.0,"72":412.0,"73":false,"74":"415","75":"NDE2",'
       '"81":1,"82":4,"83":7,"84":"424","85":"425"}';
 
   /**
@@ -50,15 +50,15 @@
     expect(getAllSet()..optionalInt32 = -1234567,
         expectedJson(':101,', ':-1234567,'));
 
-    // 64-bit numbers outside 53-bit range are quoted.
+    // All 64-bit numbers are quoted.
     expect(getAllSet()..optionalInt64 = make64(0, 0x200000),
-        expectedJson(':102,', ':9007199254740992,'));
+           expectedJson(':"102",', ':"9007199254740992",'));
     expect(getAllSet()..optionalInt64 = make64(1, 0x200000),
-        expectedJson(':102,', ':"9007199254740993",'));
+           expectedJson(':"102",', ':"9007199254740993",'));
     expect(getAllSet()..optionalInt64 = -make64(0, 0x200000),
-        expectedJson(':102,', ':-9007199254740992,'));
+           expectedJson(':"102",', ':"-9007199254740992",'));
     expect(getAllSet()..optionalInt64 = -make64(1, 0x200000),
-        expectedJson(':102,', ':"-9007199254740993",'));
+           expectedJson(':"102",', ':"-9007199254740993",'));
 
     // Quotes, backslashes, and control characters in strings are quoted.
     expect(getAllSet()..optionalString = 'a\u0000b\u0001cd\\e\"fg',
@@ -119,4 +119,28 @@
     expect(new TestAllExtensions.fromJson(TEST_ALL_TYPES_JSON, registry),
         getAllExtensionsSet());
   });
+
+  test('testUnknownEnumValueInOptionalField', () {
+    // optional NestedEnum optional_nested_enum = 21;
+    var message = new TestAllTypes.fromJson('{"21": 4}');
+    // 4 is an unknown value.
+    expect(message.optionalNestedEnum, equals(TestAllTypes_NestedEnum.FOO));
+  });
+
+  test('testUnknownEnumValueInRepeatedField', () {
+    // repeated NestedEnum repeated_nested_enum = 51;
+    var message = new TestAllTypes.fromJson('{"51": [4]}');
+    // 4 is an unknown value.
+    expect(message.repeatedNestedEnum, isEmpty);
+
+    // 1 (FOO) and 2 (BAR) are known values.
+    message = new TestAllTypes.fromJson('{"51": [1, 4, 2, 4, 1, 4]}');
+    expect(
+        message.repeatedNestedEnum,
+        equals([
+          TestAllTypes_NestedEnum.FOO,
+          TestAllTypes_NestedEnum.BAR,
+          TestAllTypes_NestedEnum.FOO
+        ]));
+  });
 }
diff --git a/test/names_test.dart b/test/names_test.dart
index 7f31ab8..95906c0 100644
--- a/test/names_test.dart
+++ b/test/names_test.dart
@@ -9,7 +9,7 @@
 import 'package:protoc_plugin/src/descriptor.pb.dart';
 import 'package:protoc_plugin/src/dart_options.pb.dart';
 
-import '../out/protos/dart_name.pb.dart';
+import '../out/protos/dart_name.pb.dart' as pb;
 
 Matcher throwsMessage(String msg) => throwsA(new _ToStringMatcher(equals(msg)));
 
@@ -21,7 +21,7 @@
 
 void main() {
   test('Can access a field that was renamed using dart_name option', () {
-    var msg = new DartName();
+    var msg = new pb.DartName();
     expect(msg.hasRenamedField(), false);
     msg.renamedField = 'test';
     expect(msg.hasRenamedField(), true);
@@ -31,7 +31,7 @@
   });
 
   test('Can swap field names using dart_name option', () {
-    var msg = new SwapNames();
+    var msg = new pb.SwapNames();
     msg.first = "one";
     msg.second = "two";
     expect(msg.getField(1), "two");
@@ -39,7 +39,7 @@
   });
 
   test("Can take another field's name using dart_name option", () {
-    var msg = new TakeExistingName();
+    var msg = new pb.TakeExistingName();
     msg.first = "one";
     expect(msg.getField(2), "one");
     msg.first_1 = "renamed";
@@ -81,6 +81,11 @@
         throwsMessage("Example.second: "
             "dart_name option is invalid: 'renamed' is already used"));
   });
+
+  test('message classes renamed to avoid Function keyword', () {
+    new pb.Function_()..fun = 'renamed';
+    new pb.Function__()..fun1 = 'also renamed';
+  });
 }
 
 FieldDescriptorProto stringField(String name, int number, String dartName) {
diff --git a/test/protos/ExtensionNameConflict.proto b/test/protos/ExtensionNameConflict.proto
new file mode 100644
index 0000000..21217eb
--- /dev/null
+++ b/test/protos/ExtensionNameConflict.proto
@@ -0,0 +1,16 @@
+syntax = "proto2";
+
+package protobuf_unittest;
+
+// Cause a conflict with the auto-generated extension class.
+message ExtensionNameConflict {
+}
+
+message AnotherMessageToBeExtended {
+  extensions 1 to max;
+}
+
+extend AnotherMessageToBeExtended {
+  optional int32 someExtension = 1;
+}
+
diff --git a/test/protos/dart_name.proto b/test/protos/dart_name.proto
index 33b157b..533d43c 100644
--- a/test/protos/dart_name.proto
+++ b/test/protos/dart_name.proto
@@ -29,3 +29,11 @@
     (dart_options.dart_name) = "first"
   ];
 }
+
+message Function {
+  optional string fun = 1;
+}
+
+message Function_ {
+  optional string fun1 = 1;
+}
diff --git a/test/protos/descriptor_2_5_opensource.proto b/test/protos/descriptor_2_5_opensource.proto
index a785f79..5832c61 100644
--- a/test/protos/descriptor_2_5_opensource.proto
+++ b/test/protos/descriptor_2_5_opensource.proto
@@ -1,3 +1,5 @@
+syntax = "proto2";
+
 // Protocol Buffers - Google's data interchange format
 // Copyright 2008 Google Inc.  All rights reserved.
 // http://code.google.com/p/protobuf/
diff --git a/test/service_generator_test.dart b/test/service_generator_test.dart
index a1c773e..b917228 100644
--- a/test/service_generator_test.dart
+++ b/test/service_generator_test.dart
@@ -35,7 +35,7 @@
   }
 
   Map<String, dynamic> get $json => Test$json;
-  Map<String, dynamic> get $messageJson => Test$messageJson;
+  Map<String, Map<String, dynamic>> get $messageJson => Test$messageJson;
 }
 
 ''';
diff --git a/test/service_test.dart b/test/service_test.dart
index 05edda0..39a8943 100644
--- a/test/service_test.dart
+++ b/test/service_test.dart
@@ -55,6 +55,7 @@
 
 class FakeJsonClient implements RpcClient {
   final FakeJsonServer server;
+
   FakeJsonClient(this.server);
 
   Future<GeneratedMessage> invoke(