Skip to content

Commit cd9c37c

Browse files
committed
[ConstraintSystem] C++ Interop: Binding a string literal to std.string shouldn't increase the score
Since this is a C++ stdlib type we need make sure that any overloads that use it are preferred over custom types that also conform to `ExpressibleByStringLiteral` when argument is a string literal. This is important for operators like `==` which could be heterogenous and have a custom C++ type that conforms to `ExpressibleByStringLiteral` on either side together with `std.string` i.e. `==(std.string, const CustomString &)`, such overloads should only be selected if argument passed to `CustomString` is non-literal because literals are convered by a stdlib `==(std.string, std.string)` overload.
1 parent 2e8f74f commit cd9c37c

File tree

6 files changed

+75
-4
lines changed

6 files changed

+75
-4
lines changed

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ IDENTIFIER(Any)
3434
IDENTIFIER(ArrayLiteralElement)
3535
IDENTIFIER(asLocalActor)
3636
IDENTIFIER(atIndexedSubscript)
37+
IDENTIFIER(basic_string)
3738
IDENTIFIER_(bridgeToObjectiveC)
3839
IDENTIFIER(buildArray)
3940
IDENTIFIER(buildBlock)

include/swift/AST/Types.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,9 @@ class alignas(1 << TypeAlignInBits) TypeBase
10941094
/// on macOS or Foundation on Linux.
10951095
bool isCGFloat();
10961096

1097+
/// Check if this is a std.string type from C++.
1098+
bool isCxxString();
1099+
10971100
/// Check if this is either an Array, Set or Dictionary collection type defined
10981101
/// at the top level of the Swift module
10991102
bool isKnownStdlibCollectionType();

lib/AST/Type.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "swift/AST/Types.h"
4444
#include "swift/Basic/Assertions.h"
4545
#include "swift/Basic/Compiler.h"
46+
#include "clang/AST/DeclCXX.h"
4647
#include "llvm/ADT/APFloat.h"
4748
#include "llvm/ADT/STLExtras.h"
4849
#include "llvm/ADT/SmallPtrSet.h"
@@ -1280,6 +1281,21 @@ bool TypeBase::isCGFloat() {
12801281
NTD->getName().is("CGFloat");
12811282
}
12821283

1284+
bool TypeBase::isCxxString() {
1285+
auto *nominal = getAnyNominal();
1286+
if (!nominal)
1287+
return false;
1288+
1289+
auto *clangDecl =
1290+
dyn_cast_or_null<clang::CXXRecordDecl>(nominal->getClangDecl());
1291+
if (!clangDecl)
1292+
return false;
1293+
1294+
auto &ctx = nominal->getASTContext();
1295+
return clangDecl->isInStdNamespace() && clangDecl->getIdentifier() &&
1296+
ctx.Id_basic_string.is(clangDecl->getName());
1297+
}
1298+
12831299
bool TypeBase::isKnownStdlibCollectionType() {
12841300
if (isArray() || isDictionary() || isSet()) {
12851301
return true;

lib/Sema/ConstraintSystem.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,28 @@ void ConstraintSystem::assignFixedType(TypeVariableType *typeVar, Type type,
256256

257257
// If the protocol has a default type, check it.
258258
if (auto defaultType = TypeChecker::getDefaultType(literalProtocol, DC)) {
259-
// Check whether the nominal types match. This makes sure that we
260-
// properly handle Array vs. Array<T>.
261-
if (defaultType->getAnyNominal() != type->getAnyNominal()) {
259+
auto isDefaultType = [&literalProtocol, &defaultType](Type type) {
260+
// Treat `std.string` as a default type just like we do
261+
// Swift standard library `String`. This helps to disambiguate
262+
// operator overloads that use `std.string` vs. a custom C++
263+
// type that conforms to `ExpressibleByStringLiteral` as well.
264+
//
265+
// This doesn't clash with String because inference won't attempt
266+
// C++ types unless we discover them from a constraint and the
267+
// optimizer and old hacks always prefer the actual default type.
268+
if (literalProtocol->getKnownProtocolKind() ==
269+
KnownProtocolKind::ExpressibleByStringLiteral &&
270+
type->isCxxString()) {
271+
return true;
272+
}
273+
274+
// Check whether the nominal types match. This makes sure that we
275+
// properly handle Array vs. Array<T>.
276+
return defaultType->getAnyNominal() == type->getAnyNominal();
277+
};
278+
279+
if (!isDefaultType(type))
262280
increaseScore(SK_NonDefaultLiteral, locator);
263-
}
264281
}
265282

266283
break;

test/Interop/Cxx/stdlib/Inputs/std-string.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,15 @@ struct HasMethodThatReturnsString {
66
};
77

88
inline std::string takesStringWithDefaultArg(std::string s = "abc") { return s; }
9+
10+
struct StringBox {
11+
std::string value;
12+
13+
friend bool operator==(const StringBox &lhs, const std::string &rhs) {
14+
return lhs.value == rhs;
15+
}
16+
17+
friend bool operator==(const std::string &lhs, const StringBox &rhs) {
18+
return rhs == lhs;
19+
}
20+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %target-swift-emit-silgen -verify -I %S/Inputs -cxx-interoperability-mode=upcoming-swift %s | %FileCheck %s
2+
3+
import CxxStdlib
4+
import StdString
5+
6+
extension StringBox: @retroactive ExpressibleByStringLiteral {
7+
public init(stringLiteral value: String) {
8+
self.value = std.string(value)
9+
}
10+
}
11+
12+
// CHECK-LABEL: sil hidden [ossa] @$s4main4testyyF
13+
// CHECK: // function_ref static std{{.*}}basic_string<CChar, std{{.*}}char_traits<CChar>, std{{.*}}allocator<CChar>>.== infix(_:_:)
14+
// CHECK: // function_ref static std{{.*}}basic_string<CChar, std{{.*}}char_traits<CChar>, std{{.*}}allocator<CChar>>.== infix(_:_:)
15+
// CHECK: // function_ref static String.== infix(_:_:)
16+
// CHECK: } // end sil function '$s4main4testyyF'
17+
func test() {
18+
let cxxString: std.string = ""
19+
let _ = cxxString == "def" // Ok
20+
let _ = "def" == cxxString // Ok
21+
let _ = "def" == "hello" // Ok
22+
}

0 commit comments

Comments
 (0)