[VM runtime] Only print ambiguous class names in type errors (fixes #33006).

Change-Id: I6e5e0564d438c267f0ca2733dd7a63b09b54a1b1
Reviewed-on: https://dart-review.googlesource.com/54628
Reviewed-by: Siva Annamalai <asiva@google.com>
diff --git a/runtime/vm/exceptions.cc b/runtime/vm/exceptions.cc
index 3f577f6..b4bcec3 100644
--- a/runtime/vm/exceptions.cc
+++ b/runtime/vm/exceptions.cc
@@ -729,25 +729,19 @@
         pieces.Add(dst_name);
         pieces.Add(Symbols::SingleQuote());
       }
-      // Print URIs of src and dst types.
-      // Do not print "where" when no URIs get printed.
-      bool printed_where = false;
+      // Print ambiguous URIs of src and dst types.
+      URIs uris(zone, 12);
       if (!src_type.IsNull()) {
-        const String& uris = String::Handle(zone, src_type.EnumerateURIs());
-        if (uris.Length() > Symbols::SpaceIsFromSpace().Length()) {
-          printed_where = true;
-          pieces.Add(Symbols::SpaceWhereNewLine());
-          pieces.Add(uris);
-        }
+        src_type.EnumerateURIs(&uris);
       }
       if (!dst_type.IsDynamicType() && !dst_type.IsVoidType()) {
-        const String& uris = String::Handle(zone, dst_type.EnumerateURIs());
-        if (uris.Length() > Symbols::SpaceIsFromSpace().Length()) {
-          if (!printed_where) {
-            pieces.Add(Symbols::SpaceWhereNewLine());
-          }
-          pieces.Add(uris);
-        }
+        dst_type.EnumerateURIs(&uris);
+      }
+      const String& formatted_uris =
+          String::Handle(zone, AbstractType::PrintURIs(&uris));
+      if (formatted_uris.Length() > 0) {
+        pieces.Add(Symbols::SpaceWhereNewLine());
+        pieces.Add(formatted_uris);
       }
     }
   }
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 3476896..caa190c 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -5468,20 +5468,18 @@
   return result.raw();
 }
 
