Improve error message for rebase_path builtin

When rebase_path encounters an invalid path (e.g., an empty string), it
previously blamed the origin of the value. If the value originated from
another function (like read_file) or a variable definition far away,
the error message could be confusing as it pointed to the definition
site rather than the usage site.

This change improves the error reporting by:
- Rewriting the error to blame the rebase_path call.
- Appending the original error location as a sub-error. This makes it clear that
rebase_path failed, while still providing context on where the bad value came
from.

Before
```
ERROR at //BUILD.gn:2:9: Empty directory path.
lines = read_file("test.txt", "list lines")
	^---------------------------------
You can't use empty strings as directories.
```

After
```
ERROR at //BUILD.gn:4:13: Empty directory path.
rebase_path(lines)
	    ^----
You can't use empty strings as directories.
See //BUILD.gn:3:9: The value that caused the error.
lines = read_file("test.txt", "list lines")
	^
```

Change-Id: I7c5d26ee13ac5fea3560a808a9d4262a0b9304fc
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/20660
Reviewed-by: David Turner <digit@google.com>
Commit-Queue: Neri Marschik <nerima@google.com>
diff --git a/src/gn/function_rebase_path.cc b/src/gn/function_rebase_path.cc
index 7e912aa..a998785 100644
--- a/src/gn/function_rebase_path.cc
+++ b/src/gn/function_rebase_path.cc
@@ -5,6 +5,7 @@
 #include <stddef.h>
 
 #include "gn/build_settings.h"
+#include <utility>
 #include "gn/filesystem_utils.h"
 #include "gn/functions.h"
 #include "gn/parse_tree.h"
@@ -53,6 +54,14 @@
   return false;
 }
 
+void AddCallLocationToError(const ParseNode* blame_node, Err* err) {
+  if (blame_node && err->has_error()) {
+    Err new_err(blame_node, err->message(), err->help_text());
+    new_err.AppendSubErr(Err(err->location(), "The value that caused the error."));
+    *err = std::move(new_err);
+  }
+}
+
 Value ConvertOnePath(const Scope* scope,
                      const FunctionCallNode* function,
                      const Value& value,
@@ -93,19 +102,21 @@
 
   result = Value(function, Value::STRING);
   if (looks_like_dir) {
+    SourceDir resolved = from_dir.ResolveRelativeDir(
+        value, err, scope->settings()->build_settings()->root_path_utf8());
+    if (err->has_error()) {
+      return Value();
+    }
     result.string_value() = RebasePath(
-        from_dir
-            .ResolveRelativeDir(
-                value, err,
-                scope->settings()->build_settings()->root_path_utf8())
-            .value(),
+        resolved.value(),
         to_dir, scope->settings()->build_settings()->root_path_utf8());
     MakeSlashEndingMatchInput(string_value, &result.string_value());
   } else {
     SourceFile resolved_file = from_dir.ResolveRelativeFile(
         value, err, scope->settings()->build_settings()->root_path_utf8());
-    if (err->has_error())
+    if (err->has_error()) {
       return Value();
+    }
     result.string_value() =
         RebasePath(resolved_file.value(), to_dir,
                    scope->settings()->build_settings()->root_path_utf8());
@@ -227,6 +238,10 @@
     return result;
   }
   const Value& inputs = args[kArgIndexInputs];
+  const ParseNode* inputs_origin = nullptr;
+  if (function->args() && function->args()->contents().size() > kArgIndexInputs) {
+    inputs_origin = function->args()->contents()[kArgIndexInputs].get();
+  }
 
   // To path.
   bool convert_to_system_absolute = true;
@@ -262,8 +277,12 @@
 
   // Path conversion.
   if (inputs.type() == Value::STRING) {
-    return ConvertOnePath(scope, function, inputs, from_dir, to_dir,
-                          convert_to_system_absolute, err);
+    Value ret = ConvertOnePath(scope, function, inputs, from_dir, to_dir,
+                               convert_to_system_absolute, err);
+    if (err->has_error()) {
+      AddCallLocationToError(inputs_origin, err);
+    }
+    return ret;
 
   } else if (inputs.type() == Value::LIST) {
     result = Value(function, Value::LIST);
@@ -274,6 +293,7 @@
           ConvertOnePath(scope, function, input, from_dir, to_dir,
                          convert_to_system_absolute, err));
       if (err->has_error()) {
+        AddCallLocationToError(inputs_origin, err);
         result = Value();
         return result;
       }