Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions NativeScript/runtime/ArgConverter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,24 @@
if ((!meta->hasErrorOutParameter() && args.Length() != argsCount) ||
(meta->hasErrorOutParameter() && args.Length() != argsCount &&
args.Length() != argsCount - 1)) {
// NOTE: The message used to read "Actual arguments count: <expected>. Expected: <actual>.",
// which had the two numbers swapped. This made the log impossible to act on without reading
// the runtime source. We now spell it out (with swap corrected) and include the call site.
const char* jsNameStr = meta ? meta->jsName() : "<unknown>";
const char* selectorStr = meta ? meta->selectorAsString() : "<unknown>";
const char* classNameStr = klass ? class_getName(klass) : "<unknown>";
const char* methodKindStr = instanceMethod ? "instance" : "static";

std::ostringstream errorStream;
errorStream << "Actual arguments count: \"" << argsCount << ". Expected: \"" << args.Length()
<< "\".";
errorStream << "Actual arguments count: \"" << args.Length() << "\". Expected: \"" << argsCount
<< "\"";
if (meta && meta->hasErrorOutParameter()) {
errorStream << " (or \"" << (argsCount - 1)
<< "\" if omitting the trailing NSError** out-parameter)";
}
errorStream << " for " << methodKindStr << " method \"" << jsNameStr << "\" on class \""
<< classNameStr << "\" (selector: -[" << classNameStr << " " << selectorStr
<< "]).";
Comment on lines +113 to +124
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use +[ when formatting static-method selectors.

Line 123 always renders the Objective-C selector as -[Class selector]. For class methods, the message now says static method but still prints an instance selector, which makes the new call-site context misleading.

Proposed fix
     const char* classNameStr = klass ? class_getName(klass) : "<unknown>";
     const char* methodKindStr = instanceMethod ? "instance" : "static";
+    const char selectorKind = instanceMethod ? '-' : '+';
 
     std::ostringstream errorStream;
     errorStream << "Actual arguments count: \"" << args.Length() << "\". Expected: \"" << argsCount
                 << "\"";
     if (meta && meta->hasErrorOutParameter()) {
@@
     }
     errorStream << " for " << methodKindStr << " method \"" << jsNameStr << "\" on class \""
-                << classNameStr << "\" (selector: -[" << classNameStr << " " << selectorStr
+                << classNameStr << "\" (selector: " << selectorKind << "[" << classNameStr << " " << selectorStr
                 << "]).";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@NativeScript/runtime/ArgConverter.mm` around lines 113 - 124, The error
message always formats the Objective-C selector as an instance selector "-[Class
selector]" even when reporting a static method; update the string construction
around errorStream (where methodKindStr is computed from instanceMethod) to
choose "+[" when instanceMethod is false so the selector is printed as "+[Class
selector]" for class methods; modify the logic that appends " (selector: -[" <<
classNameStr << " " << selectorStr << "])" to switch the prefix based on
instanceMethod (use methodKindStr or a conditional to select "+" vs "-") so
jsNameStr, classNameStr and selectorStr remain unchanged but the selector
punctuation correctly reflects instance vs static methods.

std::string errorMessage = errorStream.str();
throw NativeScriptException(errorMessage);
}
Expand Down
33 changes: 32 additions & 1 deletion NativeScript/runtime/Metadata.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef Metadata_h
#define Metadata_h

#include <cstring>
#include <stack>
#include <string>
#include <type_traits>
Expand Down Expand Up @@ -748,7 +749,37 @@ struct MethodMeta : MemberMeta {
}

inline bool hasErrorOutParameter() const {
return this->flag(MetaFlags::MethodHasErrorOutParameter);
if (this->flag(MetaFlags::MethodHasErrorOutParameter)) {
return true;
}

// Fallback: The MethodHasErrorOutParameter flag is set by the metadata
// generator via a type-tree walk that historically missed methods whose
// last parameter uses `_Nullable` nullability (e.g.
// `NSError * _Nullable * _Nullable` on modern iOS SDK headers).
// The *binary* encoding does preserve the underlying shape (NullableType
// is unwrapped during serialization), so we can recover the missing
// signal by inspecting the last parameter encoding directly. This keeps
// apps built against older/buggy metadata working with the auto-error
// handling in Interop::CallFunctionInternal and ArgConverter::Invoke.
const TypeEncodingsList<ArrayCount>* enc = this->encodings();
if (enc == nullptr || enc->count < 2) {
return false;
}
// TypeEncodingsList layout: [returnType, param1, param2, ..., lastParam]
const TypeEncoding* cur = enc->first();
for (int i = 0; i < enc->count - 1; i++) {
cur = cur->next();
}
if (cur == nullptr || cur->type != BinaryTypeEncodingType::PointerEncoding) {
return false;
}
const TypeEncoding* innerEnc = cur->details.pointer.getInnerType();
if (innerEnc == nullptr || innerEnc->type != BinaryTypeEncodingType::InterfaceDeclarationReference) {
return false;
}
const char* innerName = innerEnc->details.interfaceDeclarationReference.name.valuePtr();
return innerName != nullptr && strcmp(innerName, "NSError") == 0;
}

inline bool isInitializer() const {
Expand Down
3 changes: 2 additions & 1 deletion NativeScript/runtime/ObjectManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@

if (info.Length() != 1) {
std::ostringstream errorStream;
errorStream << "Actual arguments count: \"" << info.Length() << "\". Expected: \"1\".";
errorStream << "Actual arguments count: \"" << info.Length() << "\". Expected: \"1\"."
<< " (function: __releaseNativeCounterpart(nativeObject))";
std::string errorMessage = errorStream.str();
Local<Value> error = Exception::Error(tns::ToV8String(isolate, errorMessage));
isolate->ThrowException(error);
Expand Down
11 changes: 11 additions & 0 deletions metadata-generator/src/Meta/MetaFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,17 @@ void MetaFactory::createFromMethod(const clang::ObjCMethodDecl& method,
isNullTerminatedVariadic);

// set MethodHasErrorOutParameter flag
//
// Modern iOS SDK headers annotate out-error parameters as
// `NSError * _Nullable * _Nullable`, which TypeFactory wraps in
// `NullableType`/`NonNullableType` nodes (see
// `TypeFactory::createFromAttributedType`). The original implementation only
// checked for a raw `PointerType(InterfaceType("NSError"))` and therefore
// missed the vast majority of NSError**-returning Objective-C selectors
// (createDirectoryAtPath:...:error:, removeItemAtPath:error:, etc.). Unwrap
// the nullability wrapper at both the outer pointer and inner interface
// levels so the flag is set correctly whether or not the SDK annotates
// nullability.
if (method.parameters().size() > 0) {
clang::ParmVarDecl* lastParameter =
method.parameters()[method.parameters().size() - 1];
Expand Down
Loading