-RawString* TypeArguments::EnumerateURIs() const {
+void TypeArguments::EnumerateURIs(URIs* uris) const {
   if (IsNull()) {
-    return Symbols::Empty().raw();
+    return;
   }
   Thread* thread = Thread::Current();
   Zone* zone = thread->zone();
   AbstractType& type = AbstractType::Handle(zone);
   const intptr_t num_types = Length();
-  const Array& pieces = Array::Handle(zone, Array::New(num_types));
   for (intptr_t i = 0; i < num_types; i++) {
     type = TypeAt(i);
-    pieces.SetAt(i, String::Handle(zone, type.EnumerateURIs()));
+    type.EnumerateURIs(uris);
   }
-  return String::ConcatAll(pieces);
 }
 
 const char* TypeArguments::ToCString() const {
@@ -16346,10 +16344,9 @@
   return NULL;
 }
 
-RawString* AbstractType::EnumerateURIs() const {
+void AbstractType::EnumerateURIs(URIs* uris) const {
   // AbstractType is an abstract class.
   UNREACHABLE();
-  return NULL;
 }
 
 RawAbstractType* AbstractType::OnlyBuddyInTrail(TrailPtr trail) const {
@@ -16421,6 +16418,52 @@
   return false;
 }
 
+void AbstractType::AddURI(URIs* uris, const String& name, const String& uri) {
+  ASSERT(uris != NULL);
+  const intptr_t len = uris->length();
+  ASSERT((len % 3) == 0);
+  bool print_uri = false;
+  for (intptr_t i = 0; i < len; i += 3) {
+    if (uris->At(i).Equals(name)) {
+      if (uris->At(i + 1).Equals(uri)) {
+        // Same name and same URI: no need to add this already listed URI.
+        return;  // No state change is possible.
+      } else {
+        // Same name and different URI: the name is ambiguous, print both URIs.
+        print_uri = true;
+        uris->SetAt(i + 2, Symbols::print());
+      }
+    }
+  }
+  uris->Add(name);
+  uris->Add(uri);
+  if (print_uri) {
+    uris->Add(Symbols::print());
+  } else {
+    uris->Add(Symbols::Empty());
+  }
+}
+
+RawString* AbstractType::PrintURIs(URIs* uris) {
+  ASSERT(uris != NULL);
+  Thread* thread = Thread::Current();
+  Zone* zone = thread->zone();
+  const intptr_t len = uris->length();
+  ASSERT((len % 3) == 0);
+  GrowableHandlePtrArray<const String> pieces(zone, 5 * (len / 3));
+  for (intptr_t i = 0; i < len; i += 3) {
+    // Only print URIs that have been marked.
+    if (uris->At(i + 2).raw() == Symbols::print().raw()) {
+      pieces.Add(Symbols::TwoSpaces());
+      pieces.Add(uris->At(i));
+      pieces.Add(Symbols::SpaceIsFromSpace());
+      pieces.Add(uris->At(i + 1));
+      pieces.Add(Symbols::NewLine());
+    }
+  }
+  return Symbols::FromConcatAll(thread, pieces);
+}
+
 RawString* AbstractType::BuildName(NameVisibility name_visibility) const {
   ASSERT(name_visibility != kScrubbedName);
   Thread* thread = Thread::Current();
@@ -17723,13 +17766,12 @@
 }
 #endif  // DEBUG
 
-RawString* Type::EnumerateURIs() const {
+void Type::EnumerateURIs(URIs* uris) const {
   if (IsDynamicType() || IsVoidType()) {
-    return Symbols::Empty().raw();
+    return;
   }
   Thread* thread = Thread::Current();
   Zone* zone = thread->zone();
-  GrowableHandlePtrArray<const String> pieces(zone, 6);
   if (IsFunctionType()) {
     // The scope class and type arguments do not appear explicitly in the user
     // visible name. The type arguments were used to instantiate the function
@@ -17737,26 +17779,22 @@
     const Function& sig_fun = Function::Handle(zone, signature());
     AbstractType& type = AbstractType::Handle(zone);
     const intptr_t num_params = sig_fun.NumParameters();
-    GrowableHandlePtrArray<const String> pieces(zone, num_params + 1);
     for (intptr_t i = 0; i < num_params; i++) {
       type = sig_fun.ParameterTypeAt(i);
-      pieces.Add(String::Handle(zone, type.EnumerateURIs()));
+      type.EnumerateURIs(uris);
     }
     // Handle result type last, since it appears last in the user visible name.
     type = sig_fun.result_type();
-    pieces.Add(String::Handle(zone, type.EnumerateURIs()));
+    type.EnumerateURIs(uris);
   } else {
     const Class& cls = Class::Handle(zone, type_class());
-    pieces.Add(Symbols::TwoSpaces());
-    pieces.Add(String::Handle(zone, cls.UserVisibleName()));
-    pieces.Add(Symbols::SpaceIsFromSpace());
+    const String& name = String::Handle(zone, cls.UserVisibleName());
     const Library& library = Library::Handle(zone, cls.library());
-    pieces.Add(String::Handle(zone, library.url()));
-    pieces.Add(Symbols::NewLine());
+    const String& uri = String::Handle(zone, library.url());
+    AddURI(uris, name, uri);
     const TypeArguments& type_args = TypeArguments::Handle(zone, arguments());
-    pieces.Add(String::Handle(zone, type_args.EnumerateURIs()));
+    type_args.EnumerateURIs(uris);
   }
-  return Symbols::FromConcatAll(thread, pieces);
 }
 
 intptr_t Type::ComputeHash() const {
@@ -17993,22 +18031,17 @@
 }
 #endif  // DEBUG
 
-RawString* TypeRef::EnumerateURIs() const {
+void TypeRef::EnumerateURIs(URIs* uris) const {
   Thread* thread = Thread::Current();
   Zone* zone = thread->zone();
   const AbstractType& ref_type = AbstractType::Handle(zone, type());
   ASSERT(!ref_type.IsDynamicType() && !ref_type.IsVoidType());
-  GrowableHandlePtrArray<const String> pieces(zone, 6);
   const Class& cls = Class::Handle(zone, ref_type.type_class());
-  pieces.Add(Symbols::TwoSpaces());
-  pieces.Add(String::Handle(zone, cls.UserVisibleName()));
-  // Break cycle by not printing type arguments, but '<optimized out>' instead.
-  pieces.Add(Symbols::OptimizedOut());
-  pieces.Add(Symbols::SpaceIsFromSpace());
+  const String& name = String::Handle(zone, cls.UserVisibleName());
   const Library& library = Library::Handle(zone, cls.library());
-  pieces.Add(String::Handle(zone, library.url()));
-  pieces.Add(Symbols::NewLine());
-  return Symbols::FromConcatAll(thread, pieces);
+  const String& uri = String::Handle(zone, library.url());
+  AddURI(uris, name, uri);
+  // Break cycle by not printing type arguments.
 }
 
 intptr_t TypeRef::Hash() const {
@@ -18225,12 +18258,10 @@
           *bound_error, script, token_pos(), Report::AtLocation,
           Report::kMalboundedType, Heap::kNew,
           "type parameter '%s' of class '%s' must extend bound '%s', "
-          "but type argument '%s' is not a subtype of '%s' where\n%s%s",
+          "but type argument '%s' is not a subtype of '%s'",
           type_param_name.ToCString(), class_name.ToCString(),
           declared_bound_name.ToCString(), bounded_type_name.ToCString(),
-          upper_bound_name.ToCString(),
-          String::Handle(bounded_type.EnumerateURIs()).ToCString(),
-          String::Handle(upper_bound.EnumerateURIs()).ToCString());
+          upper_bound_name.ToCString());
     }
   }
   return false;
@@ -18279,11 +18310,10 @@
   return clone.raw();
 }
 
