From e1399582b85f86e8f5645d1325ef989bb2d2c458 Mon Sep 17 00:00:00 2001
From: Teon Banek <teon.banek@memgraph.io>
Date: Tue, 26 Nov 2019 12:46:29 +0100
Subject: [PATCH] Implement SatisfiesType on CypherType classes

Reviewers: mferencevic, ipaljak

Reviewed By: mferencevic

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2571
---
 src/query/procedure/cypher_types.hpp    | 127 +++++++++-
 tests/unit/query_procedure_mgp_type.cpp | 312 ++++++++++++++++++++++++
 2 files changed, 432 insertions(+), 7 deletions(-)

diff --git a/src/query/procedure/cypher_types.hpp b/src/query/procedure/cypher_types.hpp
index c5dd099b3..d614cf43b 100644
--- a/src/query/procedure/cypher_types.hpp
+++ b/src/query/procedure/cypher_types.hpp
@@ -1,10 +1,13 @@
 /// @file
 #pragma once
 
+#include "mg_procedure.h"
+
 #include <functional>
 #include <memory>
 #include <string_view>
 
+#include "query/typed_value.hpp"
 #include "utils/memory.hpp"
 #include "utils/pmr/string.hpp"
 
@@ -27,8 +30,11 @@ class CypherType {
   /// Get name of the type as it should be presented to the user.
   virtual std::string_view GetPresentableName() const = 0;
 
-  // TODO: Type checking
-  // virtual bool SatisfiesType(const mgp_value &) const = 0;
+  /// Return true if given mgp_value is of the type as described by `this`.
+  virtual bool SatisfiesType(const mgp_value &) const = 0;
+
+  /// Return true if given TypedValue is of the type as described by `this`.
+  virtual bool SatisfiesType(const query::TypedValue &) const = 0;
 
   // The following methods are a simple replacement for RTTI because we have
   // some special cases we need to handle.
@@ -44,36 +50,92 @@ using CypherTypePtr =
 class AnyType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "ANY"; }
+
+  bool SatisfiesType(const mgp_value &value) const override {
+    return !mgp_value_is_null(&value);
+  }
+
+  bool SatisfiesType(const query::TypedValue &value) const override {
+    return !value.IsNull();
+  }
 };
 
 class BoolType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "BOOLEAN"; }
+
+  bool SatisfiesType(const mgp_value &value) const override {
+    return mgp_value_is_bool(&value);
+  }
+
+  bool SatisfiesType(const query::TypedValue &value) const override {
+    return value.IsBool();
+  }
 };
 
 class StringType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "STRING"; }
+
+  bool SatisfiesType(const mgp_value &value) const override {
+    return mgp_value_is_string(&value);
+  }
+
+  bool SatisfiesType(const query::TypedValue &value) const override {
+    return value.IsString();
+  }
 };
 
 class IntType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "INTEGER"; }
+
+  bool SatisfiesType(const mgp_value &value) const override {
+    return mgp_value_is_int(&value);
+  }
+
+  bool SatisfiesType(const query::TypedValue &value) const override {
+    return value.IsInt();
+  }
 };
 
 class FloatType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "FLOAT"; }
+
+  bool SatisfiesType(const mgp_value &value) const override {
+    return mgp_value_is_double(&value);
+  }
+
+  bool SatisfiesType(const query::TypedValue &value) const override {
+    return value.IsDouble();
+  }
 };
 
 class NumberType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "NUMBER"; }
+
+  bool SatisfiesType(const mgp_value &value) const override {
+    return mgp_value_is_int(&value) || mgp_value_is_double(&value);
+  }
+
+  bool SatisfiesType(const query::TypedValue &value) const override {
+    return value.IsInt() || value.IsDouble();
+  }
 };
 
 class NodeType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "NODE"; }
