diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index fa4afd7e356..351451ccd13 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -35,6 +35,7 @@ IDENTIFIER(Any) IDENTIFIER(ArrayLiteralElement) IDENTIFIER(asLocalActor) IDENTIFIER(atIndexedSubscript) +IDENTIFIER(basic_string) IDENTIFIER_(bridgeToObjectiveC) IDENTIFIER(buildArray) IDENTIFIER(buildBlock) diff --git a/include/swift/AST/Types.h b/include/swift/AST/Types.h index 8144b1490f7..b3cb7f8eff6 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -1112,6 +1112,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(); diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index 19a5fa16e5a..abb8ff288c2 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -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(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()) return false; diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index 973362bfc3a..cd81bed2f97 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -257,10 +257,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. - 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. + 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); } } diff --git a/test/Interop/Cxx/stdlib/Inputs/std-string.h b/test/Interop/Cxx/stdlib/Inputs/std-string.h index 62de38a035e..0533b478cef 100644 --- a/test/Interop/Cxx/stdlib/Inputs/std-string.h +++ b/test/Interop/Cxx/stdlib/Inputs/std-string.h @@ -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}; + } +}; diff --git a/test/Interop/Cxx/stdlib/custom-expressible-by-string-literal.swift b/test/Interop/Cxx/stdlib/custom-expressible-by-string-literal.swift new file mode 100644 index 00000000000..6c1190d5d33 --- /dev/null +++ b/test/Interop/Cxx/stdlib/custom-expressible-by-string-literal.swift @@ -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, std{{.*}}allocator>.== infix(_:_:) +// CHECK: // function_ref static std{{.*}}basic_string, std{{.*}}allocator>.== 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") +}