-RawString* TypeParameter::EnumerateURIs() const {
+void TypeParameter::EnumerateURIs(URIs* uris) const {
   Thread* thread = Thread::Current();
   Zone* zone = thread->zone();
   GrowableHandlePtrArray<const String> pieces(zone, 4);
-  pieces.Add(Symbols::TwoSpaces());
   pieces.Add(String::Handle(zone, name()));
   Class& cls = Class::Handle(zone, parameterized_class());
   if (cls.IsNull()) {
@@ -18296,12 +18326,12 @@
   if (!cls.IsNull()) {
     pieces.Add(Symbols::SpaceOfSpace());
     pieces.Add(String::Handle(zone, cls.UserVisibleName()));
-    pieces.Add(Symbols::SpaceIsFromSpace());
+    const String& name =
+        String::Handle(zone, Symbols::FromConcatAll(thread, pieces));
     const Library& library = Library::Handle(zone, cls.library());
-    pieces.Add(String::Handle(zone, library.url()));
+    const String& uri = String::Handle(zone, library.url());
+    AddURI(uris, name, uri);
   }
-  pieces.Add(Symbols::NewLine());
-  return Symbols::FromConcatAll(thread, pieces);
 }
 
 intptr_t TypeParameter::ComputeHash() const {
@@ -18579,9 +18609,9 @@
   return BoundedType::New(bounded_type, upper_bound, type_param);
 }
 
-RawString* BoundedType::EnumerateURIs() const {
+void BoundedType::EnumerateURIs(URIs* uris) const {
   // The bound does not appear in the user visible name.
-  return AbstractType::Handle(type()).EnumerateURIs();
+  AbstractType::Handle(type()).EnumerateURIs(uris);
 }
 
 intptr_t BoundedType::ComputeHash() const {
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index c23eda4..a422fe0 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -903,6 +903,12 @@
 typedef ZoneGrowableHandlePtrArray<const AbstractType> Trail;
 typedef ZoneGrowableHandlePtrArray<const AbstractType>* TrailPtr;
 
+// A URIs array contains triplets of strings.
+// The first string in the triplet is a type name (usually a class).
+// The second string in the triplet is the URI of the type.
+// The third string in the triplet is "print" if the triplet should be printed.
+typedef ZoneGrowableHandlePtrArray<const String> URIs;
+
 class Class : public Object {
  public:
   intptr_t instance_size() const {
@@ -5817,8 +5823,9 @@
   // Canonicalize only if instantiated, otherwise returns 'this'.
   RawTypeArguments* Canonicalize(TrailPtr trail = NULL) const;
 
-  // Returns a formatted list of occurring type arguments with their URI.
-  RawString* EnumerateURIs() const;
+  // Add the class name and URI of each type argument of this vector to the uris
+  // list and mark ambiguous triplets to be printed.
+  void EnumerateURIs(URIs* uris) const;
 
   // Return 'this' if this type argument vector is instantiated, i.e. if it does
   // not refer to type parameters. Otherwise, return a new type argument vector
@@ -6031,6 +6038,12 @@
   // The receiver may be added several times, each time with a different buddy.
   bool TestAndAddBuddyToTrail(TrailPtr* trail, const AbstractType& buddy) const;
 
+  // Add the pair <name, uri> to the list, if not already present.
+  static void AddURI(URIs* uris, const String& name, const String& uri);
+
+  // Return a formatted string of the uris.
+  static RawString* PrintURIs(URIs* uris);
+
   // The name of this type, including the names of its type arguments, if any.
   virtual RawString* Name() const { return BuildName(kInternalName); }
 
@@ -6040,8 +6053,9 @@
     return BuildName(kUserVisibleName);
   }
 
-  // Returns a formatted list of occurring types with their URI.
-  virtual RawString* EnumerateURIs() const;
+  // Add the class name and URI of each occuring type to the uris
+  // list and mark ambiguous triplets to be printed.
+  virtual void EnumerateURIs(URIs* uris) const;
 
   virtual intptr_t Hash() const;
 
@@ -6247,7 +6261,7 @@
   // Check if type is canonical.
   virtual bool CheckIsCanonical(Thread* thread) const;
 #endif  // DEBUG
-  virtual RawString* EnumerateURIs() const;
+  virtual void EnumerateURIs(URIs* uris) const;
 
   virtual intptr_t Hash() const;
 
@@ -6400,7 +6414,7 @@
   // Check if typeref is canonical.
   virtual bool CheckIsCanonical(Thread* thread) const;
 #endif  // DEBUG
-  virtual RawString* EnumerateURIs() const;
+  virtual void EnumerateURIs(URIs* uris) const;
 
   virtual intptr_t Hash() const;
 
@@ -6490,7 +6504,7 @@
   // Check if type parameter is canonical.
   virtual bool CheckIsCanonical(Thread* thread) const { return true; }
 #endif  // DEBUG
-  virtual RawString* EnumerateURIs() const;
+  virtual void EnumerateURIs(URIs* uris) const;
 
   virtual intptr_t Hash() const;
 
@@ -6596,7 +6610,7 @@
   // Check if bounded type is canonical.
   virtual bool CheckIsCanonical(Thread* thread) const { return true; }
 #endif  // DEBUG
-  virtual RawString* EnumerateURIs() const;
+  virtual void EnumerateURIs(URIs* uris) const;
 
   virtual intptr_t Hash() const;