Better user error detection in assert()

Reorders the assert() function usage checks for clarity, and detects
incorrect usage of assert (when the message is not a string) even when
the assertion condition is true.

Change-Id: Ia6e6f897d265bc76ca3d5a6d2b6acf6d3462831a
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/6340
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/tools/gn/functions.cc b/tools/gn/functions.cc
index b08dbd0..ce76280 100644
--- a/tools/gn/functions.cc
+++ b/tools/gn/functions.cc
@@ -231,43 +231,55 @@
                 const FunctionCallNode* function,
                 const std::vector<Value>& args,
                 Err* err) {
+  // Check usage: Assert takes 1 or 2 arguments.
   if (args.size() != 1 && args.size() != 2) {
     *err = Err(function->function(), "Wrong number of arguments.",
-               "assert() takes one or two argument, "
-               "were you expecting somethig else?");
-  } else if (args[0].type() != Value::BOOLEAN) {
-    *err = Err(function->function(), "Assertion value not a bool.");
-  } else if (!args[0].boolean_value()) {
-    if (args.size() == 2) {
-      // Optional string message.
-      if (args[1].type() != Value::STRING) {
-        *err = Err(function->function(), "Assertion failed.",
-                   "<<<ERROR MESSAGE IS NOT A STRING>>>");
-      } else {
-        *err = Err(function->function(), "Assertion failed.",
-                   args[1].string_value());
-      }
-    } else {
-      *err = Err(function->function(), "Assertion failed.");
-    }
+               "assert() takes one or two arguments, "
+               "were you expecting something else?");
+    return Value();
+  }
 
-    if (args[0].origin()) {
-      // If you do "assert(foo)" we'd ideally like to show you where foo was
-      // set, and in this case the origin of the args will tell us that.
-      // However, if you do "assert(foo && bar)" the source of the value will
-      // be the assert like, which isn't so helpful.
-      //
-      // So we try to see if the args are from the same line or not. This will
-      // break if you do "assert(\nfoo && bar)" and we may show the second line
-      // as the source, oh well. The way around this is to check to see if the
-      // origin node is inside our function call block.
-      Location origin_location = args[0].origin()->GetRange().begin();
-      if (origin_location.file() != function->function().location().file() ||
-          origin_location.line_number() !=
-              function->function().location().line_number()) {
-        err->AppendSubErr(
-            Err(args[0].origin()->GetRange(), "", "This is where it was set."));
-      }
+  // Check usage: The first argument must be a boolean.
+  if (args[0].type() != Value::BOOLEAN) {
+    *err = Err(function->function(), "Assertion value not a bool.");
+    return Value();
+  }
+  bool assertion_passed = args[0].boolean_value();
+
+  // Check usage: The second argument, if present, must be a string.
+  if (args.size() == 2 && args[1].type() != Value::STRING) {
+    *err = Err(function->function(), "Assertion message is not a string.");
+    return Value();
+  }
+
+  // Assertion passed: there is nothing to do, so return an empty value.
+  if (assertion_passed) {
+    return Value();
+  }
+
+  // Assertion failed; try to make a useful message and report it.
+  if (args.size() == 2) {
+    *err = Err(function->function(), "Assertion failed.",
+               args[1].string_value());
+  } else {
+    *err = Err(function->function(), "Assertion failed.");
+  }
+  if (args[0].origin()) {
+    // If you do "assert(foo)" we'd ideally like to show you where foo was
+    // set, and in this case the origin of the args will tell us that.
+    // However, if you do "assert(foo && bar)" the source of the value will
+    // be the assert like, which isn't so helpful.
+    //
+    // So we try to see if the args are from the same line or not. This will
+    // break if you do "assert(\nfoo && bar)" and we may show the second line
+    // as the source, oh well. The way around this is to check to see if the
+    // origin node is inside our function call block.
+    Location origin_location = args[0].origin()->GetRange().begin();
+    if (origin_location.file() != function->function().location().file() ||
+        origin_location.line_number() !=
+            function->function().location().line_number()) {
+      err->AppendSubErr(
+          Err(args[0].origin()->GetRange(), "", "This is where it was set."));
     }
   }
   return Value();