diff --git a/NativeScript/ffi/objc/hermes/NativeApiJsi.mm b/NativeScript/ffi/objc/hermes/NativeApiJsi.mm index 9b3e2f93d..5f3e76e38 100644 --- a/NativeScript/ffi/objc/hermes/NativeApiJsi.mm +++ b/NativeScript/ffi/objc/hermes/NativeApiJsi.mm @@ -32,6 +32,7 @@ #include "Metadata.h" #include "MetadataReader.h" #include "ffi.h" +#include "ffi/objc/shared/EnumMemberAliases.h" #include "NativeApiJsiSignatureDispatch.h" #ifndef NATIVESCRIPT_REACT_NATIVE // js_jsr_for_runtime: maps the bridge's unsafe jsi::Runtime back to the diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h index 4c595b1c5..4cca8bb61 100644 --- a/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h @@ -24,6 +24,7 @@ #include "Metadata.h" #include "MetadataReader.h" #include "ffi.h" +#include "ffi/objc/shared/EnumMemberAliases.h" #include "jsi/jsc/JSCRuntime.h" diff --git a/NativeScript/ffi/objc/napi/Enum.mm b/NativeScript/ffi/objc/napi/Enum.mm index da6ce6add..c66c80a09 100644 --- a/NativeScript/ffi/objc/napi/Enum.mm +++ b/NativeScript/ffi/objc/napi/Enum.mm @@ -5,6 +5,7 @@ #include #include #include "ObjCBridge.h" +#include "ffi/objc/shared/EnumMemberAliases.h" namespace nativescript { @@ -30,10 +31,6 @@ inline void defineConstantIfMissing(napi_env env, napi_value object, const std:: napi_define_properties(env, object, 1, &prop); } -inline bool startsWith(const std::string& value, const std::string& prefix) { - return value.size() >= prefix.size() && value.compare(0, prefix.size(), prefix) == 0; -} - inline std::string stripEnumSuffix(const std::string& enumName) { static const std::vector suffixes = { "Options", "Option", "Enums", "Enum", "Result", "Direction", "Orientation", @@ -48,14 +45,6 @@ inline bool startsWith(const std::string& value, const std::string& prefix) { return enumName; } - -inline bool isNSComparisonResultOrderingName(const std::string& enumName, - const std::string& member) { - if (enumName != "NSComparisonResult") { - return false; - } - return member == "Ascending" || member == "Same" || member == "Descending"; -} } // namespace void ObjCBridgeState::registerEnumGlobals(napi_env env, napi_value global) { @@ -63,23 +52,17 @@ inline bool isNSComparisonResultOrderingName(const std::string& enumName, while (offset < metadata->signaturesOffset) { MDSectionOffset originalOffset = offset; auto name = metadata->getString(offset); - offset += sizeof(MDSectionOffset); - - bool next = true; - while (next) { - auto nameOffset = metadata->getOffset(offset); - next = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - auto memberName = metadata->resolveString(nameOffset); - offset += sizeof(MDSectionOffset); - int64_t value = metadata->getEnumValue(offset); - offset += sizeof(int64_t); - - napi_value member; - napi_create_int64(env, value, &member); - defineConstantIfMissing(env, global, memberName, member, - (napi_property_attributes)(napi_enumerable | napi_configurable)); - } + + offset = metadata->forEachEnumMember( + originalOffset, [&](const char*, const std::string& cName, int64_t value) { + if (cName.empty()) { + return; + } + napi_value member; + napi_create_int64(env, value, &member); + defineConstantIfMissing(env, global, cName, member, + (napi_property_attributes)(napi_enumerable | napi_configurable)); + }); napi_property_descriptor prop = { .utf8name = name, @@ -112,71 +95,23 @@ inline bool isNSComparisonResultOrderingName(const std::string& enumName, // enum name auto enumName = bridgeState->metadata->getString(offset); std::string enumNameStr = enumName; - offset += sizeof(MDSectionOffset); std::string strippedPrefix = stripEnumSuffix(enumNameStr); - napi_value global; - napi_get_global(env, &global); - - bool next = true; - while (next) { - auto nameOffset = bridgeState->metadata->getOffset(offset); - next = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - auto name = bridgeState->metadata->resolveString(nameOffset); - offset += sizeof(MDSectionOffset); - int64_t value = bridgeState->metadata->getEnumValue(offset); - offset += sizeof(int64_t); - + bridgeState->metadata->forEachEnumMember(originalOffset, [&](const char* name, + const std::string& cName, + int64_t value) { napi_value member; napi_create_int64(env, value, &member); std::string memberName = name; - std::vector aliases; - aliases.push_back(memberName); - - if (!strippedPrefix.empty() && startsWith(memberName, strippedPrefix) && - memberName.size() > strippedPrefix.size()) { - aliases.push_back(memberName.substr(strippedPrefix.size())); - } else if (!strippedPrefix.empty() && !startsWith(memberName, strippedPrefix)) { - aliases.push_back(strippedPrefix + memberName); - } + auto aliases = enum_members::aliasesFor(enumNameStr, strippedPrefix, memberName, cName); - if (startsWith(enumNameStr, "NS") && !startsWith(memberName, "NS")) { - aliases.push_back(std::string("NS") + memberName); - } - - if (enumNameStr == "NSStringCompareOptions" && !memberName.ends_with("Search")) { - aliases.push_back(memberName + "Search"); - aliases.push_back(std::string("NS") + memberName + "Search"); - } - - if (!startsWith(memberName, "k")) { - aliases.push_back(std::string("k") + enumNameStr + memberName); - } - - if (isNSComparisonResultOrderingName(enumNameStr, memberName)) { - aliases.push_back(std::string("Ordered") + memberName); - aliases.push_back(std::string("NSOrdered") + memberName); - } - - std::vector uniqueAliases; - uniqueAliases.reserve(aliases.size()); - std::unordered_set seenAliases; for (const auto& alias : aliases) { - if (seenAliases.insert(alias).second) { - uniqueAliases.push_back(alias); - } + defineConstantIfMissing(env, result, alias.name, member); } - for (const auto& alias : uniqueAliases) { - defineConstantIfMissing(env, result, alias, member); - defineConstantIfMissing(env, global, alias, member, - (napi_property_attributes)(napi_enumerable | napi_configurable)); - } - - std::string reverseCanonical = uniqueAliases.size() > 1 ? uniqueAliases[1] : memberName; + const std::string& reverseCanonical = aliases.size() > 1 ? aliases[1].name : memberName; // reverse mapping enum[value] -> canonical member name char valueKey[32]; @@ -197,7 +132,7 @@ inline bool isNSComparisonResultOrderingName(const std::string& enumName, }; napi_define_properties(env, result, 1, &reverseProp); } - } + }); bridgeState->mdValueCache[originalOffset] = make_ref(env, result); diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h index 9f945f8e0..302546aef 100644 --- a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h @@ -24,6 +24,7 @@ #include "Metadata.h" #include "MetadataReader.h" #include "ffi.h" +#include "ffi/objc/shared/EnumMemberAliases.h" #include "jsi/quickjs/QuickJSRuntime.h" diff --git a/NativeScript/ffi/objc/shared/EnumMemberAliases.h b/NativeScript/ffi/objc/shared/EnumMemberAliases.h new file mode 100644 index 000000000..4040744db --- /dev/null +++ b/NativeScript/ffi/objc/shared/EnumMemberAliases.h @@ -0,0 +1,85 @@ +#ifndef NATIVESCRIPT_FFI_OBJC_SHARED_ENUM_MEMBER_ALIASES_H +#define NATIVESCRIPT_FFI_OBJC_SHARED_ENUM_MEMBER_ALIASES_H + +#include +#include +#include + +namespace nativescript { +namespace enum_members { + +struct Alias { + std::string name; + // Only the C name may become a global; the other aliases are enum-object + // conveniences and can shadow unrelated globals (e.g. Center, Zone). + bool isCName = false; +}; + +inline bool startsWith(const std::string& value, const std::string& prefix) { + return value.size() >= prefix.size() && + value.compare(0, prefix.size(), prefix) == 0; +} + +inline bool endsWith(const std::string& value, const std::string& suffix) { + return value.size() >= suffix.size() && + value.compare(value.size() - suffix.size(), suffix.size(), suffix) == + 0; +} + +// The first alias is always the stored member name. cName is empty when the +// metadata does not record C names; then no alias is tagged as the C name. +inline std::vector aliasesFor(const std::string& enumName, + const std::string& strippedEnumName, + const std::string& storedName, + const std::string& cName) { + std::vector names; + names.push_back(storedName); + + if (!strippedEnumName.empty() && startsWith(storedName, strippedEnumName) && + storedName.size() > strippedEnumName.size()) { + names.push_back(storedName.substr(strippedEnumName.size())); + } else if (!strippedEnumName.empty() && + !startsWith(storedName, strippedEnumName)) { + names.push_back(strippedEnumName + storedName); + } + + if (startsWith(enumName, "NS") && !startsWith(storedName, "NS")) { + names.push_back(std::string("NS") + storedName); + } + + if (enumName == "NSStringCompareOptions" && !endsWith(storedName, "Search")) { + names.push_back(storedName + "Search"); + names.push_back(std::string("NS") + storedName + "Search"); + } + + if (!startsWith(storedName, "k")) { + names.push_back(std::string("k") + enumName + storedName); + } + + if (enumName == "NSComparisonResult" && + (storedName == "Ascending" || storedName == "Same" || + storedName == "Descending")) { + names.push_back(std::string("Ordered") + storedName); + names.push_back(std::string("NSOrdered") + storedName); + } + + if (!cName.empty()) { + names.push_back(cName); + } + + std::vector aliases; + aliases.reserve(names.size()); + std::unordered_set seen; + for (auto& name : names) { + if (!name.empty() && seen.insert(name).second) { + bool isCName = name == cName; + aliases.push_back(Alias{std::move(name), isCName}); + } + } + return aliases; +} + +} // namespace enum_members +} // namespace nativescript + +#endif // NATIVESCRIPT_FFI_OBJC_SHARED_ENUM_MEMBER_ALIASES_H diff --git a/NativeScript/ffi/objc/shared/bridge/HostObject.mm b/NativeScript/ffi/objc/shared/bridge/HostObject.mm index d16a91f96..5150f0aed 100644 --- a/NativeScript/ffi/objc/shared/bridge/HostObject.mm +++ b/NativeScript/ffi/objc/shared/bridge/HostObject.mm @@ -413,6 +413,21 @@ throw JSError(runtime, return enumToObject(runtime, bridge->metadata(), *symbol); }); } + if (property == "getEnumGlobalNames") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getEnumGlobalNames"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string enumName = readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findEnum(enumName); + if (symbol == nullptr) { + return namesToArray(runtime, {}); + } + return namesToArray(runtime, + enumGlobalNames(bridge->metadata(), *symbol)); + }); + } if (property == "getProtocol") { auto bridge = bridge_; return Function::createFromHostFunction( @@ -528,6 +543,7 @@ throw JSError(runtime, addPropertyName(runtime, names, "getFunction"); addPropertyName(runtime, names, "getConstant"); addPropertyName(runtime, names, "getEnum"); + addPropertyName(runtime, names, "getEnumGlobalNames"); addPropertyName(runtime, names, "getProtocol"); addPropertyName(runtime, names, "getStruct"); addPropertyName(runtime, names, "getUnion"); diff --git a/NativeScript/ffi/objc/shared/bridge/Install.mm b/NativeScript/ffi/objc/shared/bridge/Install.mm index 611747cf8..4c219a0c0 100644 --- a/NativeScript/ffi/objc/shared/bridge/Install.mm +++ b/NativeScript/ffi/objc/shared/bridge/Install.mm @@ -1754,16 +1754,10 @@ function resolveNativeApiEnum(enumName) { }); enumNameList.forEach(function(name) { defineLazyGlobal(name, resolveNativeApiEnum, false, 'enum'); - var enumValue = resolveNativeApiEnum(name); - if (!enumValue || typeof enumValue !== 'object') { - return; - } - Object.keys(enumValue).forEach(function(memberName) { - if (/^-?\d+$/.test(memberName)) { - return; - } + api.getEnumGlobalNames(name).forEach(function(memberName) { defineLazyGlobal(memberName, function() { - return enumValue[memberName]; + var enumValue = resolveNativeApiEnum(name); + return enumValue && enumValue[memberName]; }, false, 'enumMember:' + name); }); }); diff --git a/NativeScript/ffi/objc/shared/bridge/Invocation.mm b/NativeScript/ffi/objc/shared/bridge/Invocation.mm index af32e2d3e..0086c26a7 100644 --- a/NativeScript/ffi/objc/shared/bridge/Invocation.mm +++ b/NativeScript/ffi/objc/shared/bridge/Invocation.mm @@ -30,14 +30,6 @@ bool endsWith(const std::string& value, const std::string& suffix) { return enumName; } -bool isNSComparisonResultOrderingName(const std::string& enumName, - const std::string& member) { - if (enumName != "NSComparisonResult") { - return false; - } - return member == "Ascending" || member == "Same" || member == "Descending"; -} - class NativeApiReturnStorage { public: explicit NativeApiReturnStorage(size_t size) @@ -101,72 +93,45 @@ Value enumToObject(Runtime& runtime, MDMetadataReader* metadata, return result; } - std::string enumName = symbol.name; + const std::string& enumName = symbol.name; std::string strippedPrefix = stripEnumSuffix(enumName); - MDSectionOffset offset = symbol.offset + sizeof(MDSectionOffset); - bool next = true; - while (next) { - auto nameOffset = metadata->getOffset(offset); - next = (nameOffset & metagen::mdSectionOffsetNext) != 0; - nameOffset &= ~metagen::mdSectionOffsetNext; - offset += sizeof(MDSectionOffset); - - const char* memberName = metadata->resolveString(nameOffset); - int64_t value = metadata->getEnumValue(offset); - offset += sizeof(int64_t); - - std::string canonicalName = memberName != nullptr ? memberName : ""; - std::vector aliases; - aliases.push_back(canonicalName); - - if (!strippedPrefix.empty() && startsWith(canonicalName, strippedPrefix) && - canonicalName.size() > strippedPrefix.size()) { - aliases.push_back(canonicalName.substr(strippedPrefix.size())); - } else if (!strippedPrefix.empty() && - !startsWith(canonicalName, strippedPrefix)) { - aliases.push_back(strippedPrefix + canonicalName); - } - - if (startsWith(enumName, "NS") && !startsWith(canonicalName, "NS")) { - aliases.push_back(std::string("NS") + canonicalName); - } - - if (enumName == "NSStringCompareOptions" && - !endsWith(canonicalName, "Search")) { - aliases.push_back(canonicalName + "Search"); - aliases.push_back(std::string("NS") + canonicalName + "Search"); - } - - if (!startsWith(canonicalName, "k")) { - aliases.push_back(std::string("k") + enumName + canonicalName); - } - - if (isNSComparisonResultOrderingName(enumName, canonicalName)) { - aliases.push_back(std::string("Ordered") + canonicalName); - aliases.push_back(std::string("NSOrdered") + canonicalName); - } - - std::vector uniqueAliases; - std::unordered_set seenAliases; - for (const auto& alias : aliases) { - if (!alias.empty() && seenAliases.insert(alias).second) { - uniqueAliases.push_back(alias); - } - } + metadata->forEachEnumMember( + symbol.offset, [&](const char* memberName, const std::string& cName, + int64_t value) { + std::string storedName = memberName != nullptr ? memberName : ""; + auto aliases = enum_members::aliasesFor(enumName, strippedPrefix, + storedName, cName); + for (const auto& alias : aliases) { + result.setProperty(runtime, alias.name.c_str(), + static_cast(value)); + } - for (const auto& alias : uniqueAliases) { - result.setProperty(runtime, alias.c_str(), static_cast(value)); - } + char valueKey[32] = {}; + snprintf(valueKey, sizeof(valueKey), "%lld", + static_cast(value)); + if (!result.hasProperty(runtime, valueKey)) { + const std::string& reverseName = + aliases.size() > 1 ? aliases[1].name : storedName; + result.setProperty(runtime, valueKey, + makeString(runtime, reverseName)); + } + }); + return result; +} - char valueKey[32] = {}; - snprintf(valueKey, sizeof(valueKey), "%lld", static_cast(value)); - if (!result.hasProperty(runtime, valueKey)) { - std::string reverseName = - uniqueAliases.size() > 1 ? uniqueAliases[1] : canonicalName; - result.setProperty(runtime, valueKey, makeString(runtime, reverseName)); - } +std::vector enumGlobalNames(MDMetadataReader* metadata, + const NativeApiSymbol& symbol) { + std::vector names; + if (metadata == nullptr || symbol.offset == MD_SECTION_OFFSET_NULL) { + return names; } - return result; + metadata->forEachEnumMember( + symbol.offset, [&](const char*, const std::string& cName, int64_t) { + if (!cName.empty()) { + names.push_back(cName); + } + }); + return names; } Value constantToValue(Runtime& runtime, diff --git a/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm b/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm index 5baecf609..f2efcf351 100644 --- a/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm +++ b/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm @@ -1591,15 +1591,7 @@ void indexEnums() { MDSectionOffset originalOffset = offset; addSymbol(NativeApiSymbolKind::Enum, originalOffset, metadata_->getString(offset)); - offset += sizeof(MDSectionOffset); - - bool next = true; - while (next) { - auto nameOffset = metadata_->getOffset(offset); - next = (nameOffset & metagen::mdSectionOffsetNext) != 0; - offset += sizeof(MDSectionOffset); - offset += sizeof(int64_t); - } + offset = metadata_->skipEnum(originalOffset); } } diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h index 67c59063d..bc28ab2a3 100644 --- a/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h +++ b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h @@ -24,6 +24,7 @@ #include "Metadata.h" #include "MetadataReader.h" #include "ffi.h" +#include "ffi/objc/shared/EnumMemberAliases.h" #include "jsi/v8/V8Runtime.h" diff --git a/metadata-generator/include/IR.h b/metadata-generator/include/IR.h index eb05dc7fc..774ec9ecd 100644 --- a/metadata-generator/include/IR.h +++ b/metadata-generator/include/IR.h @@ -97,6 +97,7 @@ enum VariableConstEvalKind { struct EnumConstDecl { std::string name; + std::string cName; int64_t value; AvailabilityInfo availability; }; @@ -131,6 +132,10 @@ class EnumDecl { std::string name; std::vector constants; + // Stripped from every member's cName to form its name, except members + // whose name is their cName. + std::string memberPrefix; + std::string memberSuffix; MDSectionOffset mdOffset = MD_SECTION_OFFSET_NULL; }; diff --git a/metadata-generator/include/Metadata.h b/metadata-generator/include/Metadata.h index 8ebc6e9dd..70398f3c1 100644 --- a/metadata-generator/include/Metadata.h +++ b/metadata-generator/include/Metadata.h @@ -18,6 +18,9 @@ typedef uint32_t MDSectionOffset; enum MDSectionOffsetFlag : uint32_t { mdSectionOffsetNext = 1u << 31, + // Enum member names only: the stored name is the full C name rather than + // one shortened by the enum's member prefix/suffix. + mdSectionOffsetEnumMemberCName = 1u << 30, }; // Bits 0-1: flags (2 flags) @@ -113,7 +116,11 @@ enum MDFunctionFlag : uint8_t { #define MD_HEADER_MAGIC_SIZE 4 #define MD_HEADER_MAGIC "NSMD" #define MD_HEADER_VERSION_SIZE 2 -#define MD_HEADER_VERSION 1 +#define MD_HEADER_VERSION 2 +#define MD_HEADER_MIN_VERSION 1 +// From this version, enum entries carry the member prefix/suffix and member +// names carry mdSectionOffsetEnumMemberCName. +#define MD_HEADER_VERSION_ENUM_MEMBER_C_NAMES 2 #define MD_HEADER_SIZE (MD_HEADER_MAGIC_SIZE + MD_HEADER_VERSION_SIZE) #define MD_NUM_SECTIONS 9 diff --git a/metadata-generator/include/MetadataReader.h b/metadata-generator/include/MetadataReader.h index 4a7595322..a1f444582 100644 --- a/metadata-generator/include/MetadataReader.h +++ b/metadata-generator/include/MetadataReader.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "Metadata.h" @@ -14,6 +15,7 @@ class MDMetadataReader { public: void* data; bool ownsData = false; + uint16_t version = 0; MDSectionOffset stringsOffset; MDSectionOffset constantsOffset; @@ -35,9 +37,8 @@ class MDMetadataReader { } ptr_add(&data, MD_HEADER_MAGIC_SIZE); - uint16_t version; memcpy(&version, data, MD_HEADER_VERSION_SIZE); - if (version != MD_HEADER_VERSION) { + if (version < MD_HEADER_MIN_VERSION || version > MD_HEADER_VERSION) { fprintf(stderr, "Invalid metadata header version\n"); exit(1); } @@ -95,6 +96,66 @@ class MDMetadataReader { return *(int64_t*)((char*)data + offset); } + inline bool hasEnumMemberCNames() const { + return version >= MD_HEADER_VERSION_ENUM_MEMBER_C_NAMES; + } + + // Visits fn(name, cName, value) for each member of the enum entry at + // enumOffset and returns the offset just past the entry. cName is empty when + // the metadata predates stored C names; it cannot be derived from name. + template + inline MDSectionOffset forEachEnumMember(MDSectionOffset enumOffset, + Fn&& fn) { + MDSectionOffset offset = enumOffset + sizeof(MDSectionOffset); + const bool hasCNames = hasEnumMemberCNames(); + const char* prefix = ""; + const char* suffix = ""; + if (hasCNames) { + prefix = getString(offset); + offset += sizeof(MDSectionOffset); + suffix = getString(offset); + offset += sizeof(MDSectionOffset); + } + + bool next = true; + while (next) { + MDSectionOffset nameOffset = getOffset(offset); + next = (nameOffset & mdSectionOffsetNext) != 0; + const bool nameIsCName = + hasCNames && (nameOffset & mdSectionOffsetEnumMemberCName) != 0; + if (hasCNames) { + nameOffset &= ~mdSectionOffsetEnumMemberCName; + } + nameOffset &= ~mdSectionOffsetNext; + offset += sizeof(MDSectionOffset); + int64_t value = getEnumValue(offset); + offset += sizeof(int64_t); + + const char* name = resolveString(nameOffset); + std::string cName; + if (nameIsCName) { + cName = name; + } else if (hasCNames) { + cName.append(prefix).append(name).append(suffix); + } + fn(name, cName, value); + } + return offset; + } + + inline MDSectionOffset skipEnum(MDSectionOffset enumOffset) { + MDSectionOffset offset = enumOffset + sizeof(MDSectionOffset); + if (hasEnumMemberCNames()) { + offset += 2 * sizeof(MDSectionOffset); + } + bool next = true; + while (next) { + next = (getOffset(offset) & mdSectionOffsetNext) != 0; + offset += sizeof(MDSectionOffset) + sizeof(int64_t); + } + return offset; + } + inline MDMemberFlag getMemberFlag(MDSectionOffset offset) { return *(MDMemberFlag*)((char*)data + offset); } diff --git a/metadata-generator/include/MetadataWriter.h b/metadata-generator/include/MetadataWriter.h index 711b4d2e8..fe69b860e 100644 --- a/metadata-generator/include/MetadataWriter.h +++ b/metadata-generator/include/MetadataWriter.h @@ -40,11 +40,14 @@ struct MDVariable { struct MDEnumMember { MDSectionOffset name; + bool nameIsCName; int64_t value; }; struct MDEnum { MDSectionOffset name; + MDSectionOffset memberPrefix; + MDSectionOffset memberSuffix; std::vector members; }; diff --git a/metadata-generator/src/IR/Enum.cpp b/metadata-generator/src/IR/Enum.cpp index ba046c583..e82429221 100644 --- a/metadata-generator/src/IR/Enum.cpp +++ b/metadata-generator/src/IR/Enum.cpp @@ -13,7 +13,8 @@ namespace metagen { // - Deferred // Basically, split the name into parts (assuming camel case), and then remove // the repeating parts from the beginning and end of the name. -void transformEnumMemberNames(std::vector& members) { +void transformEnumMemberNames(std::vector& members, + std::string& prefix, std::string& suffix) { std::vector> parts; parts.reserve(members.size()); size_t skip_begin = 0, skip_end = 0; @@ -69,6 +70,13 @@ void transformEnumMemberNames(std::vector& members) { } } + for (size_t j = 0; j < skip_begin; j++) { + prefix += largestPart[j]; + } + for (size_t j = largestPart.size() - skip_end; j < largestPart.size(); j++) { + suffix += largestPart[j]; + } + for (size_t i = 0; i < members.size(); i++) { const auto& part = parts[i]; std::string name; @@ -119,6 +127,7 @@ EnumDecl::EnumDecl(CXCursor cursor) { auto value = clang_getEnumConstantDeclValue(cursor); EnumConstDecl decl; decl.name = nameStr; + decl.cName = nameStr; decl.value = value; if (gCaptureAvailability) { decl.availability = getAvailabilityInfo(cursor); @@ -135,7 +144,8 @@ EnumDecl::EnumDecl(CXCursor cursor) { }, this); - if (!name.empty()) transformEnumMemberNames(constants); + if (!name.empty()) + transformEnumMemberNames(constants, memberPrefix, memberSuffix); } void MetadataFactory::processEnumRefs() { diff --git a/metadata-generator/src/IR/Factory.cpp b/metadata-generator/src/IR/Factory.cpp index 932bf5e44..b0c25efdc 100644 --- a/metadata-generator/src/IR/Factory.cpp +++ b/metadata-generator/src/IR/Factory.cpp @@ -366,6 +366,8 @@ void MetadataFactory::processEnum(CXCursor cursor, bool required) { if (enumIt != enums.end()) { if (enumIt->second.constants.size() < decl.constants.size()) { enumIt->second.constants = std::move(decl.constants); + enumIt->second.memberPrefix = std::move(decl.memberPrefix); + enumIt->second.memberSuffix = std::move(decl.memberSuffix); } return; } diff --git a/metadata-generator/src/MetadataWriter/Enum.cpp b/metadata-generator/src/MetadataWriter/Enum.cpp index a4abe3109..48256137e 100644 --- a/metadata-generator/src/MetadataWriter/Enum.cpp +++ b/metadata-generator/src/MetadataWriter/Enum.cpp @@ -10,11 +10,25 @@ void MDMetadataWriter::write(EnumDecl &decl) { MDEnum *mdEnum = new MDEnum(); mdEnum->name = strings.add(decl.name, decl.name); + mdEnum->memberPrefix = strings.add(decl.memberPrefix, decl.memberPrefix); + mdEnum->memberSuffix = strings.add(decl.memberSuffix, decl.memberSuffix); mdEnum->members.reserve(decl.constants.size()); for (const EnumConstDecl &c : decl.constants) { MDEnumMember member; member.name = strings.add(c.name, c.name); + if ((member.name & mdSectionOffsetEnumMemberCName) != 0) { + std::cerr << "Strings section too large to flag enum member names" + << std::endl; + exit(1); + } + member.nameIsCName = c.name == c.cName; + if (!member.nameIsCName && + decl.memberPrefix + c.name + decl.memberSuffix != c.cName) { + std::cerr << "Enum " << decl.name << ": cannot encode the C name of " + << c.cName << std::endl; + exit(1); + } member.value = c.value; mdEnum->members.push_back(member); } @@ -33,7 +47,11 @@ size_t MDEnumMemberSerde::size(MDEnumMember *value) { void MDEnumMemberSerde::serialize(MDEnumMember *value, void *data) { // Name - binwrite(value->name); + MDSectionOffset name = value->name; + if (value->nameIsCName) { + name |= mdSectionOffsetEnumMemberCName; + } + binwrite(name); // Value binwrite(value->value); } @@ -42,6 +60,8 @@ size_t MDEnumSerde::size(MDEnum *value) { size_t size = 0; // Name addsize(value->name); + addsize(value->memberPrefix); + addsize(value->memberSuffix); // Members MDEnumMemberSerde memberSerde; if (!value->members.empty()) { @@ -53,6 +73,8 @@ size_t MDEnumSerde::size(MDEnum *value) { void MDEnumSerde::serialize(MDEnum *value, void *data) { // Name binwrite(value->name); + binwrite(value->memberPrefix); + binwrite(value->memberSuffix); // Members size_t membersSize = value->members.size(); MDEnumMemberSerde memberSerde; diff --git a/packages/react-native/src/index.d.ts b/packages/react-native/src/index.d.ts index 3e15e684d..96c55f20a 100644 --- a/packages/react-native/src/index.d.ts +++ b/packages/react-native/src/index.d.ts @@ -30,6 +30,8 @@ export type NativeApiHost = { getClass?: (name: string) => unknown; getProtocol?: (name: string) => unknown; getEnum?: (name: string) => unknown; + /** C constant names of the enum's members, which installGlobals() defines as globals. */ + getEnumGlobalNames?: (name: string) => string[]; getStruct?: (name: string) => unknown; getUnion?: (name: string) => unknown; [name: string]: unknown; diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index b15d01120..14baeaa06 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -31,6 +31,7 @@ type NativeApiHost = { getClass?: (name: string) => unknown; getProtocol?: (name: string) => unknown; getEnum?: (name: string) => unknown; + getEnumGlobalNames?: (name: string) => string[]; getStruct?: (name: string) => unknown; getUnion?: (name: string) => unknown; [name: string]: unknown; @@ -943,18 +944,13 @@ export function installGlobals(): boolean { api.getEnum?.(enumName) ?? api[enumName]; defineLazyNativeGlobal(name, resolveEnum); - const enumValue = resolveEnum(name); - if (!enumValue || typeof enumValue !== "object") { - continue; - } - for (const memberName of Object.keys(enumValue)) { - if (/^-?\d+$/.test(memberName)) { - continue; - } - defineLazyNativeGlobal( - memberName, - () => (enumValue as Record)[memberName], - ); + for (const memberName of api.getEnumGlobalNames?.(name) ?? []) { + defineLazyNativeGlobal(memberName, () => { + const enumValue = resolveEnum(name) as + | Record + | undefined; + return enumValue?.[memberName]; + }); } } diff --git a/platforms/apple/test/runtime/runner/app/tests/Marshalling/EnumTests.js b/platforms/apple/test/runtime/runner/app/tests/Marshalling/EnumTests.js index 2dd33c858..b9786514b 100644 --- a/platforms/apple/test/runtime/runner/app/tests/Marshalling/EnumTests.js +++ b/platforms/apple/test/runtime/runner/app/tests/Marshalling/EnumTests.js @@ -43,6 +43,30 @@ describe(module.id, function () { expect(NSOrderedAscending).toBe(-1); expect(NSOrderedSame).toBe(0); expect(NSOrderedDescending).toBe(1); + expect(NSTextAlignmentCenter).toBe(NSTextAlignment.Center); + expect(kCLAuthorizationStatusRestricted).toBe(1); + expect(NSCaseInsensitiveSearch).toBe(1); + }); + + it("EnumConstantsWhoseNameDiffersFromTheEnumName", function () { + expect(MKMapTypeStandard).toBe(0); + expect(MKMapType.MKMapTypeStandard).toBe(0); + expect(CKErrorZoneBusy).toBe(23); + expect(CKErrorCode.CKErrorZoneBusy).toBe(23); + expect(CKRecordZoneCapabilityAtomic).toBe(2); + expect(CKRecordZoneEncryptionScopePerZone).toBe(1); + }); + + it("EnumMemberAliasesAreNotGlobalVariables", function () { + expect(NSTextAlignment.Center).toBe(1); + expect(CKRecordZoneEncryptionScope.Zone).toBe(1); + expect(CKErrorCode.ZoneBusy).toBe(23); + + ["Center", "Zone", "Ascending", "Restricted", "ZoneBusy", + "NSComparisonAscending", "kNSTextAlignmentCenter"].forEach(function (name) { + expect(name + " in globalThis: " + (name in globalThis)) + .toBe(name + " in globalThis: false"); + }); }); it("EnumWithOneValue", function () {