Repository: mesonbuild/meson

Issue:
Path join semantics changed in 0.60.0 when using an empty path component with / operator
**Describe the bug**
In meson prior to 0.60.0 the following:

```
join_paths('/foo', '')
join_paths(['/foo', ''])
'/foo' / ''
```

would all result in 

```
'/foo/'
```

Since 0.60.0 the join_paths works the same way, but the '/' operator changed semantics and now gives ```'/foo'``` not ```'/foo/'```

git bisect on meson.git blames

```
commit d93d01b6c5c98a77e057b914e8d66b01d2a10771 (HEAD)
Author: Daniel Mensinger <daniel@mensinger-ka.de>
Date:   Wed Sep 1 23:18:28 2021 +0200

    interpreter: Introduce StringHolder
    
    Another commit in my quest to rid InterpreterBase from all higher
    level object processing logic.
    
    Additionally, there is a a logic change here, since `str.join` now
    uses varargs and can now accept more than one argument (and supports
    list flattening).
```

**To Reproduce**

The following test suite addition illustrates the issue

```
diff --git a/test cases/common/111 pathjoin/meson.build b/test cases/common/111 pathjoin/meson.build
index d3957dd77..1c339dd30 100644
--- a/test cases/common/111 pathjoin/meson.build        
+++ b/test cases/common/111 pathjoin/meson.build        
@@ -7,6 +7,7 @@ assert(join_paths('foo', 'bar', 'baz') == 'foo/bar/baz', 'Path joining is broken
 assert(join_paths('/foo', 'bar')       == '/foo/bar', 'Path joining is broken')
 assert(join_paths('foo', '/bar')       == '/bar', 'Absolute path joining is broken')
 assert(join_paths('/foo', '/bar')      == '/bar', 'Absolute path joining is broken')
+assert(join_paths('/foo', '')          == '/foo/', 'Trailing / on path')
 
 # Test array form since people are using that too
 assert(join_paths(['foo'])               == 'foo', 'Single argument join is broken')
@@ -15,6 +16,7 @@ assert(join_paths(['foo', 'bar', 'baz']) == 'foo/bar/baz', 'Path joining is brok
 assert(join_paths(['/foo', 'bar'])       == '/foo/bar', 'Path joining is broken')
 assert(join_paths(['foo', '/bar'])       == '/bar', 'Absolute path joining is broken')
 assert(join_paths(['/foo', '/bar'])      == '/bar', 'Absolute path joining is broken')
+assert(join_paths(['/foo', ''])          == '/foo/', 'Trailing / on path')
 
 # Division operator should do the same as join_paths
 assert('foo' / 'bar'       == 'foo/bar',     'Path division is broken')
@@ -22,3 +24,4 @@ assert('foo' /'bar' /'baz' == 'foo/bar/baz', 'Path division is broken')
 assert('/foo' / 'bar'      == '/foo/bar',    'Path division is broken')
 assert('foo' / '/bar'      == '/bar',        'Absolute path division is broken')
 assert('/foo' / '/bar'     == '/bar',        'Absolute path division is broken')
+assert('/foo' / ''         == '/foo/',       'Trailing / on path')
```

**Expected behavior**
All forms of joining "/foo" and "" result in "/foo/"

**system parameters**
* Is this a [cross build](https://mesonbuild.com/Cross-compilation.html) or just a plain native build (for the same computer)?

Native

* what operating system

Fedora 34

* what Python version are you using

Python 3.9.6

* what `meson --version`

meson from git



Discussion:

Resolving patch:
diff --git a/mesonbuild/interpreter/primitives/string.py b/mesonbuild/interpreter/primitives/string.py
--- a/mesonbuild/interpreter/primitives/string.py
+++ b/mesonbuild/interpreter/primitives/string.py
@@ -2,6 +2,7 @@
 # SPDX-license-identifier: Apache-2.0
 
 import re
+import os
 from pathlib import PurePath
 
 import typing as T
@@ -159,7 +160,7 @@ def version_compare_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> boo
     @FeatureNew('/ with string arguments', '0.49.0')
     @typed_operator(MesonOperator.DIV, str)
     def op_div(self, other: str) -> str:
-        return (PurePath(self.held_object) / other).as_posix()
+        return os.path.join(self.held_object, other).replace('\\', '/')
 
     @typed_operator(MesonOperator.INDEX, int)
     def op_index(self, other: int) -> str:


Test patch:
diff --git a/test cases/common/111 pathjoin/meson.build b/test cases/common/111 pathjoin/meson.build
--- a/test cases/common/111 pathjoin/meson.build	
+++ b/test cases/common/111 pathjoin/meson.build	
@@ -7,6 +7,7 @@ assert(join_paths('foo', 'bar', 'baz') == 'foo/bar/baz', 'Path joining is broken
 assert(join_paths('/foo', 'bar')       == '/foo/bar', 'Path joining is broken')
 assert(join_paths('foo', '/bar')       == '/bar', 'Absolute path joining is broken')
 assert(join_paths('/foo', '/bar')      == '/bar', 'Absolute path joining is broken')
+assert(join_paths('/foo', '')          == '/foo/', 'Trailing / on path')
 
 # Test array form since people are using that too
 assert(join_paths(['foo'])               == 'foo', 'Single argument join is broken')
@@ -15,6 +16,7 @@ assert(join_paths(['foo', 'bar', 'baz']) == 'foo/bar/baz', 'Path joining is brok
 assert(join_paths(['/foo', 'bar'])       == '/foo/bar', 'Path joining is broken')
 assert(join_paths(['foo', '/bar'])       == '/bar', 'Absolute path joining is broken')
 assert(join_paths(['/foo', '/bar'])      == '/bar', 'Absolute path joining is broken')
+assert(join_paths(['/foo', ''])          == '/foo/', 'Trailing / on path')
 
 # Division operator should do the same as join_paths
 assert('foo' / 'bar'       == 'foo/bar',     'Path division is broken')
@@ -22,3 +24,4 @@ assert('foo' /'bar' /'baz' == 'foo/bar/baz', 'Path division is broken')
 assert('/foo' / 'bar'      == '/foo/bar',    'Path division is broken')
 assert('foo' / '/bar'      == '/bar',        'Absolute path division is broken')
 assert('/foo' / '/bar'     == '/bar',        'Absolute path division is broken')
+assert('/foo' / ''         == '/foo/',       'Trailing / on path')