mirror of
https://github.com/dlang/dmd.git
synced 2025-04-25 20:50:41 +03:00
Merge pull request #20559 from KitsunebiGames/objc
Objective-C Improvements Signed-off-by: Dennis <dkorpel@users.noreply.github.com> Signed-off-by: Nicholas Wilson <thewilsonator@users.noreply.github.com> Merged-on-behalf-of: Dennis <dkorpel@users.noreply.github.com>
This commit is contained in:
commit
059b1ab407
3 changed files with 160 additions and 12 deletions
38
changelog/dmd.objc-improvements.dd
Normal file
38
changelog/dmd.objc-improvements.dd
Normal file
|
@ -0,0 +1,38 @@
|
|||
Objective-C selectors are now automatically generated when not specified with @selector.
|
||||
|
||||
Additionally, the Objective-C selector generation rules have changed, following these steps:
|
||||
1. Functions marked with @property will generate `setXYZ:` for the setters.
|
||||
2. For property functions named with a "is" prefix, the `is` will be stripped off in the setter.
|
||||
3. Selector generation now uses the names of the function parameters instead of their D mangled types.
|
||||
|
||||
Selectors may still be specified with the @selector UDA, in which case it takes precedence over the
|
||||
automatically generated selectors.
|
||||
|
||||
These new rules apply both for extern and non-extern objective-c classes and protocols.
|
||||
|
||||
---
|
||||
extern(Objective-C)
|
||||
extern class NSObject {
|
||||
static NSObject alloc(); // Generates as `alloc`
|
||||
NSObject init(); // Generates as `init`
|
||||
}
|
||||
|
||||
extern(Objective-C)
|
||||
class Fox : NSObject {
|
||||
bool fluffy;
|
||||
|
||||
@property bool isFluffy() => fluffy; // `isFluffy`
|
||||
@property void isFluffy(bool value) { fluffy = value; } // `setFluffy:`
|
||||
|
||||
void yip(int a) @selector("bark:") { // `bark:`
|
||||
// ...
|
||||
}
|
||||
|
||||
void doSomething(int a, int b, int c) { // `doSomething:b:c:`
|
||||
// ...
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
These changes should not break any existing code as the automatic selector generation
|
||||
was not present before. And automatic selector generation only applies to extern(Objective-C) methods.
|
|
@ -93,39 +93,66 @@ struct ObjcSelector
|
|||
return sel;
|
||||
}
|
||||
|
||||
static const(char)[] toPascalCase(const(char)[] id) {
|
||||
OutBuffer buf;
|
||||
char firstChar = id[0];
|
||||
if (firstChar >= 'a' && firstChar <= 'z')
|
||||
firstChar = cast(char)(firstChar - 'a' + 'A');
|
||||
|
||||
buf.writeByte(firstChar);
|
||||
buf.writestring(id[1..$]);
|
||||
return cast(const(char)[])buf.extractSlice(false);
|
||||
}
|
||||
|
||||
extern (C++) static ObjcSelector* create(FuncDeclaration fdecl)
|
||||
{
|
||||
OutBuffer buf;
|
||||
auto ftype = cast(TypeFunction)fdecl.type;
|
||||
const id = fdecl.ident.toString();
|
||||
const nparams = ftype.parameterList.length;
|
||||
|
||||
// Special case: property setter
|
||||
if (ftype.isProperty && nparams == 1)
|
||||
{
|
||||
// rewrite "identifier" as "setIdentifier"
|
||||
char firstChar = id[0];
|
||||
if (firstChar >= 'a' && firstChar <= 'z')
|
||||
firstChar = cast(char)(firstChar - 'a' + 'A');
|
||||
buf.writestring("set");
|
||||
buf.writeByte(firstChar);
|
||||
buf.write(id[1 .. id.length - 1]);
|
||||
|
||||
// Special case: "isXYZ:"
|
||||
if (id.length >= 2 && id[0..2] == "is")
|
||||
{
|
||||
buf.writestring("set");
|
||||
buf.write(toPascalCase(id[2..$]));
|
||||
}
|
||||
else
|
||||
{
|
||||
buf.writestring("set");
|
||||
buf.write(toPascalCase(id));
|
||||
}
|
||||
buf.writeByte(':');
|
||||
goto Lcomplete;
|
||||
}
|
||||
|
||||
// write identifier in selector
|
||||
buf.write(id[]);
|
||||
// add mangled type and colon for each parameter
|
||||
if (nparams)
|
||||
|
||||
// To make it easier to match the selectors of objects nicely,
|
||||
// the implementation has been replaced so that the parameter name followed by a colon
|
||||
// is used instead.
|
||||
// eg. void myFunction(int a, int b, int c) would be mangled to a selector as `myFunction:b:c:
|
||||
if (nparams > 1)
|
||||
{
|
||||
buf.writeByte('_');
|
||||
foreach (i, fparam; ftype.parameterList)
|
||||
buf.writeByte(':');
|
||||
foreach(i; 1..nparams)
|
||||
{
|
||||
mangleToBuffer(fparam.type, buf);
|
||||
buf.write(ftype.parameterList[i].ident.toString());
|
||||
buf.writeByte(':');
|
||||
}
|
||||
}
|
||||
else if (nparams == 1)
|
||||
{
|
||||
buf.writeByte(':');
|
||||
}
|
||||
Lcomplete:
|
||||
buf.writeByte('\0');
|
||||
|
||||
// the slice is not expected to include a terminating 0
|
||||
return lookup(cast(const(char)*)buf[].ptr, buf.length - 1, nparams);
|
||||
}
|
||||
|
@ -565,6 +592,16 @@ extern(C++) private final class Supported : Objc
|
|||
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Avoid attempting to generate selectors for template instances.
|
||||
if (fd.parent && fd.parent.isTemplateInstance())
|
||||
return;
|
||||
|
||||
// No selector declared, generate one.
|
||||
if (fd._linkage == LINK.objc && !fd.objc.selector)
|
||||
{
|
||||
fd.objc.selector = ObjcSelector.create(fd);
|
||||
}
|
||||
}
|
||||
|
||||
override void validateSelector(FuncDeclaration fd)
|
||||
|
|
73
compiler/test/runnable/objc_autoselector.d
Normal file
73
compiler/test/runnable/objc_autoselector.d
Normal file
|
@ -0,0 +1,73 @@
|
|||
// EXTRA_OBJC_SOURCES:
|
||||
// REQUIRED_ARGS: -L-framework -LFoundation
|
||||
|
||||
extern(Objective-C)
|
||||
extern class NSObject
|
||||
{
|
||||
static NSObject alloc();
|
||||
NSObject init();
|
||||
|
||||
@property NSString className() const;
|
||||
}
|
||||
|
||||
extern(Objective-C)
|
||||
extern class NSString : NSObject
|
||||
{
|
||||
override static NSString alloc();
|
||||
override NSString init();
|
||||
|
||||
@property const(char)* UTF8String() const;
|
||||
}
|
||||
|
||||
extern(Objective-C)
|
||||
class MyClass : NSObject
|
||||
{
|
||||
int x;
|
||||
|
||||
override static MyClass alloc();
|
||||
override MyClass init() { x = 42; return this; }
|
||||
|
||||
@property bool isFourtyTwo() => x == 42;
|
||||
@property void isFourtyTwo(bool value) { x = value ? 42 : 0; }
|
||||
|
||||
void myFunction(int a, int b, int c)
|
||||
{
|
||||
x = a + b + c;
|
||||
}
|
||||
}
|
||||
|
||||
extern(C) void* object_getClass(NSObject obj);
|
||||
extern(C) void* class_getInstanceMethod(void* cls, void* sel);
|
||||
extern(C) void* method_getName(void* m);
|
||||
extern(C) void* sel_registerName(const(char)* str);
|
||||
extern(C) bool sel_isEqual(void* lhs, void* rhs);
|
||||
|
||||
bool validateMethod(NSObject obj, const(char)* selName)
|
||||
{
|
||||
auto sel = sel_registerName(selName);
|
||||
|
||||
auto cls = object_getClass(obj);
|
||||
if (auto mth = class_getInstanceMethod(cls, sel)) {
|
||||
return sel_isEqual(sel, method_getName(mth));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
// Basic alloc & init
|
||||
auto obj = NSObject.alloc.init;
|
||||
assert(obj !is null);
|
||||
|
||||
// Basic property
|
||||
auto cname = obj.className();
|
||||
assert(cname !is null);
|
||||
assert(cname.UTF8String());
|
||||
|
||||
// Properties
|
||||
obj = MyClass.alloc().init();
|
||||
assert(obj !is null);
|
||||
assert(validateMethod(obj, "isFourtyTwo")); // Case: isXYZ
|
||||
assert(validateMethod(obj, "setFourtyTwo:")); // Case: isXYZ
|
||||
assert(validateMethod(obj, "myFunction:b:c:")); // Case: Auto-gen function selector.
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue