diff --git a/changelog/deprecated_objc_interfaces.dd b/changelog/deprecated_objc_interfaces.dd new file mode 100644 index 000000000000..82bed674b9c0 --- /dev/null +++ b/changelog/deprecated_objc_interfaces.dd @@ -0,0 +1,18 @@ +Deprecate Objective-C interfaces + +Prior to this release, it was necessary to represent an Objective-C class +as a D interface. Now support for Objective-C classes has been implemented and +the `class` keyword should be used instead. + +The reason for this deprecation is to allow `extern (Objective-C)` interfaces +to be repurposed for representing Objective-C protocols in the future. + +Deprecated: +--- +extern (Objective-C) interface NSObject {} // deprecated +--- + +Replace with: +--- +extern (Objective-C) class NSObject {} +--- diff --git a/changelog/objc_class.dd b/changelog/objc_class.dd new file mode 100644 index 000000000000..8bbd70430bdf --- /dev/null +++ b/changelog/objc_class.dd @@ -0,0 +1,49 @@ +Add support for Objective-C classes + +Prior to this release D interfaces were used to represent Objective-C classes. +Now proper support for Objective-C classes has been added and D classes can +be used instead to represent Objective-C classes. It's preferred to use D +classes to represent Objective-C classes. + +This release also adds support for implementing Objective-C subclasses and +methods in D. + +To match the behavior in Objective-C some additional changes have been made: + +$(UL + $(LI `static` and `final` methods are virtual. Although `final` methods are + virtual it's not possible to override a `final` method in a subclass) + $(LI `static` methods are overridable in subclasses) +) + +Example: + +--- +extern (Objective-C) +class NSObject +{ + static NSObject alloc() @selector("alloc"); + NSObject init() @selector("init"); + void release() @selector("release"); +} + +extern (Objective-C) +class Foo : NSObject +{ + override static Foo alloc() @selector("alloc"); + override Foo init() @selector("init"); + + int bar(int a) @selector("bar:") + { + return a; + } +} + +void main() +{ + auto foo = Foo.alloc.init; + scope (exit) foo.release(); + + assert(foo.bar(3) == 3); +} +--- diff --git a/docs/objective-c_abi.md b/docs/objective-c_abi.md index 87fdb790e4ae..26aa09e53c8b 100644 --- a/docs/objective-c_abi.md +++ b/docs/objective-c_abi.md @@ -179,6 +179,10 @@ different `this` pointer. The `this` pointer should be a ### Linkages +#### External Linkage + +Externally visible function. + #### Internal Linkage Rename collisions when linking (static functions). @@ -198,6 +202,81 @@ section data. |---------------------------------------------|------------------------------|-----------| | [`__objc_methname`](#segments-and-sections) | [Private](#private-linkage) | 1 | +### `L_OBJC_METH_VAR_TYPE_` + +For each method that is defined, a symbol is generated in the resulting binary. +The symbol has the name `L_OBJC_METH_VAR_TYPE_.`, where `` is an +incrementing number. The section data contains the return type and the parameter +types encoded as a null terminated C string as according to the [Type Encoding](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100) +documentation provided by Apple. + +| Section | Linkage | Alignment | +|---------------------------------------------|------------------------------|-----------| +| [`__objc_methtype`](#segments-and-sections) | [Private](#private-linkage) | 1 | + +### `l_OBJC_$_INSTANCE_METHODS_`/`l_OBJC_$_CLASS_METHODS_` + +For each class that is defined and contains at least a one class (static) +method, a symbol is generated in the resulting binary. The symbol has the name +`l_OBJC_$_CLASS_METHODS_`, where `` is the name of the +class. For each class that is defined and contains at least one instance method, +a symbol is generated with the name `l_OBJC_$_INSTANCE_METHODS_`, +where `` is the name of the class. The section data that is stored +corresponds to the following structs: + +```d +struct __method_list_t +{ + int entsize; + int count; + _objc_method first; +} +``` + +```d +struct _objc_method +{ + char* name; + char* types; + void* imp; +} +``` + +#### `__method_list_t` +##### `entsize` + +The size of `_objc_method` in bytes, always `24`. + +##### `count` + +The number of methods in the list. + +##### `first` + +The first method. + +#### `_objc_method` +##### `name` + +The name of the method. This is stored as a reference to the +`L_OBJC_METH_VAR_NAME_.` symbol, where `` is an incrementing +number. + +##### `types` + +The type encoding of the method. This is stored as a reference to the +`L_OBJC_METH_VAR_TYPE_.` symbol, where `` is an incrementing +number. + +##### `imp` + +The actual method implementation. The address to the function that is the method +implementation. + +| Section | Linkage | Alignment | +|------------------------------------------|------------------------------|-----------| +| [`__objc_const`](#segments-and-sections) | [Private](#private-linkage) | 8 | + ### `L_OBJC_SELECTOR_REFERENCES_` For each `L_OBJC_METH_VAR_NAME_` symbol that is generated, a corresponding @@ -252,18 +331,187 @@ by default when no switches are specified. |----------------------------------------------|------------------------------|-----------| | [`__objc_imageinfo`](#segments-and-sections) | [Private](#private-linkage) | 8 | +### `L_OBJC_CLASS_NAME_` + +For each class defined, a symbol is generated in the resulting binary. The +symbol has the name `L_OBJC_CLASS_NAME_.`, where `` is an +incrementing number. The the name of the class is stored as a null terminated C +string as the section data. + +| Section | Linkage | Alignment | +|----------------------------------------------|------------------------------|-----------| +| [`__objc_classname`](#segments-and-sections) | [Private](#private-linkage) | 8 | + +### `l_OBJC_CLASS_RO_$_`/`l_OBJC_METACLASS_RO_$_` + +For each class defined, two symbols is generated in the resulting binary. One +symbols with the name `l_OBJC_CLASS_RO_$_` and one with the name +`l_OBJC_METACLASS_RO_$_`, where `` is the name of the +class. The first symbol is for the class and the second symbol is for the +metaclass. The section data that is stored corresponds to the following struct: + +```d +struct _class_ro_t +{ + int flags; + int instanceStart = 40; + int instanceSize = 40; + byte* reserved; + byte* ivarLayout; + char* name; + __method_list_t* baseMethods; + _objc_protocol_list* baseProtocols; + _ivar_list_t* ivars; + byte* weakIvarLayout; + _prop_list_t* baseProperties; +} +``` + +#### `flags` + +A bit field indicating if the class is a regular class, metaclass or root class. +Possible flags: + +* regular class: `0` +* metaclass: `0x00001` +* root class: `0x00002` + +#### `instanceStart` + +The start of the instance, in bytes. For a metaclass this is always `40`. For a +class without instance variables it's the size of the class declaration. +Otherwise it's the offset of the first instance variable. + +#### `instanceSize` + +The size of an instance of this class, in bytes. For a metaclass this is always +`40`. + +#### `reserved` + +Currently not used. Reserved for future use. + +#### `ivarLayout` + +Unknown. Seems to be `null`. + +#### `name` + +The name of the class. This is stored as a reference to the +`L_OBJC_CLASS_NAME_` symbol, where `` is the name of the +class. + +#### `baseMethods` + +A list of the class (static) methods this class contains. This is stored as a +reference to the `l_OBJC_$_CLASS_METHODS_` symbol, where +`` is the name of the class. If the class doesn't contain any class +methods, `null` is stored instead. + +#### `baseProtocols` + +A list of the protocols this class implements. + +#### `ivars` + +A list of the instance variables this class contains. This is stored as a +a reference to the `l_OBJC_$_INSTANCE_VARIABLES_`, where +`` is the name of the class. For a metaclass or if the class doesn't +have any instance variables, this will be `null`. + +#### `weakIvarLayout` + +Unknown. Seems to be `null`. + +#### `baseProperties` + +A list of the properties this class contains. + +| Section | Linkage | Alignment | +|------------------------------------------|------------------------------|-----------| +| [`__objc_const`](#segments-and-sections) | [Private](#private-linkage) | 8 | + +### `_OBJC_CLASS_$_`/`_OBJC_METACLASS_$_` + +For each class defined, two symbols is generated in the resulting binary. One +symbols with the name `_OBJC_CLASS_$_` and one with the name +`_OBJC_METACLASS_$_`, where `` is the name of the +class. The first symbol is for the class and the second symbol is for the +metaclass. The section data that is stored corresponds to the following struct: + +```d +struct _class_t +{ + _class_t* isa; + _class_t* superclass; + _objc_cache* cache; + void* vtable; + _class_ro_t* data; +} +``` + +#### `isa` + +Pointer to the metaclass. This is stored as a reference to the +`_OBJC_METACLASS_$_` symbol, where `` is the name of the +class. + +#### `superclass` + +Pointer to the base class. This is stored as a reference to the +`_OBJC_CLASS_$_` symbol, where `` is the name of the +base class. Or a reference to the `_OBJC_METACLASS_$_`, if this is a +metaclass. + +#### `cache` + +Unknown. Usually a pointer to an empty cache object. This is stored as a +reference to the externally defined `__objc_empty_cache` symbol. + +#### `vtable` + +Pointer to the vtable. For some selectors, as an optimization, a vtable can be +used when calling the method, instead of the regular implementation. This +applies to around 20 selectors that are very common to call but unlikely for +these methods to be overridden. + +#### `data` + +A pointer to the class implementation. This is stored as a reference to the +`l_OBJC_CLASS_RO_$_` symbol, where `` is the name of the +class. Or a reference to the `l_OBJC_METACLASS_RO_$_` symbol, if +this class is metaclass. + +| Section | Linkage | Alignment | +|-----------------------------------------|--------------------------------|-----------| +| [`__objc_data`](#segments-and-sections) | [External](#external-linkage) | 8 | + +### `L_OBJC_LABEL_CLASS_$` + +Contains a list of `_class_t` pointers for each class that is defined. This is +stored as a reference to the `_OBJC_CLASS_$_` symbol, where +`` is the name of the class. + +| Section | Linkage | Alignment | +|----------------------------------------------|------------------------------|-----------| +| [`__objc_classlist`](#segments-and-sections) | [Private](#private-linkage) | 8 | + ## Segments and Sections The following segments and sections are used to store data in the binary. This table also includes properties of these sections: -| Section | Segment | Type | Attribute | Alignment | -|--------------------|-----------|---------------------|-----------------|------------| -| `__objc_imageinfo` | `__DATA` | `regular` | `no_dead_strip` | 0 | -| `__objc_methname` | `__TEXT` | `cstring_literals` | | 0 | -| `__objc_classlist` | `__DATA` | `regular` | `no_dead_strip` | 8 | -| `__objc_selrefs` | `__DATA` | `literal_pointers` | `no_dead_strip` | 8 | -| `__objc_classrefs` | `__DATA` | `regular` | `no_dead_strip` | 8 | +| Section | Segment | Type | Attribute | Alignment | +|--------------------|----------|--------------------|-----------------|-----------| +| `__objc_imageinfo` | `__DATA` | `regular` | `no_dead_strip` | 0 | +| `__objc_methname` | `__TEXT` | `cstring_literals` | | 0 | +| `__objc_classlist` | `__DATA` | `regular` | `no_dead_strip` | 8 | +| `__objc_selrefs` | `__DATA` | `literal_pointers` | `no_dead_strip` | 8 | +| `__objc_classrefs` | `__DATA` | `regular` | `no_dead_strip` | 8 | +| `__objc_classname` | `__TEXT` | `cstring_literals` | | 0 | +| `__objc_const` | `__DATA` | `regular` | | 8 | +| `__objc_data` | `__DATA` | `regular` | | 8 | +| `__objc_methtype` | `__TEXT` | `cstring_literals` | | 0 | For more information about the different section types and attributes, see the documentation for [Assembler Directives](https://developer.apple.com/library/content/documentation/DeveloperTools/Reference/Assembler/040-Assembler_Directives/asm_directives.html) from Apple. diff --git a/src/dmd/aggregate.h b/src/dmd/aggregate.h index b7bc84bee027..3a876d0cbc84 100644 --- a/src/dmd/aggregate.h +++ b/src/dmd/aggregate.h @@ -304,6 +304,7 @@ class ClassDeclaration : public AggregateDeclaration const char *kind() const; void addLocalClass(ClassDeclarations *); + void addObjcSymbols(ClassDeclarations *classes, ClassDeclarations *categories); // Back end Dsymbol *vtblsym; diff --git a/src/dmd/attrib.d b/src/dmd/attrib.d index a8bb5c79d298..e9557849843b 100644 --- a/src/dmd/attrib.d +++ b/src/dmd/attrib.d @@ -28,6 +28,7 @@ import dmd.hdrgen; import dmd.id; import dmd.identifier; import dmd.mtype; +import dmd.objc; import dmd.root.outbuffer; import dmd.target; import dmd.tokens; @@ -183,6 +184,11 @@ extern (C++) abstract class AttribDeclaration : Dsymbol include(null).foreachDsymbol( s => s.addLocalClass(aclasses) ); } + override final void addObjcSymbols(ClassDeclarations* classes, ClassDeclarations* categories) + { + objc.addSymbols(this, classes, categories); + } + override final inout(AttribDeclaration) isAttribDeclaration() inout { return this; diff --git a/src/dmd/dclass.d b/src/dmd/dclass.d index df830d4e4e8b..457c0c46bcb7 100644 --- a/src/dmd/dclass.d +++ b/src/dmd/dclass.d @@ -225,6 +225,8 @@ extern (C++) class ClassDeclaration : AggregateDeclaration final extern (D) this(const ref Loc loc, Identifier id, BaseClasses* baseclasses, Dsymbols* members, bool inObject) { + objc = ObjcClassDeclaration(this); + if (!id) { id = Identifier.generateId("__anonclass"); @@ -955,7 +957,13 @@ extern (C++) class ClassDeclaration : AggregateDeclaration */ override final void addLocalClass(ClassDeclarations* aclasses) { - aclasses.push(this); + if (classKind != ClassKind.objc) + aclasses.push(this); + } + + override final void addObjcSymbols(ClassDeclarations* classes, ClassDeclarations* categories) + { + .objc.addSymbols(this, classes, categories); } // Back end diff --git a/src/dmd/declaration.d b/src/dmd/declaration.d index 85be80b27d09..c5567bdfb926 100644 --- a/src/dmd/declaration.d +++ b/src/dmd/declaration.d @@ -1097,11 +1097,18 @@ extern (C++) class VarDeclaration : Declaration VarDeclarations* maybes; // STC.maybescope variables that are assigned to this STC.maybescope variable + private bool _isAnonymous; + final extern (D) this(const ref Loc loc, Type type, Identifier id, Initializer _init, StorageClass storage_class = STC.undefined_) { - super(id); + if (id is Identifier.anonymous) + { + id = Identifier.generateId("__anonvar"); + _isAnonymous = true; + } //printf("VarDeclaration('%s')\n", id.toChars()); assert(id); + super(id); debug { if (!type && !_init) @@ -1244,6 +1251,11 @@ extern (C++) class VarDeclaration : Declaration return isField(); } + override final bool isAnonymous() + { + return _isAnonymous; + } + override final bool isExport() const { return protection.kind == Prot.Kind.export_; diff --git a/src/dmd/declaration.h b/src/dmd/declaration.h index e11c3ed3a987..7fb24ed2e55f 100644 --- a/src/dmd/declaration.h +++ b/src/dmd/declaration.h @@ -451,6 +451,12 @@ void builtin_init(); class FuncDeclaration : public Declaration { public: + struct HiddenParameters + { + VarDeclaration* this_; + VarDeclaration* selector; + }; + Types *fthrows; // Array of Type's of exceptions (not used) Statements *frequires; // in contracts Ensures *fensures; // out contracts @@ -545,7 +551,7 @@ class FuncDeclaration : public Declaration bool functionSemantic(); bool functionSemantic3(); // called from semantic3 - VarDeclaration *declareThis(Scope *sc, AggregateDeclaration *ad); + HiddenParameters declareThis(Scope *sc, AggregateDeclaration *ad); bool equals(RootObject *o); int overrides(FuncDeclaration *fd); diff --git a/src/dmd/dsymbol.d b/src/dmd/dsymbol.d index 0e810cc739e5..19a009db130b 100644 --- a/src/dmd/dsymbol.d +++ b/src/dmd/dsymbol.d @@ -1027,6 +1027,10 @@ extern (C++) class Dsymbol : RootObject { } + void addObjcSymbols(ClassDeclarations* classes, ClassDeclarations* categories) + { + } + void checkCtorConstInit() { } diff --git a/src/dmd/dsymbol.h b/src/dmd/dsymbol.h index 5176b0f70f61..42157a27dd2b 100644 --- a/src/dmd/dsymbol.h +++ b/src/dmd/dsymbol.h @@ -217,6 +217,7 @@ class Dsymbol : public RootObject virtual bool hasPointers(); virtual bool hasStaticCtorOrDtor(); virtual void addLocalClass(ClassDeclarations *) { } + virtual void addObjcSymbols(ClassDeclarations *classes, ClassDeclarations *categories) { } virtual void checkCtorConstInit() { } virtual void addComment(const utf8_t *comment); diff --git a/src/dmd/dsymbolsem.d b/src/dmd/dsymbolsem.d index 24b6800245e5..33c5171c94d3 100644 --- a/src/dmd/dsymbolsem.d +++ b/src/dmd/dsymbolsem.d @@ -3204,6 +3204,8 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor if (ClassDeclaration cd = parent.isClassDeclaration()) { + parent = cd = objc.getParent(funcdecl, cd); + if (funcdecl.isCtorDeclaration()) { goto Ldone; @@ -3558,6 +3560,10 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor } L2: + objc.setSelector(funcdecl, sc); + objc.checkLinkage(funcdecl); + objc.addToClassMethodList(funcdecl, cd); + /* Go through all the interface bases. * Disallow overriding any final functions in the interface(s). */ @@ -3595,6 +3601,10 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor else if (funcdecl.isOverride() && !parent.isTemplateInstance()) funcdecl.error("`override` only applies to class member functions"); + if (auto ti = parent.isTemplateInstance) + objc.setSelector(funcdecl, sc); + + objc.validateSelector(funcdecl); // Reflect this.type to f because it could be changed by findVtblIndex f = funcdecl.type.toTypeFunction(); @@ -4410,7 +4420,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor override void visit(ClassDeclaration cldec) { - //printf("ClassDeclaration.dsymbolSemantic(%s), type = %p, sizeok = %d, this = %p\n", toChars(), type, sizeok, this); + //printf("ClassDeclaration.dsymbolSemantic(%s), type = %p, sizeok = %d, this = %p\n", cldec.toChars(), cldec.type, cldec.sizeok, this); //printf("\tparent = %p, '%s'\n", sc.parent, sc.parent ? sc.parent.toChars() : ""); //printf("sc.stc = %x\n", sc.stc); @@ -4682,6 +4692,9 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor } cldec.baseok = Baseok.done; + if (cldec.classKind == ClassKind.objc || (cldec.baseClass && cldec.baseClass.classKind == ClassKind.objc)) + cldec.classKind = ClassKind.objc; // Objective-C classes do not inherit from Object + // If no base class, and this is not an Object, use Object as base class if (!cldec.baseClass && cldec.ident != Id.Object && cldec.object && cldec.classKind == ClassKind.d) { @@ -4792,6 +4805,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor if (cldec.baseok == Baseok.done) { cldec.baseok = Baseok.semanticdone; + objc.setMetaclass(cldec, sc); // initialize vtbl if (cldec.baseClass) @@ -5028,6 +5042,14 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor override void visit(InterfaceDeclaration idec) { + /// Returns: `true` is this is an anonymous Objective-C metaclass + static bool isAnonymousMetaclass(InterfaceDeclaration idec) + { + return idec.classKind == ClassKind.objc && + idec.objc.isMeta && + idec.isAnonymous; + } + //printf("InterfaceDeclaration.dsymbolSemantic(%s), type = %p\n", toChars(), type); if (idec.semanticRun >= PASS.semanticdone) return; @@ -5048,7 +5070,8 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor assert(sc.parent && sc.func); idec.parent = sc.parent; } - assert(idec.parent && !idec.isAnonymous()); + // Objective-C metaclasses are anonymous + assert(idec.parent && !idec.isAnonymous || isAnonymousMetaclass(idec)); if (idec.errors) idec.type = Type.terror; @@ -5144,7 +5167,10 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor idec.classKind = ClassKind.cpp; if (sc.linkage == LINK.objc) + { objc.setObjc(idec); + objc.deprecate(idec); + } // Check for errors, handle forward references for (size_t i = 0; i < idec.baseclasses.dim;) @@ -5254,7 +5280,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor if (idec.baseok == Baseok.done) { idec.baseok = Baseok.semanticdone; - objc.setMetaclass(idec); + objc.setMetaclass(idec, sc); // initialize vtbl if (idec.vtblOffset()) diff --git a/src/dmd/dtemplate.d b/src/dmd/dtemplate.d index 6e10272be8b4..bcd6043d7d95 100644 --- a/src/dmd/dtemplate.d +++ b/src/dmd/dtemplate.d @@ -775,7 +775,9 @@ extern (C++) final class TemplateDeclaration : ScopeDsymbol } if (isstatic) fd.storage_class |= STC.static_; - fd.vthis = fd.declareThis(scx, fd.isThis()); + auto hiddenParams = fd.declareThis(scx, fd.isThis()); + fd.vthis = hiddenParams.vthis; + fd.selectorParameter = hiddenParams.selectorParameter; } Expression e = constraint.syntaxCopy(); diff --git a/src/dmd/frontend.d b/src/dmd/frontend.d index 1c0f97fff02a..8d0432d00496 100644 --- a/src/dmd/frontend.d +++ b/src/dmd/frontend.d @@ -56,6 +56,7 @@ void initDMD() import dmd.filecache : FileCache; import dmd.globals : global; import dmd.id : Id; + import dmd.identifier : Identifier; import dmd.mars : setTarget, addDefaultVersionIdentifiers; import dmd.mtype : Type; import dmd.objc : Objc; diff --git a/src/dmd/func.d b/src/dmd/func.d index 7f2d29881dfa..c05d3e9202e6 100644 --- a/src/dmd/func.d +++ b/src/dmd/func.d @@ -198,6 +198,22 @@ extern (C++) struct Ensure */ extern (C++) class FuncDeclaration : Declaration { + /// All hidden parameters bundled. + static struct HiddenParameters + { + /** + * The `this` parameter for methods or nested functions. + * + * For methods, it would be the class object or struct value the + * method is called on. For nested functions it would be the enclosing + * function's stack frame. + */ + VarDeclaration vthis; + + /// The selector parameter for Objective-C methods. + VarDeclaration selectorParameter; + } + Types* fthrows; /// Array of Type's of exceptions (not used) Statements* frequires; /// in contracts Ensures* fensures; /// out contracts @@ -220,6 +236,7 @@ extern (C++) class FuncDeclaration : Declaration VarDeclaration vthis; /// 'this' parameter (member and nested) VarDeclaration v_arguments; /// '_arguments' parameter ObjcSelector* selector; /// Objective-C method selector (member function only) + VarDeclaration selectorParameter; /// Objective-C implicit selector parameter VarDeclaration v_argptr; /// '_argptr' variable VarDeclarations* parameters; /// Array of VarDeclaration's for parameters @@ -438,7 +455,13 @@ extern (C++) class FuncDeclaration : Declaration } // called from semantic3 - final VarDeclaration declareThis(Scope* sc, AggregateDeclaration ad) + /** + * Creates and returns the hidden parameters for this function declaration. + * + * Hidden parameters include the `this` parameter of a class, struct or + * nested function and the selector parameter for Objective-C methods. + */ + final HiddenParameters declareThis(Scope* sc, AggregateDeclaration ad) { if (ad) { @@ -471,7 +494,7 @@ extern (C++) class FuncDeclaration : Declaration if (!sc.insert(v)) assert(0); v.parent = this; - return v; + return HiddenParameters(v, objc.createSelectorParameter(this, sc)); } if (isNested()) { @@ -496,9 +519,9 @@ extern (C++) class FuncDeclaration : Declaration if (!sc.insert(v)) assert(0); v.parent = this; - return v; + return HiddenParameters(v); } - return null; + return HiddenParameters.init; } override final bool equals(RootObject o) @@ -1597,13 +1620,20 @@ extern (C++) class FuncDeclaration : Declaration return toAliasFunc().isVirtual(); auto p = toParent(); + + if (!isMember || !p.isClassDeclaration) + return false; + + if (p.isClassDeclaration.classKind == ClassKind.objc) + return objc.isVirtual(this); + version (none) { printf("FuncDeclaration::isVirtual(%s)\n", toChars()); printf("isMember:%p isStatic:%d private:%d ctor:%d !Dlinkage:%d\n", isMember(), isStatic(), protection == Prot.Kind.private_, isCtorDeclaration(), linkage != LINK.d); printf("result is %d\n", isMember() && !(isStatic() || protection == Prot.Kind.private_ || protection == Prot.Kind.package_) && p.isClassDeclaration() && !(p.isInterfaceDeclaration() && isFinalFunc())); } - return isMember() && !(isStatic() || protection.kind == Prot.Kind.private_ || protection.kind == Prot.Kind.package_) && p.isClassDeclaration() && !(p.isInterfaceDeclaration() && isFinalFunc()); + return !(isStatic() || protection.kind == Prot.Kind.private_ || protection.kind == Prot.Kind.package_) && !(p.isInterfaceDeclaration() && isFinalFunc()); } final bool isFinalFunc() const diff --git a/src/dmd/glue.d b/src/dmd/glue.d index c528b81b01e8..43903aa3240e 100644 --- a/src/dmd/glue.d +++ b/src/dmd/glue.d @@ -497,7 +497,7 @@ void genObjFile(Module m, bool multiobj) if (m.doppelganger) { - objc.generateModuleInfo(); + objc.generateModuleInfo(m); objmod.termfile(); return; } @@ -960,7 +960,8 @@ void FuncDeclaration_toObjFile(FuncDeclaration fd, bool multiobj) size_t pi = (fd.v_arguments !is null); if (fd.parameters) pi += fd.parameters.dim; - + if (fd.selector) + pi++; // Extra argument for Objective-C selector // Create a temporary buffer, params[], to hold function parameters Symbol*[10] paramsbuf = void; Symbol **params = paramsbuf.ptr; // allocate on stack if possible @@ -1010,6 +1011,7 @@ void FuncDeclaration_toObjFile(FuncDeclaration fd, bool multiobj) pi++; } + pi = objc.addSelectorParameterSymbol(fd, params, pi); if (sthis) { diff --git a/src/dmd/id.d b/src/dmd/id.d index a20d68593bb7..de7292b26ebb 100644 --- a/src/dmd/id.d +++ b/src/dmd/id.d @@ -148,8 +148,6 @@ immutable Msgtable[] msgtable = { "xopCmp", "__xopCmp" }, { "xtoHash", "__xtoHash" }, - { "Class" }, - { "LINE", "__LINE__" }, { "FILE", "__FILE__" }, { "MODULE", "__MODULE__" }, diff --git a/src/dmd/identifier.d b/src/dmd/identifier.d index 28f55bee7482..2b6b313ac02e 100644 --- a/src/dmd/identifier.d +++ b/src/dmd/identifier.d @@ -66,6 +66,17 @@ public: this(name[0 .. strlen(name)], TOK.identifier); } + /// Sentinel for an anonymous identifier. + static Identifier anonymous() nothrow + { + __gshared Identifier anonymous; + + if (anonymous) + return anonymous; + + return anonymous = new Identifier("__anonymous", TOK.identifier); + } + static Identifier create(const(char)* name) nothrow { return new Identifier(name); diff --git a/src/dmd/identifier.h b/src/dmd/identifier.h index ef0c20a15100..b07ac4882c29 100644 --- a/src/dmd/identifier.h +++ b/src/dmd/identifier.h @@ -16,10 +16,13 @@ class Identifier : public RootObject { private: + static Identifier* anonymous_; int value; DArray string; public: + static void initialize(); + static Identifier* anonymous(); static Identifier* create(const char *string); bool equals(RootObject *o); int compare(RootObject *o); diff --git a/src/dmd/objc.d b/src/dmd/objc.d index 9035715ae681..3fa29b6fbccb 100644 --- a/src/dmd/objc.d +++ b/src/dmd/objc.d @@ -14,6 +14,7 @@ module dmd.objc; import dmd.aggregate; import dmd.arraytypes; +import dmd.attrib; import dmd.cond; import dmd.dclass; import dmd.declaration; @@ -21,6 +22,9 @@ import dmd.dmangle; import dmd.dmodule; import dmd.dscope; import dmd.dstruct; +import dmd.dsymbol; +import dmd.dsymbolsem; +import dmd.errors; import dmd.expression; import dmd.expressionsem; import dmd.func; @@ -139,13 +143,38 @@ Objc objc() * Contains all data for a class declaration that is needed for the Objective-C * integration. */ -struct ObjcClassDeclaration +extern (C++) struct ObjcClassDeclaration { /// `true` if this class is a metaclass. bool isMeta = false; + /// `true` if this class is externally defined. + bool isExtern = false; + + /// Name of this class. + Identifier identifier; + + /// The class declaration this belongs to. + ClassDeclaration classDeclaration; + /// The metaclass of this class. ClassDeclaration metaclass; + + /// List of non-inherited methods. + Dsymbols* methodList; + + extern (D) this(ClassDeclaration classDeclaration) + { + this.classDeclaration = classDeclaration; + methodList = new Dsymbols; + } + + bool isRootClass() const + { + return classDeclaration.classKind == ClassKind.objc && + !metaclass && + !classDeclaration.baseClass; + } } // Should be an interface @@ -162,10 +191,67 @@ extern(C++) abstract class Objc abstract void setObjc(ClassDeclaration cd); abstract void setObjc(InterfaceDeclaration); + /** + * Deprecate the given Objective-C interface. + * + * Representing an Objective-C class as a D interface has been deprecated. + * Classes have now been properly implemented and the `class` keyword should + * be used instead. + * + * In the future, `extern(Objective-C)` interfaces will be used to represent + * Objective-C protocols. + * + * Params: + * interfaceDeclaration = the interface declaration to deprecate + */ + abstract void deprecate(InterfaceDeclaration interfaceDeclaration) const; + abstract void setSelector(FuncDeclaration, Scope* sc); abstract void validateSelector(FuncDeclaration fd); abstract void checkLinkage(FuncDeclaration fd); + /** + * Returns `true` if the given function declaration is virtual. + * + * Function declarations with Objective-C linkage and which are static or + * final are considered virtual. + * + * Params: + * fd = the function declaration to check if it's virtual + * + * Returns: `true` if the given function declaration is virtual + */ + abstract bool isVirtual(const FuncDeclaration fd) const; + + /** + * Gets the parent of the given function declaration. + * + * Handles Objective-C static member functions, which are virtual functions + * of the metaclass, by returning the parent class declaration to the + * metaclass. + * + * Params: + * fd = the function declaration to get the parent of + * cd = the current parent, i.e. the class declaration the given function + * declaration belongs to + * + * Returns: the parent + */ + abstract ClassDeclaration getParent(FuncDeclaration fd, + ClassDeclaration cd) const; + + /** + * Adds the given function to the list of Objective-C methods. + * + * This list will later be used output the necessary Objective-C module info. + * + * Params: + * fd = the function declaration to be added to the list + * cd = the class declaration the function belongs to + */ + abstract void addToClassMethodList(FuncDeclaration fd, + ClassDeclaration cd) const; + /** * Returns the `this` pointer of the given function declaration. * @@ -180,6 +266,22 @@ extern(C++) abstract class Objc */ abstract inout(AggregateDeclaration) isThis(inout FuncDeclaration funcDeclaration) const; + /** + * Creates the selector parameter for the given function declaration. + * + * Objective-C methods has an extra hidden parameter that comes after the + * `this` parameter. The selector parameter is of the Objective-C type `SEL` + * and contains the selector which this method was called with. + * + * Params: + * fd = the function declaration to create the parameter for + * sc = the scope from the semantic phase + * + * Returns: the newly created selector parameter or `null` for + * non-Objective-C functions + */ + abstract VarDeclaration createSelectorParameter(FuncDeclaration fd, Scope* sc) const; + /** * Creates and sets the metaclass on the given class/interface declaration. * @@ -188,11 +290,10 @@ extern(C++) abstract class Objc * Params: * classDeclaration = the class/interface declaration to set the metaclass on */ - abstract void setMetaclass(InterfaceDeclaration interfaceDeclaration) const; + abstract void setMetaclass(InterfaceDeclaration interfaceDeclaration, Scope* sc) const; /// ditto - abstract void setMetaclass(ClassDeclaration classDeclaration) const; - + abstract void setMetaclass(ClassDeclaration classDeclaration, Scope* sc) const; /** * Returns Objective-C runtime metaclass of the given class declaration. @@ -210,6 +311,14 @@ extern(C++) abstract class Objc */ abstract ClassDeclaration getRuntimeMetaclass(ClassDeclaration classDeclaration) const; + /// + abstract void addSymbols(AttribDeclaration attribDeclaration, + ClassDeclarations* classes, ClassDeclarations* categories) const; + + /// + abstract void addSymbols(ClassDeclaration classDeclaration, + ClassDeclarations* classes, ClassDeclarations* categories) const; + /** * Issues a compile time error if the `.offsetof`/`.tupleof` property is * used on a field of an Objective-C class. @@ -261,6 +370,11 @@ extern(C++) private final class Unsupported : Objc id.error("Objective-C interfaces not supported"); } + override void deprecate(InterfaceDeclaration) const + { + // noop + } + override void setSelector(FuncDeclaration, Scope*) { // noop @@ -276,17 +390,37 @@ extern(C++) private final class Unsupported : Objc // noop } + override bool isVirtual(const FuncDeclaration) const + { + assert(0, "Should never be called when Objective-C is not supported"); + } + + override ClassDeclaration getParent(FuncDeclaration, ClassDeclaration cd) const + { + return cd; + } + + override void addToClassMethodList(FuncDeclaration, ClassDeclaration) const + { + // noop + } + override inout(AggregateDeclaration) isThis(inout FuncDeclaration funcDeclaration) const { return null; } - override void setMetaclass(InterfaceDeclaration) const + override VarDeclaration createSelectorParameter(FuncDeclaration, Scope*) const + { + return null; + } + + override void setMetaclass(InterfaceDeclaration, Scope*) const { // noop } - override void setMetaclass(ClassDeclaration) const + override void setMetaclass(ClassDeclaration, Scope*) const { // noop } @@ -296,6 +430,18 @@ extern(C++) private final class Unsupported : Objc assert(0, "Should never be called when Objective-C is not supported"); } + override void addSymbols(AttribDeclaration attribDeclaration, + ClassDeclarations* classes, ClassDeclarations* categories) const + { + // noop + } + + override void addSymbols(ClassDeclaration classDeclaration, + ClassDeclarations* classes, ClassDeclarations* categories) const + { + // noop + } + override void checkOffsetof(Expression expression, AggregateDeclaration aggregateDeclaration) const { // noop @@ -319,12 +465,51 @@ extern(C++) private final class Supported : Objc override void setObjc(ClassDeclaration cd) { + static bool isExtern(Dsymbols* members) + { + return members.foreachDsymbol((member) { + if (auto attrib = member.isAttribDeclaration) + { + if (!isExtern(attrib.decl)) + return 1; // return !=0 to stop iteration + } + + else if (auto func = member.isFuncDeclaration) + { + if (func.fbody) + return 1; // return !=0 to stop iteration + } + + return 0; + }) == 0; + } + cd.classKind = ClassKind.objc; + cd.objc.isExtern = isExtern(cd.members); } override void setObjc(InterfaceDeclaration id) { id.classKind = ClassKind.objc; + id.objc.isExtern = true; + } + + override void deprecate(InterfaceDeclaration id) const + in + { + assert(id.classKind == ClassKind.objc); + } + body + { + // don't report deprecations for the metaclass to avoid duplicated + // messages. + if (id.objc.isMeta) + return; + + id.deprecation("Objective-C interfaces have been deprecated"); + deprecationSupplemental(id.loc, "Representing an Objective-C class " ~ + "as a D interface has been deprecated. Please use the `class` "~ + "keyword instead"); } override void setSelector(FuncDeclaration fd, Scope* sc) @@ -382,6 +567,53 @@ extern(C++) private final class Supported : Objc fd.error("must have Objective-C linkage to attach a selector"); } + override bool isVirtual(const FuncDeclaration fd) const + in + { + assert(fd.selector); + assert(fd.isMember); + } + body + { + // * final member functions are kept virtual with Objective-C linkage + // because the Objective-C runtime always use dynamic dispatch. + // * static member functions are kept virtual too, as they represent + // methods of the metaclass. + with (fd.protection) + return !(kind == Prot.Kind.private_ || kind == Prot.Kind.package_); + } + + override ClassDeclaration getParent(FuncDeclaration fd, ClassDeclaration cd) const + out(metaclass) + { + assert(metaclass); + } + body + { + if (cd.classKind == ClassKind.objc && fd.isStatic && !cd.objc.isMeta) + return cd.objc.metaclass; + else + return cd; + } + + override void addToClassMethodList(FuncDeclaration fd, ClassDeclaration cd) const + in + { + assert(fd.parent.isClassDeclaration); + } + body + { + if (cd.classKind != ClassKind.objc) + return; + + if (!fd.selector) + return; + + assert(fd.isStatic ? cd.objc.isMeta : !cd.objc.isMeta); + + cd.objc.methodList.push(fd); + } + override inout(AggregateDeclaration) isThis(inout FuncDeclaration funcDeclaration) const { with(funcDeclaration) @@ -402,26 +634,44 @@ extern(C++) private final class Supported : Objc } } - override void setMetaclass(InterfaceDeclaration interfaceDeclaration) const + override VarDeclaration createSelectorParameter(FuncDeclaration fd, Scope* sc) const + in + { + assert(fd.selectorParameter is null); + } + body + { + if (!fd.selector) + return null; + + auto var = new VarDeclaration(fd.loc, Type.tvoidptr, Identifier.anonymous, null); + var.storage_class |= STC.parameter; + var.dsymbolSemantic(sc); + if (!sc.insert(var)) + assert(false); + var.parent = fd; + + return var; + } + + override void setMetaclass(InterfaceDeclaration interfaceDeclaration, Scope* sc) const { static auto newMetaclass(Loc loc, BaseClasses* metaBases) { - return new InterfaceDeclaration(loc, Id.Class, metaBases); + return new InterfaceDeclaration(loc, null, metaBases); } - .setMetaclass!newMetaclass(interfaceDeclaration); + .setMetaclass!newMetaclass(interfaceDeclaration, sc); } - override void setMetaclass(ClassDeclaration classDeclaration) const + override void setMetaclass(ClassDeclaration classDeclaration, Scope* sc) const { auto newMetaclass(Loc loc, BaseClasses* metaBases) { - auto members = new Dsymbols(); - members.push(classDeclaration); - return new ClassDeclaration(loc, Id.Class, metaBases, members, 0); + return new ClassDeclaration(loc, null, metaBases, new Dsymbols(), 0); } - .setMetaclass!newMetaclass(classDeclaration); + .setMetaclass!newMetaclass(classDeclaration, sc); } override ClassDeclaration getRuntimeMetaclass(ClassDeclaration classDeclaration) const @@ -437,6 +687,26 @@ extern(C++) private final class Supported : Objc return classDeclaration.objc.metaclass; } + override void addSymbols(AttribDeclaration attribDeclaration, + ClassDeclarations* classes, ClassDeclarations* categories) const + { + auto symbols = attribDeclaration.include(null); + + if (!symbols) + return; + + foreach (symbol; *symbols) + symbol.addObjcSymbols(classes, categories); + } + + override void addSymbols(ClassDeclaration classDeclaration, + ClassDeclarations* classes, ClassDeclarations* categories) const + { + with (classDeclaration) + if (classKind == ClassKind.objc && !objc.isExtern && !objc.isMeta) + classes.push(classDeclaration); + } + override void checkOffsetof(Expression expression, AggregateDeclaration aggregateDeclaration) const { if (aggregateDeclaration.classKind != ClassKind.objc) @@ -482,7 +752,7 @@ extern(C++) private final class Supported : Objc * return the same type as `T`. * classDeclaration = the class/interface declaration to set the metaclass on */ -private void setMetaclass(alias newMetaclass, T)(T classDeclaration) +private void setMetaclass(alias newMetaclass, T)(T classDeclaration, Scope* sc) if (is(T == ClassDeclaration) || is(T == InterfaceDeclaration)) { static if (is(T == ClassDeclaration)) @@ -495,6 +765,9 @@ if (is(T == ClassDeclaration) || is(T == InterfaceDeclaration)) if (classKind != ClassKind.objc || objc.isMeta || objc.metaclass) return; + if (!objc.identifier) + objc.identifier = classDeclaration.ident; + auto metaBases = new BaseClasses(); foreach (base ; baseclasses.opSlice) @@ -523,5 +796,15 @@ if (is(T == ClassDeclaration) || is(T == InterfaceDeclaration)) objc.metaclass.storage_class |= STC.static_; objc.metaclass.classKind = ClassKind.objc; objc.metaclass.objc.isMeta = true; + objc.metaclass.objc.isExtern = objc.isExtern; + objc.metaclass.objc.identifier = objc.identifier; + + if (baseClass) + objc.metaclass.baseClass = baseClass.objc.metaclass; + + members.push(objc.metaclass); + objc.metaclass.addMember(sc, classDeclaration); + + objc.metaclass.dsymbolSemantic(sc); } } diff --git a/src/dmd/objc.h b/src/dmd/objc.h index 068aa779e91f..333df14a13f9 100644 --- a/src/dmd/objc.h +++ b/src/dmd/objc.h @@ -10,10 +10,17 @@ #pragma once +#include + +#include "arraytypes.h" + class AggregateDeclaration; -class FuncDeclaration; +class AttribDeclaration; class ClassDeclaration; +class FuncDeclaration; +class Identifier; class InterfaceDeclaration; + struct Scope; struct ObjcSelector @@ -32,7 +39,14 @@ struct ObjcSelector struct ObjcClassDeclaration { bool isMeta; + bool isExtern; + + Identifier* identifier; + ClassDeclaration* classDeclaration; ClassDeclaration* metaclass; + Dsymbols* methodList; + + bool isRootClass() const; }; class Objc @@ -42,13 +56,21 @@ class Objc virtual void setObjc(ClassDeclaration* cd) = 0; virtual void setObjc(InterfaceDeclaration*) = 0; + virtual void deprecate(InterfaceDeclaration*) const = 0; virtual void setSelector(FuncDeclaration*, Scope* sc) = 0; virtual void validateSelector(FuncDeclaration* fd) = 0; virtual void checkLinkage(FuncDeclaration* fd) = 0; + virtual bool isVirtual(const FuncDeclaration*) const = 0; + virtual ClassDeclaration* getParent(FuncDeclaration*, ClassDeclaration*) const = 0; + virtual void addToClassMethodList(FuncDeclaration*, ClassDeclaration*) const = 0; virtual AggregateDeclaration* isThis(FuncDeclaration* fd) = 0; + virtual VarDeclaration* createSelectorParameter(FuncDeclaration*, Scope*) const = 0; - virtual void setMetaclass(InterfaceDeclaration* id) = 0; - virtual void setMetaclass(ClassDeclaration* id) = 0; + virtual void setMetaclass(InterfaceDeclaration* id, Scope*) const = 0; + virtual void setMetaclass(ClassDeclaration* id, Scope*) const = 0; virtual ClassDeclaration* getRuntimeMetaclass(ClassDeclaration* cd) = 0; + + virtual void addSymbols(AttribDeclaration*, ClassDeclarations*, ClassDeclarations*) const = 0; + virtual void addSymbols(ClassDeclaration*, ClassDeclarations*, ClassDeclarations*) const = 0; }; diff --git a/src/dmd/objc_glue.d b/src/dmd/objc_glue.d index 65b3c3b8aa23..0734e7bc74d9 100644 --- a/src/dmd/objc_glue.d +++ b/src/dmd/objc_glue.d @@ -17,11 +17,14 @@ import core.stdc.stdlib; import core.stdc.string; import dmd.aggregate; +import dmd.arraytypes; import dmd.dclass; import dmd.declaration; import dmd.dmodule; +import dmd.dsymbol; import dmd.expression; import dmd.func; +import dmd.glue; import dmd.globals; import dmd.identifier; import dmd.mtype; @@ -62,10 +65,31 @@ extern(C++) abstract class ObjcGlue abstract void setupMethodSelector(FuncDeclaration fd, elem** esel); abstract void setupMethodCall(elem** ec, elem* ehidden, elem* ethis, TypeFunction tf); abstract void setupEp(elem* esel, elem** ep, int leftToRight); - abstract void generateModuleInfo(); + abstract void generateModuleInfo(Module module_); /// Returns: the given expression converted to an `elem` structure abstract elem* toElem(ObjcClassReferenceExp e) const; + + /// Outputs the given Objective-C class to the object file. + abstract void toObjFile(ClassDeclaration classDeclaration) const; + + /** + * Adds the selector parameter to the given list of parameters. + * + * For Objective-C methods the selector parameter is added. For + * non-Objective-C methods `parameters` is unchanged. + * + * Params: + * functionDeclaration = the function declaration to add the selector + * parameter from + * parameters = the list of parameters to add the selector parameter to + * parameterCount = the number of parameters + * + * Returns: the new number of parameters + */ + abstract size_t addSelectorParameterSymbol( + FuncDeclaration functionDeclaration, + Symbol** parameters, size_t parameterCount) const; } private: @@ -87,7 +111,7 @@ extern(C++) final class Unsupported : ObjcGlue // noop } - override void generateModuleInfo() + override void generateModuleInfo(Module) { // noop } @@ -96,6 +120,17 @@ extern(C++) final class Unsupported : ObjcGlue { assert(0, "Should never be called when Objective-C is not supported"); } + + override void toObjFile(ClassDeclaration classDeclaration) const + { + assert(0, "Should never be called when Objective-C is not supported"); + } + + override size_t addSelectorParameterSymbol(FuncDeclaration, Symbol**, + size_t count) const + { + return count; + } } extern(C++) final class Supported : ObjcGlue @@ -134,16 +169,51 @@ extern(C++) final class Supported : ObjcGlue } } - override void generateModuleInfo() + override void generateModuleInfo(Module module_) { - if (Symbols.hasSymbols) - Symbols.getImageInfo(); + ClassDeclarations classes; + ClassDeclarations categories; + + module_.members.foreachDsymbol(m => m.addObjcSymbols(&classes, &categories)); + + if (classes.length || categories.length || Symbols.hasSymbols) + Symbols.getModuleInfo(classes, categories); } override elem* toElem(ObjcClassReferenceExp e) const { return el_var(Symbols.getClassReference(e.classDeclaration)); } + + override void toObjFile(ClassDeclaration classDeclaration) const + in + { + assert(classDeclaration.classKind == ClassKind.objc); + } + body + { + if (!classDeclaration.objc.isMeta) + ObjcClassDeclaration(classDeclaration, false).toObjFile(); + } + + override size_t addSelectorParameterSymbol(FuncDeclaration fd, + Symbol** params, size_t count) const + in + { + assert(fd); + } + body + { + if (!fd.selector) + return count; + + assert(fd.selectorParameter); + auto selectorSymbol = fd.selectorParameter.toSymbol(); + memmove(params + 1, params, count * params[0].sizeof); + params[0] = selectorSymbol; + + return count + 1; + } } struct Segments @@ -152,9 +222,16 @@ struct Segments { imageInfo, methodName, + methodType, + instanceMethod, + classMethod, moduleInfo, selectorRefs, + className, classRefs, + class_, + classRo, + metaclass } private @@ -164,9 +241,16 @@ struct Segments __gshared Segments[__traits(allMembers, Id).length] segmentData = [ Segments("__objc_imageinfo", "__DATA", S_REGULAR | S_ATTR_NO_DEAD_STRIP, 0), Segments("__objc_methname", "__TEXT", S_CSTRING_LITERALS, 0), + Segments("__objc_methtype", "__TEXT", S_CSTRING_LITERALS, 0), + Segments("__objc_const", "__DATA", S_REGULAR, 3), + Segments("__objc_const", "__DATA", S_REGULAR, 3), Segments("__objc_classlist", "__DATA", S_REGULAR | S_ATTR_NO_DEAD_STRIP, 3), Segments("__objc_selrefs", "__DATA", S_LITERAL_POINTERS | S_ATTR_NO_DEAD_STRIP, 3), - Segments("__objc_classrefs", "__DATA", S_REGULAR | S_ATTR_NO_DEAD_STRIP, 3) + Segments("__objc_classname", "__TEXT", S_CSTRING_LITERALS, 0), + Segments("__objc_classrefs", "__DATA", S_REGULAR | S_ATTR_NO_DEAD_STRIP, 3), + Segments("__objc_data", "__DATA", S_REGULAR, 3), + Segments("__objc_const", "__DATA", S_REGULAR, 3), + Segments("__objc_data", "__DATA", S_REGULAR, 3) ]; static assert(segmentData.length == __traits(allMembers, Id).length); @@ -229,6 +313,9 @@ static: Symbol* imageInfo = null; Symbol* moduleInfo = null; + Symbol* emptyCache = null; + Symbol* emptyVTable = null; + // Cache for `_OBJC_METACLASS_$_`/`_OBJC_CLASS_$_` symbols. StringTable* classNameTable = null; @@ -237,6 +324,7 @@ static: StringTable* methVarNameTable = null; StringTable* methVarRefTable = null; + StringTable* methVarTypeTable = null; } void initialize() @@ -315,19 +403,19 @@ static: return symbolName(name, SCstatic, t); } - Symbol* getCString(const(char)[] str, const(char)* symbolName, Segments.Id segment) + Symbol* getCString(const(char)[] str, const(char)[] symbolName, Segments.Id segment) { hasSymbols_ = true; // create data auto dtb = DtBuilder(0); - dtb.nbytes(cast(uint)(str.length + 1), str.ptr); + dtb.nbytes(cast(uint) (str.length + 1), str.toStringz()); // find segment auto seg = Segments[segment]; // create symbol - auto s = symbol_name(symbolName, SCstatic, type_allocn(TYarray, tstypes[TYchar])); + auto s = getStatic(symbolName, type_allocn(TYarray, tstypes[TYchar])); s.Sdt = dtb.finish(); s.Sseg = seg; return s; @@ -343,9 +431,9 @@ static: if (!symbol) { __gshared size_t classNameCount = 0; - char[42] nameString; - sprintf(nameString.ptr, "L_OBJC_METH_VAR_NAME_%lu", classNameCount++); - symbol = getCString(name, nameString.ptr, Segments.Id.methodName); + char[42] buffer; + const symbolName = format(buffer, "L_OBJC_METH_VAR_NAME_%lu", classNameCount++); + symbol = getCString(name, symbolName, Segments.Id.methodName); stringValue.ptrvalue = symbol; } @@ -402,12 +490,19 @@ static: return imageInfo; } - Symbol* getModuleInfo() + Symbol* getModuleInfo(/*const*/ ref ClassDeclarations classes, + /*const*/ ref ClassDeclarations categories) { assert(!moduleInfo); // only allow once per object file auto dtb = DtBuilder(0); + foreach (c; classes) + dtb.xoff(getClassName(c), 0); + + foreach (c; categories) + dtb.xoff(getClassName(c), 0); + Symbol* symbol = symbol_name("L_OBJC_LABEL_CLASS_$", SCstatic, type_allocn(TYarray, tstypes[TYchar])); symbol.Sdt = dtb.finish(); symbol.Sseg = Segments[Segments.Id.moduleInfo]; @@ -418,17 +513,16 @@ static: return moduleInfo; } - /* + /** * Returns: the `_OBJC_METACLASS_$_`/`_OBJC_CLASS_$_` symbol for the given * class declaration. */ - Symbol* getClassName(const ClassDeclaration classDeclaration) + Symbol* getClassName(ObjcClassDeclaration objcClass) { hasSymbols_ = true; - auto isMeta = classDeclaration.objc.isMeta; - auto prefix = isMeta ? "_OBJC_METACLASS_$_" : "_OBJC_CLASS_$_"; - auto name = prefix ~ classDeclaration.ident.toString(); + const prefix = objcClass.isMeta ? "_OBJC_METACLASS_$_" : "_OBJC_CLASS_$_"; + auto name = prefix ~ objcClass.classDeclaration.objc.identifier.toString(); auto stringValue = classNameTable.update(name); auto symbol = cast(Symbol*) stringValue.ptrvalue; @@ -442,15 +536,21 @@ static: return symbol; } + /// ditto + Symbol* getClassName(ClassDeclaration classDeclaration, bool isMeta = false) + { + return getClassName(ObjcClassDeclaration(classDeclaration, isMeta)); + } + /* * Returns: the `L_OBJC_CLASSLIST_REFERENCES_$_` symbol for the given class * declaration. */ - Symbol* getClassReference(const ClassDeclaration classDeclaration) + Symbol* getClassReference(ClassDeclaration classDeclaration) { hasSymbols_ = true; - auto name = classDeclaration.ident.toString(); + auto name = classDeclaration.objc.identifier.toString(); auto stringValue = classReferenceTable.update(name); auto symbol = cast(Symbol*) stringValue.ptrvalue; @@ -514,10 +614,444 @@ static: { return getMethVarRef(ident.toString()); } + + /** + * Returns the Objective-C type encoding for the given type. + * + * The available type encodings are documented by Apple, available at + * $(LINK2 https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100, Type Encoding). + * The type encodings can also be obtained by running an Objective-C + * compiler and using the `@encode()` compiler directive. + * + * Params: + * type = the type to return the type encoding for + * + * Returns: a string containing the type encoding + */ + string getTypeEncoding(Type type) + in + { + assert(type !is null); + } + body + { + enum assertMessage = "imaginary types are not supported by Objective-C"; + + with (ENUMTY) switch (type.ty) + { + case Tvoid: return "v"; + case Tbool: return "B"; + case Tint8: return "c"; + case Tuns8: return "C"; + case Tchar: return "C"; + case Tint16: return "s"; + case Tuns16: return "S"; + case Twchar: return "S"; + case Tint32: return "i"; + case Tuns32: return "I"; + case Tdchar: return "I"; + case Tint64: return "q"; + case Tuns64: return "Q"; + case Tfloat32: return "f"; + case Tcomplex32: return "jf"; + case Tfloat64: return "d"; + case Tcomplex64: return "jd"; + case Tfloat80: return "D"; + case Tcomplex80: return "jD"; + case Timaginary32: assert(false, assertMessage); + case Timaginary64: assert(false, assertMessage); + case Timaginary80: assert(false, assertMessage); + default: return "?"; // unknown + // TODO: add "*" char*, "#" Class, "@" id, ":" SEL + // TODO: add "^" indirection and "^^" double indirection + } + } + + /** + * Returns: the `L_OBJC_METH_VAR_TYPE_` symbol containing the given + * type encoding. + */ + Symbol* getMethVarType(const(char)[] typeEncoding) + { + hasSymbols_ = true; + + auto stringValue = methVarTypeTable.update(typeEncoding); + auto symbol = cast(Symbol*) stringValue.ptrvalue; + + if (symbol) + return symbol; + + __gshared size_t count = 0; + char[42] nameString; + const symbolName = format(nameString, "L_OBJC_METH_VAR_TYPE_%lu", count++); + symbol = getCString(typeEncoding, symbolName, Segments.Id.methodType); + + stringValue.ptrvalue = symbol; + outdata(symbol); + + return symbol; + } + + /// ditto + Symbol* getMethVarType(Type[] types) + { + string typeCode; + typeCode.reserve(types.length); + + foreach (type; types) + typeCode ~= getTypeEncoding(type); + + return getMethVarType(typeCode); + } + + /// ditto + Symbol* getMethVarType(FuncDeclaration func) + { + Type[] types = [func.type.nextOf]; // return type first + + if (func.parameters) + { + types.reserve(func.parameters.length); + + foreach (e; *func.parameters) + types ~= e.type; + } + + return getMethVarType(types); + } + + /// Returns: the externally defined `__objc_empty_cache` symbol + Symbol* getEmptyCache() + { + return emptyCache = emptyCache ? emptyCache : getGlobal("__objc_empty_cache"); + } + + /// Returns: the externally defined `__objc_empty_vtable` symbol + Symbol* getEmptyVTable() + { + return emptyVTable = emptyVTable ? emptyVTable : getGlobal("__objc_empty_vtable"); + } + + /// Returns: the `L_OBJC_CLASS_NAME_` symbol for a class with the given name + Symbol* getClassNameRo(const(char)[] name) + { + hasSymbols_ = true; + + auto stringValue = classNameTable.update(name); + auto symbol = cast(Symbol*) stringValue.ptrvalue; + + __gshared size_t count = 0; + char[42] nameString; + const symbolName = format(nameString, "L_OBJC_CLASS_NAME_%lu", count++); + symbol = getCString(name, symbolName, Segments.Id.className); + stringValue.ptrvalue = symbol; + + return symbol; + } + + /// ditto + Symbol* getClassNameRo(const Identifier ident) + { + return getClassNameRo(ident.toString()); + } } private: +/** + * Functionality for outputting symbols for a specific Objective-C class + * declaration. + */ +struct ObjcClassDeclaration +{ + /// Indicates what kind of class this is. + private enum Flags + { + /// Regular class. + regular = 0x00000, + + /// Meta class. + meta = 0x00001, + + /// Root class. A class without any base class. + root = 0x00002 + } + + /// The class declaration + ClassDeclaration classDeclaration; + + /// `true` if this class is a metaclass. + bool isMeta; + + this(ClassDeclaration classDeclaration, bool isMeta) + in + { + assert(classDeclaration !is null); + } + body + { + this.classDeclaration = classDeclaration; + this.isMeta = isMeta; + } + + /** + * Outputs the class declaration to the object file. + * + * Returns: the exported symbol, that is, `_OBJC_METACLASS_$_` or + * `_OBJC_CLASS_$_` + */ + Symbol* toObjFile() + { + if (classDeclaration.objc.isExtern) + return null; // only a declaration for an externally-defined class + + const segmentId = isMeta ? Segments.Id.metaclass : Segments.Id.class_; + + auto dtb = DtBuilder(0); + toDt(dtb); + + auto symbol = Symbols.getClassName(this); + symbol.Sdt = dtb.finish(); + symbol.Sseg = Segments[segmentId]; + outdata(symbol); + + return symbol; + } + +private: + + /** + * Outputs the class declaration to the object file. + * + * Params: + * dtb = the `DtBuilder` to output the class declaration to + */ + void toDt(ref DtBuilder dtb) + { + auto baseClassSymbol = Symbols.getClassName(classDeclaration.baseClass, + isMeta); + + dtb.xoff(getMetaclass(), 0); // pointer to metaclass + dtb.xoff(baseClassSymbol, 0); // pointer to base class + dtb.xoff(Symbols.getEmptyCache(), 0); + dtb.xoff(Symbols.getEmptyVTable(), 0); + dtb.xoff(getClassRo(), 0); + } + + /// Returns: the name of the metaclass of this class declaration + Symbol* getMetaclass() + { + if (isMeta) + { + // metaclass: return root class's name + // (will be replaced with metaclass reference at load) + + auto metaclassDeclaration = classDeclaration; + + while (metaclassDeclaration.baseClass) + metaclassDeclaration = metaclassDeclaration.baseClass; + + return Symbols.getClassName(metaclassDeclaration, true); + } + + else + { + // regular class: return metaclass with the same name + return ObjcClassDeclaration(classDeclaration, true).toObjFile(); + } + } + + /** + * Returns: the `l_OBJC_CLASS_RO_$_`/`l_OBJC_METACLASS_RO_$_` symbol for + * this class declaration + */ + Symbol* getClassRo() + { + auto dtb = DtBuilder(0); + + dtb.dword(flags); + dtb.dword(instanceStart); + dtb.dword(instanceSize); + dtb.dword(0); // reserved + + dtb.size(0); // ivar layout + dtb.xoff(Symbols.getClassNameRo(classDeclaration.ident), 0); // name of the class + + dtb.xoffOrNull(getMethodList()); // instance method list + dtb.xoffOrNull(getProtocolList()); // protocol list + + if (isMeta) + { + dtb.size(0); // instance variable list + dtb.size(0); // weak ivar layout + dtb.size(0); // properties + } + + else + { + auto ivars = getIVarList(); + + if (ivars && !isMeta) + dtb.xoff(ivars, 0); // instance variable list + else + dtb.size(0); // or null if no ivar + + dtb.size(0); // weak ivar layout + dtb.xoffOrNull(getPropertyList()); // properties + } + + const prefix = isMeta ? "l_OBJC_METACLASS_RO_$_" : "l_OBJC_CLASS_RO_$_"; + const symbolName = prefix ~ classDeclaration.objc.identifier.toString(); + auto symbol = Symbols.getStatic(symbolName); + + symbol.Sdt = dtb.finish(); + symbol.Sseg = Segments[Segments.Id.classRo]; + outdata(symbol); + + return symbol; + } + + /** + * Returns method list for this class declaration. + * + * This is a list of all methods defined in this class declaration, i.e. + * methods with a body. + * + * Returns: the symbol for the method list, `l_OBJC_$_CLASS_METHODS_` or + * `l_OBJC_$_INSTANCE_METHODS_` + */ + Symbol* getMethodList() + { + /** + * Returns the number of methods that should be added to the binary. + * + * Only methods with a body should be added. + * + * Params: + * members = the members of the class declaration + */ + static int methodCount(Dsymbols* members) + { + int count; + + members.foreachDsymbol((member) { + const func = member.isFuncDeclaration; + + if (func && func.fbody) + count++; + }); + + return count; + } + + auto methods = isMeta ? classDeclaration.objc.metaclass.objc.methodList : + classDeclaration.objc.methodList; + + const count = methodCount(methods); + + if (count == 0) + return null; + + auto dtb = DtBuilder(0); + + dtb.dword(24); // _objc_method.sizeof + dtb.dword(count); // method count + + methods.foreachDsymbol((method) { + auto func = method.isFuncDeclaration; + + if (func && func.fbody) + { + assert(func.selector); + dtb.xoff(func.selector.toNameSymbol(), 0); // method name + dtb.xoff(Symbols.getMethVarType(func), 0); // method type string + dtb.xoff(func.toSymbol(), 0); // function implementation + } + }); + + const prefix = isMeta ? "l_OBJC_$_CLASS_METHODS_" : "l_OBJC_$_INSTANCE_METHODS_"; + const segmentId = isMeta ? Segments.Id.metaclass : Segments.Id.class_; + const symbolName = prefix ~ classDeclaration.objc.identifier.toString(); + auto symbol = Symbols.getStatic(symbolName); + + symbol.Sdt = dtb.finish(); + symbol.Sseg = Segments[segmentId]; + + return symbol; + } + + Symbol* getProtocolList() + { + // protocols are not supported yet + return null; + } + + Symbol* getIVarList() + { + // ivars are not supported yet + return null; + } + + Symbol* getPropertyList() + { + // properties are not supported yet + return null; + } + + /** + * Returns the flags for this class declaration. + * + * That is, if this is a regular class, a metaclass and/or a root class. + * + * Returns: the flags + */ + uint flags() const + { + uint flags = isMeta ? Flags.meta : Flags.regular; + + if (classDeclaration.objc.isRootClass) + flags |= Flags.root; + + return flags; + } + + /** + * Returns the offset of where an instance of this class starts. + * + * For a metaclass this is always `40`. For a class with no instance + * variables this is the size of the class declaration. For a class with + * instance variables it's the offset of the first instance variable. + * + * Returns: the instance start + */ + int instanceStart() + { + if (isMeta) + return 40; + + const start = cast(uint) classDeclaration.size(classDeclaration.loc); + + if (!classDeclaration.members || classDeclaration.members.length == 0) + return start; + + foreach (member; *classDeclaration.members) + { + auto var = member.isVarDeclaration; + + if (var && var.isField) + return var.offset; + } + + return start; + } + + /// Returns: the size of an instance of this class + int instanceSize() + { + return isMeta ? 40 : cast(int) classDeclaration.size(classDeclaration.loc); + } +} + /* * Formats the given arguments into the given buffer. * @@ -541,3 +1075,64 @@ char[] format(size_t bufLength, Args...)(return ref char[bufLength] buffer, return buffer[0 .. length]; } + +/// Returns: the symbol of the given selector +Symbol* toNameSymbol(const ObjcSelector* selector) +{ + return Symbols.getMethVarName(selector.toString()); +} + +/** + * Adds a reference to the given `symbol` or null if the symbol is null. + * + * Params: + * dtb = the dt builder to add the symbol to + * symbol = the symbol to add + */ +void xoffOrNull(ref DtBuilder dtb, Symbol* symbol) +{ + if (symbol) + dtb.xoff(symbol, 0); + else + dtb.size(0); +} + +/** + * Converts the given D string to a null terminated C string. + * + * Asserts if `str` is longer than `maxLength`, with assertions enabled. With + * assertions disabled it will truncate the result to `maxLength`. + * + * Params: + * maxLength = the max length of `str` + * str = the string to convert + * buf = the buffer where to allocate the result. By default this will be + * allocated in the caller scope using `alloca`. If the buffer is created + * by the callee it needs to be able to fit at least `str.length + 1` bytes + * + * Returns: the given string converted to a C string, a slice of `str` or the + * given buffer `buffer` + */ +const(char)* toStringz(size_t maxLength = 4095)(in const(char)[] str, + scope return void[] buffer = alloca(maxLength + 1)[0 .. maxLength + 1]) pure +in +{ + assert(maxLength >= str.length); +} +out(result) +{ + assert(str.length == result.strlen); +} +body +{ + if (str.length == 0) + return "".ptr; + + const maxLength = buffer.length - 1; + const len = str.length > maxLength ? maxLength : str.length; + auto buf = cast(char[]) buffer[0 .. len + 1]; + buf[0 .. len] = str[0 .. len]; + buf[len] = '\0'; + + return cast(const(char)*) buf.ptr; +} diff --git a/src/dmd/semantic2.d b/src/dmd/semantic2.d index aa66c2b66242..36aced3114f5 100644 --- a/src/dmd/semantic2.d +++ b/src/dmd/semantic2.d @@ -436,12 +436,6 @@ private extern(C++) final class Semantic2Visitor : Visitor return 0; }); } - objc.setSelector(fd, sc); - objc.validateSelector(fd); - if (ClassDeclaration cd = fd.parent.isClassDeclaration()) - { - objc.checkLinkage(fd); - } if (!fd.type || fd.type.ty != Tfunction) return; TypeFunction f = cast(TypeFunction) fd.type; diff --git a/src/dmd/semantic3.d b/src/dmd/semantic3.d index 6f722e947f19..653775b3acb3 100644 --- a/src/dmd/semantic3.d +++ b/src/dmd/semantic3.d @@ -365,7 +365,9 @@ private extern(C++) final class Semantic3Visitor : Visitor // Declare 'this' auto ad = funcdecl.isThis(); - funcdecl.vthis = funcdecl.declareThis(sc2, ad); + auto hiddenParams = funcdecl.declareThis(sc2, ad); + funcdecl.vthis = hiddenParams.vthis; + funcdecl.selectorParameter = hiddenParams.selectorParameter; //printf("[%s] ad = %p vthis = %p\n", loc.toChars(), ad, vthis); //if (vthis) printf("\tvthis.type = %s\n", vthis.type.toChars()); diff --git a/src/dmd/toobj.d b/src/dmd/toobj.d index c6d95c7d69e4..c05686d7a675 100644 --- a/src/dmd/toobj.d +++ b/src/dmd/toobj.d @@ -215,7 +215,7 @@ void genModuleInfo(Module m) //printf("nameoffset = x%x\n", nameoffset); } - objc.generateModuleInfo(); + objc.generateModuleInfo(m); m.csym.Sdt = dtb.finish(); out_readonly(m.csym); outdata(m.csym); @@ -346,6 +346,12 @@ void toObjFile(Dsymbol ds, bool multiobj) */ cd.members.foreachDsymbol( (s) { s.accept(this); } ); + if (cd.classKind == ClassKind.objc) + { + objc.toObjFile(cd); + return; + } + // If something goes wrong during this pass don't bother with the // rest as we may have incomplete info // https://issues.dlang.org/show_bug.cgi?id=17918 diff --git a/src/dmd/typesem.d b/src/dmd/typesem.d index 8f96c52da557..b3a1e9193637 100644 --- a/src/dmd/typesem.d +++ b/src/dmd/typesem.d @@ -1726,11 +1726,8 @@ extern(C++) Type typeSemantic(Type t, Loc loc, Scope* sc) /* Don't semantic for sym because it should be deferred until * sizeof needed or its members accessed. */ - // instead, parent should be set correctly, except for Objective-C - // metaclasses which don't have a parent since they're not directly - // accessible in the code. - assert((mtype.sym.classKind == ClassKind.objc && mtype.sym.objc.isMeta) - || mtype.sym.parent !is null); + // instead, parent should be set correctly + assert(mtype.sym.parent); if (mtype.sym.type.ty == Terror) return error(); diff --git a/test/compilable/objc_interface.d b/test/compilable/objc_class.d similarity index 69% rename from test/compilable/objc_interface.d rename to test/compilable/objc_class.d index 866184284d64..25daab28fa09 100644 --- a/test/compilable/objc_interface.d +++ b/test/compilable/objc_class.d @@ -1,14 +1,14 @@ // EXTRA_OBJC_SOURCES extern (Objective-C) -interface A +class A { void oneTwo(int a, int b) pure @selector("one:two:"); void test(int a, int b, int c) @selector("test:::"); } // https://issues.dlang.org/show_bug.cgi?id=19494 -extern (Objective-C) interface NSObject +extern (Objective-C) class NSObject { - extern (Objective-C) interface Class {} + extern (Objective-C) class Class {} } diff --git a/test/fail_compilation/deprecate_objc_interface.d b/test/fail_compilation/deprecate_objc_interface.d new file mode 100644 index 000000000000..ccf4e68c7542 --- /dev/null +++ b/test/fail_compilation/deprecate_objc_interface.d @@ -0,0 +1,13 @@ +// EXTRA_OBJC_SOURCES +// REQUIRED_ARGS: -de +/* +TEST_OUTPUT: +--- +fail_compilation/deprecate_objc_interface.d(10): Deprecation: interface `deprecate_objc_interface.NSObject` Objective-C interfaces have been deprecated +fail_compilation/deprecate_objc_interface.d(10): Representing an Objective-C class as a D interface has been deprecated. Please use the `class` keyword instead +--- +*/ +extern (Objective-C) interface NSObject +{ + +} diff --git a/test/fail_compilation/objc_interface1.d b/test/fail_compilation/objc_class1.d similarity index 51% rename from test/fail_compilation/objc_interface1.d rename to test/fail_compilation/objc_class1.d index 61c600e91f13..c7604091d208 100644 --- a/test/fail_compilation/objc_interface1.d +++ b/test/fail_compilation/objc_class1.d @@ -2,11 +2,11 @@ /* TEST_OUTPUT: --- -fail_compilation/objc_interface1.d(11): Error: function `objc_interface1.A.oneTwo` must have Objective-C linkage to attach a selector +fail_compilation/objc_class1.d(11): Error: function `objc_class1.A.oneTwo` must have Objective-C linkage to attach a selector --- */ -interface A +class A { void oneTwo(int a, int b) @selector("one:two:"); // selector attached in non-Objective-C interface } diff --git a/test/fail_compilation/objc_class2.d b/test/fail_compilation/objc_class2.d new file mode 100644 index 000000000000..f8d4a4aa5dcc --- /dev/null +++ b/test/fail_compilation/objc_class2.d @@ -0,0 +1,13 @@ +// EXTRA_OBJC_SOURCES +/* +TEST_OUTPUT: +--- +fail_compilation/objc_class2.d(12): Error: function `objc_class2.A.test` number of colons in Objective-C selector must match number of parameters +--- +*/ + +extern (Objective-C) +class A +{ + void test(int a, int b, int c) @selector("test:"); // non-matching number of colon +} diff --git a/test/fail_compilation/objc_class3.d b/test/fail_compilation/objc_class3.d new file mode 100644 index 000000000000..3d6c1467ff09 --- /dev/null +++ b/test/fail_compilation/objc_class3.d @@ -0,0 +1,20 @@ +// EXTRA_OBJC_SOURCES +/* +TEST_OUTPUT: +--- +fail_compilation/objc_class3.d(13): Error: function `objc_class3.A.test!int.test` template cannot have an Objective-C selector attached +fail_compilation/objc_class3.d(19): Error: template instance `objc_class3.A.test!int` error instantiating +--- +*/ + +extern (Objective-C) +class A +{ + void test(T)(T a) @selector("test:"); // selector defined for template +} + +void test() +{ + A a; + a.test(3); +} diff --git a/test/fail_compilation/objc_interface2.d b/test/fail_compilation/objc_interface2.d deleted file mode 100644 index f62fa6414505..000000000000 --- a/test/fail_compilation/objc_interface2.d +++ /dev/null @@ -1,13 +0,0 @@ -// EXTRA_OBJC_SOURCES -/* -TEST_OUTPUT: ---- -fail_compilation/objc_interface2.d(12): Error: function `objc_interface2.A.test` number of colons in Objective-C selector must match number of parameters ---- -*/ - -extern (Objective-C) -interface A -{ - void test(int a, int b, int c) @selector("test:"); // non-matching number of colon -} diff --git a/test/fail_compilation/objc_interface3.d b/test/fail_compilation/objc_interface3.d deleted file mode 100644 index d91801199f33..000000000000 --- a/test/fail_compilation/objc_interface3.d +++ /dev/null @@ -1,20 +0,0 @@ -// EXTRA_OBJC_SOURCES -/* -TEST_OUTPUT: ---- -fail_compilation/objc_interface3.d(13): Error: function `objc_interface3.A.test!int.test` template cannot have an Objective-C selector attached -fail_compilation/objc_interface3.d(19): Error: template instance `objc_interface3.A.test!int` error instantiating ---- -*/ - -extern (Objective-C) -interface A -{ - void test(T)(T a) @selector("test:"); // selector defined for template -} - -void test() -{ - A a; - a.test(3); -} diff --git a/test/fail_compilation/objc_non_objc_base.d b/test/fail_compilation/objc_non_objc_base.d index d51879e25c30..6e2b0782bea9 100644 --- a/test/fail_compilation/objc_non_objc_base.d +++ b/test/fail_compilation/objc_non_objc_base.d @@ -2,11 +2,11 @@ /* TEST_OUTPUT: --- -fail_compilation/objc_non_objc_base.d(12): Error: interface `objc_non_objc_base.A` base interface for an Objective-C interface must be `extern (Objective-C)` +fail_compilation/objc_non_objc_base.d(12): Error: class `objc_non_objc_base.A` base class for an Objective-C class must be `extern (Objective-C)` --- */ interface Base {} extern (Objective-C) -interface A : Base {} +class A : Base {} diff --git a/test/runnable/extra-files/objc_class.m b/test/runnable/extra-files/objc_class.m new file mode 100644 index 000000000000..dbdbb770b5e5 --- /dev/null +++ b/test/runnable/extra-files/objc_class.m @@ -0,0 +1,20 @@ +#import + +@interface Foo : NSObject ++(int) classMethod:(int)a; +-(int) instanceMethod:(int)a; +@end + +int callFooInstanceMethod(int a) +{ + Foo* foo = [[Foo alloc] init]; + int result = [foo instanceMethod:a]; + [foo release]; + + return result; +} + +int callFooClassMethod(int a) +{ + return [Foo classMethod: a]; +} diff --git a/test/runnable/objc_call.d b/test/runnable/objc_call.d index 8c47588dcddc..a798c35c9cc9 100644 --- a/test/runnable/objc_call.d +++ b/test/runnable/objc_call.d @@ -2,13 +2,13 @@ // REQUIRED_ARGS: -L-framework -LFoundation extern (Objective-C) -interface Class +class Class { NSObject alloc() @selector("alloc"); } extern (Objective-C) -interface NSObject +class NSObject { NSObject initWithUTF8String(in char* str) @selector("initWithUTF8String:"); void release() @selector("release"); diff --git a/test/runnable/objc_call_static.d b/test/runnable/objc_call_static.d index 0a9f23a90395..560de5422d75 100644 --- a/test/runnable/objc_call_static.d +++ b/test/runnable/objc_call_static.d @@ -2,7 +2,7 @@ // REQUIRED_ARGS: -L-framework -LFoundation extern (Objective-C) -interface NSObject +class NSObject { static NSObject alloc() @selector("alloc"); static NSObject allocWithZone(void* zone) @selector("allocWithZone:"); @@ -16,7 +16,7 @@ void main() auto obj2 = NSObject.allocWithZone(null); auto obj3 = NSObject.alloc().init(); - assert(obj1); - assert(obj2); - assert(obj3); + assert(obj1 !is null); + assert(obj2 !is null); + assert(obj3 !is null); } diff --git a/test/runnable/objc_class.d b/test/runnable/objc_class.d new file mode 100644 index 000000000000..d2cede2e6d88 --- /dev/null +++ b/test/runnable/objc_class.d @@ -0,0 +1,62 @@ +// EXTRA_OBJC_SOURCES: objc_class.m +// REQUIRED_ARGS: -L-framework -LFoundation + +// This function is implemented in `runnable/extra-files/objc_class.m` and will +// create a new instance of `Foo` (defined below), call `callFooInstanceMethod` +// and return the result of `callFooInstanceMethod`. +extern (C) int callFooInstanceMethod(int); + +// This function is implemented in `runnable/extra-files/objc_class.m` and will +// call the `classMethod` method defined in `Foo` (below) and return the result +// of `classMethod` +extern (C) int callFooClassMethod(int); + +extern (Objective-C) +class NSObject +{ + static NSObject alloc() @selector("alloc"); + NSObject init() @selector("init"); + void release() @selector("release"); +} + +extern (Objective-C) +class Foo : NSObject +{ + override static Foo alloc() @selector("alloc"); + override Foo init() @selector("init"); + + static int classMethod(int a) @selector("classMethod:") + { + return a; + } + + int instanceMethod(int a) @selector("instanceMethod:") + { + return a; + } +} + +void testClassDeclaration() +{ + assert(NSObject.alloc.init !is null); +} + +void testSubclass() +{ + assert(Foo.alloc.init.instanceMethod(3) == 3); +} + +// verify that Objective-C can instantiate a class defined in D and call a +// both a instance method and a class/static method. +void testCallThroughObjc() +{ + assert(callFooInstanceMethod(4) == 4); + assert(callFooClassMethod(5) == 5); +} + +void main() +{ + testClassDeclaration(); + testSubclass(); + testCallThroughObjc(); +} diff --git a/test/runnable/objc_objc_msgSend.d b/test/runnable/objc_objc_msgSend.d index a6fa28bd70d0..712f47fef0cf 100644 --- a/test/runnable/objc_objc_msgSend.d +++ b/test/runnable/objc_objc_msgSend.d @@ -9,7 +9,7 @@ struct Struct } extern (Objective-C) -interface Class +class Class { stret alloc_stret() @selector("alloc"); fp2ret alloc_fp2ret() @selector("alloc"); @@ -19,7 +19,7 @@ interface Class } extern (Objective-C) -interface stret +class stret { stret init() @selector("init"); Struct getValue() @selector("getValue"); @@ -27,7 +27,7 @@ interface stret } extern (Objective-C) -interface fp2ret +class fp2ret { fp2ret init() @selector("init"); creal getValue() @selector("getValue"); @@ -35,7 +35,7 @@ interface fp2ret } extern (Objective-C) -interface fpret +class fpret { fpret init() @selector("init"); real getValue() @selector("getValue"); @@ -43,7 +43,7 @@ interface fpret } extern (Objective-C) -interface float32 +class float32 { float32 init() @selector("init"); float getValue() @selector("getValue"); @@ -51,7 +51,7 @@ interface float32 } extern (Objective-C) -interface double64 +class double64 { double64 init() @selector("init"); double getValue() @selector("getValue");