+
+  bool SatisfiesType(const mgp_value &value) const override {
+    return mgp_value_is_vertex(&value);
+  }
+
+  bool SatisfiesType(const query::TypedValue &value) const override {
+    return value.IsVertex();
+  }
 };
 
 class RelationshipType : public CypherType {
@@ -81,11 +143,27 @@ class RelationshipType : public CypherType {
   std::string_view GetPresentableName() const override {
     return "RELATIONSHIP";
   }
+
+  bool SatisfiesType(const mgp_value &value) const override {
+    return mgp_value_is_edge(&value);
+  }
+
+  bool SatisfiesType(const query::TypedValue &value) const override {
+    return value.IsEdge();
+  }
 };
 
 class PathType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "PATH"; }
+
+  bool SatisfiesType(const mgp_value &value) const override {
+    return mgp_value_is_path(&value);
+  }
+
+  bool SatisfiesType(const query::TypedValue &value) const override {
+    return value.IsPath();
+  }
 };
 
 // TODO: There's also Temporal Types, but we currently do not support those.
@@ -98,26 +176,53 @@ class PathType : public CypherType {
 class MapType : public CypherType {
  public:
   std::string_view GetPresentableName() const override { return "MAP"; }
+
+  bool SatisfiesType(const mgp_value &value) const override {
+    return mgp_value_is_map(&value) || mgp_value_is_vertex(&value) ||
+           mgp_value_is_edge(&value);
+  }
+
+  bool SatisfiesType(const query::TypedValue &value) const override {
+    return value.IsMap() || value.IsVertex() || value.IsEdge();
+  }
 };
 
 // Composite Types
 
 class ListType : public CypherType {
  public:
-  CypherTypePtr type_;
+  CypherTypePtr element_type_;
   utils::pmr::string presentable_name_;
 
   /// @throw std::bad_alloc
   /// @throw std::length_error
-  explicit ListType(CypherTypePtr type, utils::MemoryResource *memory)
-      : type_(std::move(type)), presentable_name_("LIST OF ", memory) {
-    presentable_name_.append(type_->GetPresentableName());
+  explicit ListType(CypherTypePtr element_type, utils::MemoryResource *memory)
+      : element_type_(std::move(element_type)),
+        presentable_name_("LIST OF ", memory) {
+    presentable_name_.append(element_type_->GetPresentableName());
   }
 
   std::string_view GetPresentableName() const override {
     return presentable_name_;
   }
 
+  bool SatisfiesType(const mgp_value &value) const override {
+    if (!mgp_value_is_list(&value)) return false;
+    const auto *list = mgp_value_get_list(&value);
+    for (size_t i = 0; i < mgp_list_size(list); ++i) {
+      if (!element_type_->SatisfiesType(*mgp_list_at(list, i))) return false;
+    }
+    return true;
+  }
+
+  bool SatisfiesType(const query::TypedValue &value) const override {
+    if (!value.IsList()) return false;
+    for (const auto &elem : value.ValueList()) {
+      if (!element_type_->SatisfiesType(elem)) return false;
+    }
+    return true;
+  }
+
   const ListType *AsListType() const override { return this; }
 };
 
@@ -135,7 +240,7 @@ class NullableType : public CypherType {
     // ListType is specially formatted
     if (list_type) {
       presentable_name_.assign("LIST? OF ")
-          .append(list_type->type_->GetPresentableName());
+          .append(list_type->element_type_->GetPresentableName());
     } else {
       presentable_name_.assign(type_->GetPresentableName()).append("?");
     }
@@ -167,6 +272,14 @@ class NullableType : public CypherType {
     return presentable_name_;
   }
 
+  bool SatisfiesType(const mgp_value &value) const override {
+    return mgp_value_is_null(&value) || type_->SatisfiesType(value);
+  }
+
+  bool SatisfiesType(const query::TypedValue &value) const override {
+    return value.IsNull() || type_->SatisfiesType(value);
+  }
+
   const NullableType *AsNullableType() const override { return this; }
 };
 
diff --git a/tests/unit/query_procedure_mgp_type.cpp b/tests/unit/query_procedure_mgp_type.cpp
index 86ef0c174..1894bf199 100644
--- a/tests/unit/query_procedure_mgp_type.cpp
+++ b/tests/unit/query_procedure_mgp_type.cpp
@@ -56,3 +56,315 @@ TEST(CypherType, PresentableNameCompositeTypes) {
               "LIST? OF LIST? OF STRING?");
   }
 }
+
+TEST(CypherType, NullSatisfiesType) {
+  mgp_memory memory{utils::NewDeleteResource()};
+  {
+    auto *mgp_null = mgp_value_make_null(&memory);
+    const query::TypedValue tv_null;
+    std::vector<const mgp_type *> primitive_types{
+        mgp_type_any(), mgp_type_bool(),  mgp_type_string(),
+        mgp_type_int(), mgp_type_float(), mgp_type_number(),
+        mgp_type_map(), mgp_type_node(),  mgp_type_relationship(),
+        mgp_type_path()};
+    for (const auto *primitive_type : primitive_types) {
+      for (const auto *type :
+           {primitive_type, mgp_type_list(primitive_type),
+            mgp_type_list(mgp_type_nullable(primitive_type))}) {
+        EXPECT_FALSE(type->impl->SatisfiesType(*mgp_null));
+        EXPECT_FALSE(type->impl->SatisfiesType(tv_null));
+        const auto *null_type = mgp_type_nullable(type);
+        EXPECT_TRUE(null_type->impl->SatisfiesType(*mgp_null));
+        EXPECT_TRUE(null_type->impl->SatisfiesType(tv_null));
+      }
+    }
+  }
+}
+
+static void CheckSatisfiesTypesAndNullable(
+    const mgp_value *mgp_val, const query::TypedValue &tv,
+    const std::vector<const mgp_type *> &types) {
+  for (const auto *type : types) {
+    EXPECT_TRUE(type->impl->SatisfiesType(*mgp_val))
+        << type->impl->GetPresentableName();
+    EXPECT_TRUE(type->impl->SatisfiesType(tv));
+    const auto *null_type = mgp_type_nullable(type);
+    EXPECT_TRUE(null_type->impl->SatisfiesType(*mgp_val))
+        << null_type->impl->GetPresentableName();
+    EXPECT_TRUE(null_type->impl->SatisfiesType(tv));
+  }
+}
+
+static void CheckNotSatisfiesTypesAndListAndNullable(
+    const mgp_value *mgp_val, const query::TypedValue &tv,
+    const std::vector<const mgp_type *> &elem_types) {
+  for (const auto *elem_type : elem_types) {
+    for (const auto *type : {elem_type, mgp_type_list(elem_type),
+                             mgp_type_list(mgp_type_nullable(elem_type))}) {
+      EXPECT_FALSE(type->impl->SatisfiesType(*mgp_val))
+          << type->impl->GetPresentableName();
+      EXPECT_FALSE(type->impl->SatisfiesType(tv));
+      const auto *null_type = mgp_type_nullable(type);
+      EXPECT_FALSE(null_type->impl->SatisfiesType(*mgp_val))
+          << null_type->impl->GetPresentableName();
+      EXPECT_FALSE(null_type->impl->SatisfiesType(tv));
+    }
+  }
+}
+
+TEST(CypherType, BoolSatisfiesType) {
+  mgp_memory memory{utils::NewDeleteResource()};
+  auto *mgp_bool = mgp_value_make_bool(1, &memory);
+  const query::TypedValue tv_bool(true);
+  CheckSatisfiesTypesAndNullable(mgp_bool, tv_bool,
+                                 {mgp_type_any(), mgp_type_bool()});
+  CheckNotSatisfiesTypesAndListAndNullable(
+      mgp_bool, tv_bool,
+      {mgp_type_string(), mgp_type_int(), mgp_type_float(), mgp_type_number(),
+       mgp_type_map(), mgp_type_node(), mgp_type_relationship(),
+       mgp_type_path()});
+}
+
+TEST(CypherType, IntSatisfiesType) {
+  mgp_memory memory{utils::NewDeleteResource()};
+  auto *mgp_int = mgp_value_make_int(42, &memory);
+  const query::TypedValue tv_int(42);
+  CheckSatisfiesTypesAndNullable(
+      mgp_int, tv_int, {mgp_type_any(), mgp_type_int(), mgp_type_number()});
+  CheckNotSatisfiesTypesAndListAndNullable(
+      mgp_int, tv_int,
+      {mgp_type_bool(), mgp_type_string(), mgp_type_float(), mgp_type_map(),
+       mgp_type_node(), mgp_type_relationship(), mgp_type_path()});
+}
+
+TEST(CypherType, DoubleSatisfiesType) {
+  mgp_memory memory{utils::NewDeleteResource()};
+  auto *mgp_double = mgp_value_make_double(42, &memory);
+  const query::TypedValue tv_double(42.0);
+  CheckSatisfiesTypesAndNullable(
+      mgp_double, tv_double,
+      {mgp_type_any(), mgp_type_float(), mgp_type_number()});
+  CheckNotSatisfiesTypesAndListAndNullable(
+      mgp_double, tv_double,
+      {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_map(),
+       mgp_type_node(), mgp_type_relationship(), mgp_type_path()});
+}
+
+TEST(CypherType, StringSatisfiesType) {
+  mgp_memory memory{utils::NewDeleteResource()};
+  auto *mgp_string = mgp_value_make_string("text", &memory);
+  const query::TypedValue tv_string("text");
+  CheckSatisfiesTypesAndNullable(mgp_string, tv_string,
+                                 {mgp_type_any(), mgp_type_string()});
+  CheckNotSatisfiesTypesAndListAndNullable(
+      mgp_string, tv_string,
+      {mgp_type_bool(), mgp_type_int(), mgp_type_float(), mgp_type_number(),
+       mgp_type_map(), mgp_type_node(), mgp_type_relationship(),
+       mgp_type_path()});
+}
+
+TEST(CypherType, MapSatisfiesType) {
+  mgp_memory memory{utils::NewDeleteResource()};
+  auto *map = mgp_map_make_empty(&memory);
+  mgp_map_insert(map, "key", mgp_value_make_int(42, &memory));
+  auto *mgp_map_v = mgp_value_make_map(map);
+  const query::TypedValue tv_map(
+      std::map<std::string, query::TypedValue>{{"key", query::TypedValue(42)}});
+  CheckSatisfiesTypesAndNullable(mgp_map_v, tv_map,
+                                 {mgp_type_any(), mgp_type_map()});
+  CheckNotSatisfiesTypesAndListAndNullable(
+      mgp_map_v, tv_map,
+      {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(),
+       mgp_type_number(), mgp_type_node(), mgp_type_relationship(),
+       mgp_type_path()});
+}
+
+TEST(CypherType, VertexSatisfiesType) {
+#ifdef MG_SINGLE_NODE_V2
+  storage::Storage db;
+  auto storage_dba = db.Access();
+  query::DbAccessor dba(&storage_dba);
+#else
+  database::GraphDb db;
+  auto storage_dba = db.Access();
+  query::DbAccessor dba(&storage_dba);
+#endif
+  auto vertex = dba.InsertVertex();
+  mgp_memory memory{utils::NewDeleteResource()};
+  utils::Allocator<mgp_vertex> alloc(memory.impl);
+  mgp_graph graph{&dba, storage::View::NEW};
+  auto *mgp_vertex_v =
+      mgp_value_make_vertex(alloc.new_object<mgp_vertex>(vertex, &graph));
+  const query::TypedValue tv_vertex(vertex);
+  CheckSatisfiesTypesAndNullable(
+      mgp_vertex_v, tv_vertex,
+      {mgp_type_any(), mgp_type_node(), mgp_type_map()});
+  CheckNotSatisfiesTypesAndListAndNullable(
+      mgp_vertex_v, tv_vertex,
+      {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(),
+       mgp_type_number(), mgp_type_relationship(), mgp_type_path()});
+}
+
+TEST(CypherType, EdgeSatisfiesType) {
+#ifdef MG_SINGLE_NODE_V2
+  storage::Storage db;
+  auto storage_dba = db.Access();
+  query::DbAccessor dba(&storage_dba);
+#else
+  database::GraphDb db;
+  auto storage_dba = db.Access();
+  query::DbAccessor dba(&storage_dba);
+#endif
+  auto v1 = dba.InsertVertex();
+  auto v2 = dba.InsertVertex();
+  auto edge = *dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("edge_type"));
+  mgp_memory memory{utils::NewDeleteResource()};
+  utils::Allocator<mgp_edge> alloc(memory.impl);
+  mgp_graph graph{&dba, storage::View::NEW};
+  auto *mgp_edge_v =
+      mgp_value_make_edge(alloc.new_object<mgp_edge>(edge, &graph));
+  const query::TypedValue tv_edge(edge);
+  CheckSatisfiesTypesAndNullable(
+      mgp_edge_v, tv_edge,
+      {mgp_type_any(), mgp_type_relationship(), mgp_type_map()});
+  CheckNotSatisfiesTypesAndListAndNullable(
+      mgp_edge_v, tv_edge,
+      {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(),
+       mgp_type_number(), mgp_type_node(), mgp_type_path()});
+}
+
+TEST(CypherType, PathSatisfiesType) {
+#ifdef MG_SINGLE_NODE_V2
+  storage::Storage db;
+  auto storage_dba = db.Access();
+  query::DbAccessor dba(&storage_dba);
+#else
+  database::GraphDb db;
+  auto storage_dba = db.Access();
+  query::DbAccessor dba(&storage_dba);
+#endif
+  auto v1 = dba.InsertVertex();
+  auto v2 = dba.InsertVertex();
+  auto edge = *dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("edge_type"));
+  mgp_memory memory{utils::NewDeleteResource()};
+  utils::Allocator<mgp_path> alloc(memory.impl);
+  mgp_graph graph{&dba, storage::View::NEW};
+  auto *path = mgp_path_make_with_start(
+      alloc.new_object<mgp_vertex>(v1, &graph), &memory);
+  ASSERT_TRUE(path);
+  ASSERT_TRUE(mgp_path_expand(path, alloc.new_object<mgp_edge>(edge, &graph)));
+  auto *mgp_path_v = mgp_value_make_path(path);
+  const query::TypedValue tv_path(query::Path(v1, edge, v2));
+  CheckSatisfiesTypesAndNullable(mgp_path_v, tv_path,
+                                 {mgp_type_any(), mgp_type_path()});
+  CheckNotSatisfiesTypesAndListAndNullable(
+      mgp_path_v, tv_path,
+      {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(),
+       mgp_type_number(), mgp_type_map(), mgp_type_node(),
+       mgp_type_relationship()});
+}
+
+static std::vector<const mgp_type *> MakeListTypes(
+    const std::vector<const mgp_type *> &element_types) {
+  std::vector<const mgp_type *> list_types;
+  list_types.reserve(2U * element_types.size());
+  for (const auto *type : element_types) {
+    list_types.push_back(mgp_type_list(type));
+    list_types.push_back(mgp_type_list(mgp_type_nullable(type)));
+  }
+  return list_types;
+}
+
+TEST(CypherType, EmptyListSatisfiesType) {
+  mgp_memory memory{utils::NewDeleteResource()};
+  auto *list = mgp_list_make_empty(0, &memory);
+  auto *mgp_list_v = mgp_value_make_list(list);
+  query::TypedValue tv_list(std::vector<query::TypedValue>{});
+  // Empty List satisfies all list element types
+  std::vector<const mgp_type *> primitive_types{
+      mgp_type_any(), mgp_type_bool(),  mgp_type_string(),
+      mgp_type_int(), mgp_type_float(), mgp_type_number(),
+      mgp_type_map(), mgp_type_node(),  mgp_type_relationship(),
+      mgp_type_path()};
+  auto all_types = MakeListTypes(primitive_types);
+  all_types.push_back(mgp_type_any());
+  CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, all_types);
+}
+
+TEST(CypherType, ListOfIntSatisfiesType) {
+  mgp_memory memory{utils::NewDeleteResource()};
+  constexpr int64_t elem_count = 3;
+  auto *list = mgp_list_make_empty(elem_count, &memory);
+  auto *mgp_list_v = mgp_value_make_list(list);
+  query::TypedValue tv_list(std::vector<query::TypedValue>{});
+  for (int64_t i = 0; i < elem_count; ++i) {
+    ASSERT_TRUE(mgp_list_append(list, mgp_value_make_int(i, &memory)));
+    tv_list.ValueList().emplace_back(i);
+    auto valid_types =
+        MakeListTypes({mgp_type_any(), mgp_type_int(), mgp_type_number()});
+    valid_types.push_back(mgp_type_any());
+    CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, valid_types);
+    CheckNotSatisfiesTypesAndListAndNullable(
+        mgp_list_v, tv_list,
+        {mgp_type_bool(), mgp_type_string(), mgp_type_float(), mgp_type_map(),
+         mgp_type_node(), mgp_type_relationship(), mgp_type_path()});
+  }
+}
+
+TEST(CypherType, ListOfIntAndBoolSatisfiesType) {
+  mgp_memory memory{utils::NewDeleteResource()};
+  constexpr int64_t elem_count = 2;
+  auto *list = mgp_list_make_empty(elem_count, &memory);
+  auto *mgp_list_v = mgp_value_make_list(list);
+  query::TypedValue tv_list(std::vector<query::TypedValue>{});
+  // Add an int
+  ASSERT_TRUE(mgp_list_append(list, mgp_value_make_int(42, &memory)));
+  tv_list.ValueList().emplace_back(42);
+  // Add a boolean
+  ASSERT_TRUE(mgp_list_append(list, mgp_value_make_bool(1, &memory)));
+  tv_list.ValueList().emplace_back(true);
+  auto valid_types = MakeListTypes({mgp_type_any()});
+  valid_types.push_back(mgp_type_any());
+  CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, valid_types);
+  // All other types will not be satisfied
+  CheckNotSatisfiesTypesAndListAndNullable(
+      mgp_list_v, tv_list,
+      {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(),
+       mgp_type_number(), mgp_type_map(), mgp_type_node(),
+       mgp_type_relationship(), mgp_type_path()});
+}
+
+TEST(CypherType, ListOfNullSatisfiesType) {
+  mgp_memory memory{utils::NewDeleteResource()};
+  auto *list = mgp_list_make_empty(1, &memory);
+  auto *mgp_list_v = mgp_value_make_list(list);
+  query::TypedValue tv_list(std::vector<query::TypedValue>{});
+  ASSERT_TRUE(mgp_list_append(list, mgp_value_make_null(&memory)));
+  tv_list.ValueList().emplace_back();
+  // List with Null satisfies all nullable list element types
+  std::vector<const mgp_type *> primitive_types{
+      mgp_type_any(), mgp_type_bool(),  mgp_type_string(),
+      mgp_type_int(), mgp_type_float(), mgp_type_number(),
+      mgp_type_map(), mgp_type_node(),  mgp_type_relationship(),
+      mgp_type_path()};
+  std::vector<const mgp_type *> valid_types{mgp_type_any()};
+  valid_types.reserve(1U + primitive_types.size());
+  for (const auto *elem_type : primitive_types) {
+    valid_types.push_back(mgp_type_list(mgp_type_nullable(elem_type)));
+  }
+  CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, valid_types);
+  std::vector<const mgp_type *> invalid_types;
+  invalid_types.reserve(primitive_types.size());
+  for (const auto *elem_type : primitive_types) {
+    invalid_types.push_back(mgp_type_list(elem_type));
+  }
+  for (const auto *type : invalid_types) {
+    EXPECT_FALSE(type->impl->SatisfiesType(*mgp_list_v))
+        << type->impl->GetPresentableName();
+    EXPECT_FALSE(type->impl->SatisfiesType(tv_list));
+    const auto *null_type = mgp_type_nullable(type);
+    EXPECT_FALSE(null_type->impl->SatisfiesType(*mgp_list_v))
+        << null_type->impl->GetPresentableName();
+    EXPECT_FALSE(null_type->impl->SatisfiesType(tv_list));
+  }
+}