Merge pull request #85906 from egorzhdan/egorzhdan/reland-cxx-string-cs

Reapply "[ConstraintSystem] C++ Interop: Binding a string literal to `std.string` shouldn't increase the score"
This commit is contained in:
Egor Zhdan
2025-12-10 21:04:15 +00:00
committed by GitHub
6 changed files with 88 additions and 4 deletions

View File

@@ -35,6 +35,7 @@ IDENTIFIER(Any)
IDENTIFIER(ArrayLiteralElement)
IDENTIFIER(asLocalActor)
IDENTIFIER(atIndexedSubscript)
IDENTIFIER(basic_string)
IDENTIFIER_(bridgeToObjectiveC)
IDENTIFIER(buildArray)
IDENTIFIER(buildBlock)

View File

@@ -1116,6 +1116,9 @@ public:
/// Check if this is a ObjCBool type from the Objective-C module.
bool isObjCBool();
/// Check if this is a std.string type from C++.
bool isCxxString();
/// Check if this is the type Unicode.Scalar from the Swift standard library.
bool isUnicodeScalar();

View File

@@ -43,6 +43,7 @@
#include "swift/AST/Types.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/Compiler.h"
#include "clang/AST/DeclCXX.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
@@ -1302,6 +1303,21 @@ bool TypeBase::isObjCBool() {
return module->getName().is("ObjectiveC") && NTD->getName().is("ObjCBool");
}
bool TypeBase::isCxxString() {
auto *nominal = getAnyNominal();
if (!nominal)
return false;
auto *clangDecl =
dyn_cast_or_null<clang::CXXRecordDecl>(nominal->getClangDecl());
if (!clangDecl)
return false;
auto &ctx = nominal->getASTContext();
return clangDecl->isInStdNamespace() && clangDecl->getIdentifier() &&
ctx.Id_basic_string.is(clangDecl->getName());
}
bool TypeBase::isUnicodeScalar() {
if (!is<StructType>())
return false;

View File

@@ -293,10 +293,24 @@ void ConstraintSystem::assignFixedType(TypeVariableType *typeVar, Type type,
// If the protocol has a default type, check it.
if (auto defaultType = TypeChecker::getDefaultType(literalProtocol, DC)) {
// Check whether the nominal types match. This makes sure that we
// properly handle Array vs. Array<T>.
if (defaultType->getAnyNominal() != type->getAnyNominal()) {
increaseScore(SK_NonDefaultLiteral, locator);
auto isDefaultType = [&defaultType](Type type) {
// Check whether the nominal types match. This makes sure that we
// properly handle Array vs. Array<T>.
return defaultType->getAnyNominal() == type->getAnyNominal();
};
if (!isDefaultType(type)) {
// Treat `std.string` as a default type just like we do
// Swift standard library `String`. This helps to disambiguate
// operator overloads that use `std.string` vs. a custom C++
// type that conforms to `ExpressibleByStringLiteral` as well.
bool isCxxDefaultType =
literalProtocol->isSpecificProtocol(
KnownProtocolKind::ExpressibleByStringLiteral) &&
type->isCxxString();
increaseScore(SK_NonDefaultLiteral, locator,
isCxxDefaultType ? 1 : 2);
}
}

View File

@@ -6,3 +6,19 @@ struct HasMethodThatReturnsString {
};
inline std::string takesStringWithDefaultArg(std::string s = "abc") { return s; }
struct StringBox {
std::string value;
friend bool operator==(const StringBox &lhs, const std::string &rhs) {
return lhs.value == rhs;
}
friend bool operator==(const std::string &lhs, const StringBox &rhs) {
return rhs == lhs;
}
StringBox operator+(const StringBox &rhs) const {
return {value + rhs.value};
}
};

View File

@@ -0,0 +1,34 @@
// RUN: %target-swift-emit-silgen -verify -I %S/Inputs -cxx-interoperability-mode=upcoming-swift %s | %FileCheck %s
import CxxStdlib
import StdString
extension StringBox: @retroactive ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self.value = std.string(value)
}
}
func takesAny(_: Any) {}
// CHECK-LABEL: sil hidden [ossa] @$s4main4testyyF
// CHECK: // function_ref static std{{.*}}basic_string<CChar, std{{.*}}char_traits<CChar>, std{{.*}}allocator<CChar>>.== infix(_:_:)
// CHECK: // function_ref static std{{.*}}basic_string<CChar, std{{.*}}char_traits<CChar>, std{{.*}}allocator<CChar>>.== infix(_:_:)
// CHECK: // function_ref static String.== infix(_:_:)
// CHECK: // function_ref static String.+ infix(_:_:)
// CHECK: // function_ref static StringBox.+ infix(_:_:)
// CHECK-NEXT: %{{.*}} = function_ref @$sSo9StringBoxV1poiyA2B_ABtFZ
// CHECK: // function_ref takesAny(_:)
// CHECK-NEXT: %{{.*}} = function_ref @$s4main8takesAnyyyypF
// CHECK: } // end sil function '$s4main4testyyF'
func test() {
let cxxString: std.string = ""
let _ = cxxString == "def" // Ok
let _ = "def" == cxxString // Ok
let _ = "def" == "hello" // Ok
takesAny("abc" + "def")
takesAny("abc" + "def" + "xyz")
takesAny(StringBox("abc") + "def" + "xyz")
takesAny("abc" + StringBox("def") + "xyz")
}