Skip to content

Commit 2e92c98

Browse files
committed
fix: refine Symbol::For overload resolution
Signed-off-by: umuoy1 <burningdian@gmail.com>
1 parent 9af66ff commit 2e92c98

5 files changed

Lines changed: 195 additions & 11 deletions

File tree

‎doc/symbol.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Returns a `Napi::Symbol` representing a well-known `Symbol` from the
4949

5050
### For
5151
```cpp
52+
static Napi::Symbol Napi::Symbol::For(napi_env env, const std::string& description);
5253
static Napi::Symbol Napi::Symbol::For(napi_env env, std::string_view description);
5354
static Napi::Symbol Napi::Symbol::For(napi_env env, const char* description);
5455
static Napi::Symbol Napi::Symbol::For(napi_env env, String description);
@@ -58,12 +59,16 @@ static Napi::Symbol Napi::Symbol::For(napi_env env, napi_value description);
5859
- `[in] env`: The `napi_env` environment in which to construct the `Napi::Symbol` object.
5960
- `[in] description`: The C++ string representing the `Napi::Symbol` in the global registry to retrieve.
6061
`description` may be any of:
61-
- `std::string_view` - represents a UTF-8 string view. `std::string` values
62-
are implicitly convertible to `std::string_view`.
62+
- `const std::string&` - represents a UTF-8 string.
63+
- `std::string_view` - represents a UTF-8 string view.
6364
- `const char*` - represents a UTF8 string description.
6465
- `String` - Node addon API String description.
6566
- `napi_value` - Node-API `napi_value` description.
6667
68+
String-like arguments implicitly convertible to both `const std::string&` and
69+
`std::string_view` that do not have a unique best match among the non-template
70+
overloads are resolved through `std::string_view`.
71+
6772
Searches in the global registry for existing symbol with the given name. If the symbol already exist it will be returned, otherwise a new symbol will be created in the registry. It's equivalent to Symbol.for() called from JavaScript.
6873
6974
[`Napi::Name`]: ./name.md

‎napi-inl.h‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,12 +1416,24 @@ inline MaybeOrValue<Symbol> Symbol::WellKnown(napi_env env,
14161416
#endif
14171417
}
14181418

1419+
inline MaybeOrValue<Symbol> Symbol::For(napi_env env,
1420+
const std::string& description) {
1421+
napi_value descriptionValue = String::New(env, description);
1422+
return Symbol::For(env, descriptionValue);
1423+
}
1424+
14191425
inline MaybeOrValue<Symbol> Symbol::For(napi_env env,
14201426
std::string_view description) {
14211427
napi_value descriptionValue = String::New(env, description);
14221428
return Symbol::For(env, descriptionValue);
14231429
}
14241430

1431+
template <typename T, details::enable_if_ambiguous_symbol_for_t<T&&>>
1432+
inline MaybeOrValue<Symbol> Symbol::For(napi_env env, T&& description) {
1433+
std::string_view descriptionView = std::forward<T>(description);
1434+
return Symbol::For(env, descriptionView);
1435+
}
1436+
14251437
inline MaybeOrValue<Symbol> Symbol::For(napi_env env, const char* description) {
14261438
napi_value descriptionValue = String::New(env, description);
14271439
return Symbol::For(env, descriptionValue);

‎napi.h‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include <chrono>
2121
#include <string>
2222
#include <string_view>
23+
#include <type_traits>
24+
#include <utility>
2325
#include <vector>
2426

2527
// VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known
@@ -786,6 +788,41 @@ class String : public Name {
786788
const; ///< Converts a String value to a UTF-16 encoded C++ string.
787789
};
788790

791+
namespace details {
792+
793+
// This overload set must mirror the non-template Symbol::For overloads.
794+
struct symbol_for_overload_probe {
795+
static void select(const std::string&);
796+
static void select(std::string_view);
797+
static void select(const char*);
798+
static void select(String);
799+
static void select(napi_value);
800+
};
801+
802+
template <typename T, typename = void>
803+
struct has_unambiguous_symbol_for_overload : std::false_type {};
804+
805+
template <typename T>
806+
struct has_unambiguous_symbol_for_overload<
807+
T,
808+
std::void_t<decltype(symbol_for_overload_probe::select(std::declval<T>()))>>
809+
: std::true_type {};
810+
811+
// Enable the template overload only for string-like arguments that have no
812+
// unique best match among the non-template Symbol::For overloads.
813+
//
814+
// Exclude nullptr because it matches the pointer overloads equally well and
815+
// cannot safely initialize a std::string_view.
816+
template <typename T>
817+
using enable_if_ambiguous_symbol_for_t =
818+
std::enable_if_t<!std::is_null_pointer_v<std::decay_t<T>> &&
819+
std::is_convertible_v<T, const std::string&> &&
820+
std::is_convertible_v<T, std::string_view> &&
821+
!has_unambiguous_symbol_for_overload<T>::value,
822+
int>;
823+
824+
} // namespace details
825+
789826
/// A JavaScript symbol value.
790827
class Symbol : public Name {
791828
public:
@@ -825,9 +862,17 @@ class Symbol : public Name {
825862
/// Get a public Symbol (e.g. Symbol.iterator).
826863
static MaybeOrValue<Symbol> WellKnown(napi_env, const std::string& name);
827864

865+
// Create a symbol in the global registry, UTF-8 Encoded cpp string
866+
static MaybeOrValue<Symbol> For(napi_env env, const std::string& description);
867+
828868
// Create a symbol in the global registry, UTF-8 encoded cpp string view
829869
static MaybeOrValue<Symbol> For(napi_env env, std::string_view description);
830870

871+
// Resolve otherwise ambiguous string-like arguments through the
872+
// std::string_view overload
873+
template <typename T, details::enable_if_ambiguous_symbol_for_t<T&&> = 0>
874+
static MaybeOrValue<Symbol> For(napi_env env, T&& description);
875+
831876
// Create a symbol in the global registry, C style string (null terminated)
832877
static MaybeOrValue<Symbol> For(napi_env env, const char* description);
833878

‎test/symbol.cc‎

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,61 @@
11
#include <napi.h>
22

33
#include <string_view>
4+
#include <utility>
45

56
#include "test_helper.h"
67
using namespace Napi;
78

89
namespace {
910

10-
class StringLike {
11-
public:
12-
explicit StringLike(const std::string& value) : _value(value) {}
11+
struct StringLike {
12+
operator std::string() const { return "unexpected-string-key"; }
13+
operator std::string_view() const { return value; }
1314

14-
operator std::string() const { return _value; }
15-
operator std::string_view() const { return _value; }
15+
std::string value;
16+
};
17+
18+
struct RvalueStringLike {
19+
operator std::string() && { return "unexpected-rvalue-string-key"; }
20+
operator std::string_view() && { return value; }
21+
22+
std::string value;
23+
};
24+
25+
struct StringOnlyLike {
26+
operator std::string() const { return value; }
27+
28+
std::string value;
29+
};
30+
31+
struct BothBases : std::string, std::string_view {};
32+
33+
struct ViewAndNapiString : std::string_view, Napi::String {};
34+
35+
struct StringReferenceLike {
36+
operator std::string&() const { return stringValue; }
37+
operator std::string&&() const { return std::move(stringValue); }
38+
operator std::string_view() const { return viewValue; }
1639

17-
private:
18-
std::string _value;
40+
mutable std::string stringValue;
41+
std::string_view viewValue;
42+
};
43+
44+
struct ImplicitStringViewLike {
45+
operator std::string_view() const { return value; }
46+
47+
std::string_view value;
48+
};
49+
50+
struct ExplicitStringViewLike {
51+
explicit operator std::string_view() const { return value; }
52+
53+
std::string_view value;
54+
};
55+
56+
struct ImplicitAndExplicitStringViewLike : ImplicitStringViewLike,
57+
ExplicitStringViewLike {
58+
operator std::string() const { return "unexpected-string-key"; }
1959
};
2060

2161
} // namespace
@@ -64,10 +104,59 @@ Symbol FetchSymbolFromGlobalRegistryWithStringViewKey(
64104

65105
Symbol FetchSymbolFromGlobalRegistryWithStringLikeKey(
66106
const Napi::CallbackInfo& info) {
67-
StringLike key(info[0].As<String>().Utf8Value());
107+
StringLike key{info[0].As<String>().Utf8Value()};
68108
return MaybeUnwrap(Napi::Symbol::For(info.Env(), key));
69109
}
70110

111+
Symbol FetchSymbolFromGlobalRegistryWithRvalueStringLikeKey(
112+
const Napi::CallbackInfo& info) {
113+
return MaybeUnwrap(Napi::Symbol::For(
114+
info.Env(), RvalueStringLike{info[0].As<String>().Utf8Value()}));
115+
}
116+
117+
Symbol FetchSymbolFromGlobalRegistryWithStringOnlyLikeKey(
118+
const Napi::CallbackInfo& info) {
119+
StringOnlyLike key{info[0].As<String>().Utf8Value()};
120+
return MaybeUnwrap(Napi::Symbol::For(info.Env(), key));
121+
}
122+
123+
Symbol FetchSymbolFromGlobalRegistryWithBothBasesKey(
124+
const Napi::CallbackInfo& info) {
125+
std::string value = info[0].As<String>().Utf8Value();
126+
BothBases key;
127+
static_cast<std::string&>(key) = "unexpected-string-key";
128+
static_cast<std::string_view&>(key) = value;
129+
return MaybeUnwrap(Symbol::For(info.Env(), key));
130+
}
131+
132+
Symbol FetchSymbolFromGlobalRegistryWithViewAndNapiStringKey(
133+
const Napi::CallbackInfo& info) {
134+
Env env = info.Env();
135+
std::string value = info[0].As<String>().Utf8Value();
136+
ViewAndNapiString key;
137+
static_cast<std::string_view&>(key) = value;
138+
static_cast<Napi::String&>(key) =
139+
Napi::String::New(env, "unexpected-napi-string-key");
140+
return MaybeUnwrap(Symbol::For(env, key));
141+
}
142+
143+
Symbol FetchSymbolFromGlobalRegistryWithStringReferenceKey(
144+
const Napi::CallbackInfo& info) {
145+
std::string value = info[0].As<String>().Utf8Value();
146+
StringReferenceLike key{"unexpected-string-reference-key", value};
147+
return MaybeUnwrap(Symbol::For(info.Env(), key));
148+
}
149+
150+
Symbol FetchSymbolFromGlobalRegistryWithImplicitViewKey(
151+
const Napi::CallbackInfo& info) {
152+
std::string value = info[0].As<String>().Utf8Value();
153+
ImplicitAndExplicitStringViewLike key;
154+
static_cast<ImplicitStringViewLike&>(key).value = value;
155+
static_cast<ExplicitStringViewLike&>(key).value =
156+
"unexpected-explicit-string-view-key";
157+
return MaybeUnwrap(Symbol::For(info.Env(), key));
158+
}
159+
71160
Symbol FetchSymbolFromGlobalRegistryWithCKey(const Napi::CallbackInfo& info) {
72161
String cppStringKey = info[0].As<String>();
73162
return MaybeUnwrap(
@@ -106,6 +195,18 @@ Object InitSymbol(Env env) {
106195
Function::New(env, FetchSymbolFromGlobalRegistryWithStringViewKey);
107196
exports["getSymbolFromGlobalRegistryWithStringLikeKey"] =
108197
Function::New(env, FetchSymbolFromGlobalRegistryWithStringLikeKey);
198+
exports["getSymbolFromGlobalRegistryWithRvalueStringLikeKey"] =
199+
Function::New(env, FetchSymbolFromGlobalRegistryWithRvalueStringLikeKey);
200+
exports["getSymbolFromGlobalRegistryWithStringOnlyLikeKey"] =
201+
Function::New(env, FetchSymbolFromGlobalRegistryWithStringOnlyLikeKey);
202+
exports["getSymbolFromGlobalRegistryWithBothBasesKey"] =
203+
Function::New(env, FetchSymbolFromGlobalRegistryWithBothBasesKey);
204+
exports["getSymbolFromGlobalRegistryWithViewAndNapiStringKey"] =
205+
Function::New(env, FetchSymbolFromGlobalRegistryWithViewAndNapiStringKey);
206+
exports["getSymbolFromGlobalRegistryWithStringReferenceKey"] =
207+
Function::New(env, FetchSymbolFromGlobalRegistryWithStringReferenceKey);
208+
exports["getSymbolFromGlobalRegistryWithImplicitViewKey"] =
209+
Function::New(env, FetchSymbolFromGlobalRegistryWithImplicitViewKey);
109210
exports["testUndefinedSymbolCanBeCreated"] =
110211
Function::New(env, TestUndefinedSymbolsCanBeCreated);
111212
exports["testNullSymbolCanBeCreated"] =

‎test/symbol.js‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ function test (binding) {
4242
const symbTwo = fetchFunction(symbol);
4343
assert(symbOne && symbTwo);
4444
assert(symbOne === symbTwo);
45+
assert(symbOne === Symbol.for(symbol));
4546
}
4647

4748
assertCanCreateSymbol('testing');
@@ -55,7 +56,27 @@ function test (binding) {
5556
assertCanCreateOrFetchGlobalSymbols('data', binding.symbol.getSymbolFromGlobalRegistry);
5657
assertCanCreateOrFetchGlobalSymbols('CppKey', binding.symbol.getSymbolFromGlobalRegistryWithCppKey);
5758
assertCanCreateOrFetchGlobalSymbols('StringViewKey', binding.symbol.getSymbolFromGlobalRegistryWithStringViewKey);
58-
assertCanCreateOrFetchGlobalSymbols('StringLikeKey', binding.symbol.getSymbolFromGlobalRegistryWithStringLikeKey);
59+
assertCanCreateOrFetchGlobalSymbols(
60+
'StringLikeKey',
61+
binding.symbol.getSymbolFromGlobalRegistryWithStringLikeKey);
62+
assertCanCreateOrFetchGlobalSymbols(
63+
'RvalueStringLikeKey',
64+
binding.symbol.getSymbolFromGlobalRegistryWithRvalueStringLikeKey);
65+
assertCanCreateOrFetchGlobalSymbols(
66+
'StringOnlyLikeKey',
67+
binding.symbol.getSymbolFromGlobalRegistryWithStringOnlyLikeKey);
68+
assertCanCreateOrFetchGlobalSymbols(
69+
'BothBasesKey',
70+
binding.symbol.getSymbolFromGlobalRegistryWithBothBasesKey);
71+
assertCanCreateOrFetchGlobalSymbols(
72+
'ViewAndNapiStringKey',
73+
binding.symbol.getSymbolFromGlobalRegistryWithViewAndNapiStringKey);
74+
assertCanCreateOrFetchGlobalSymbols(
75+
'StringReferenceKey',
76+
binding.symbol.getSymbolFromGlobalRegistryWithStringReferenceKey);
77+
assertCanCreateOrFetchGlobalSymbols(
78+
'ImplicitViewKey',
79+
binding.symbol.getSymbolFromGlobalRegistryWithImplicitViewKey);
5980
assertCanCreateOrFetchGlobalSymbols('CKey', binding.symbol.getSymbolFromGlobalRegistryWithCKey);
6081

6182
assert(binding.symbol.createNewSymbolWithNoArgs() === undefined);

0 commit comments

Comments
 (0)