/** * Interfacing with Objective-C. * * Specification: $(LINK2 https://dlang.org/spec/objc_interface.html, Interfacing to Objective-C) * * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/objc.d, _objc.d) * Documentation: https://dlang.org/phobos/dmd_objc.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/objc.d */ module dmd.objc; import dmd.aggregate; import dmd.arraytypes; import dmd.astenums; import dmd.attrib; import dmd.cond; import dmd.dclass; import dmd.declaration; import dmd.denum; 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; import dmd.globals; import dmd.gluelayer; import dmd.hdrgen; import dmd.id; import dmd.identifier; import dmd.location; import dmd.mtype; import dmd.root.array; import dmd.common.outbuffer; import dmd.root.stringtable; import dmd.target; import dmd.tokens; struct ObjcSelector { // MARK: Selector private __gshared StringTable!(ObjcSelector*) stringtable; private __gshared int incnum = 0; const(char)* stringvalue; size_t stringlen; size_t paramCount; extern (C++) static void _init() { stringtable._init(); } extern (D) this(const(char)* sv, size_t len, size_t pcount) { stringvalue = sv; stringlen = len; paramCount = pcount; } extern (D) static ObjcSelector* lookup(const(char)* s) { size_t len = 0; size_t pcount = 0; const(char)* i = s; while (*i != 0) { ++len; if (*i == ':') ++pcount; ++i; } return lookup(s, len, pcount); } extern (D) static ObjcSelector* lookup(const(char)* s, size_t len, size_t pcount) { auto sv = stringtable.update(s, len); ObjcSelector* sel = sv.value; if (!sel) { sel = new ObjcSelector(sv.toDchars(), len, pcount); sv.value = sel; } return sel; } extern (C++) static ObjcSelector* create(FuncDeclaration fdecl) { OutBuffer buf; TypeFunction 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]); buf.writeByte(':'); goto Lcomplete; } // write identifier in selector buf.write(id[]); // add mangled type and colon for each parameter if (nparams) { buf.writeByte('_'); foreach (i, fparam; ftype.parameterList) { mangleToBuffer(fparam.type, &buf); 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); } extern (D) const(char)[] toString() const pure { return stringvalue[0 .. stringlen]; } } private __gshared Objc _objc; Objc objc() { return _objc; } /** * Contains all data for a class declaration that is needed for the Objective-C * integration. */ 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. FuncDeclaration[] methodList; extern (D) this(ClassDeclaration classDeclaration) { this.classDeclaration = classDeclaration; } bool isRootClass() const { return classDeclaration.classKind == ClassKind.objc && !metaclass && !classDeclaration.baseClass; } } /** * Contains all data for a function declaration that is needed for the * Objective-C integration. */ extern (C++) struct ObjcFuncDeclaration { /// The method selector (member functions only). ObjcSelector* selector; /// The implicit selector parameter. VarDeclaration selectorParameter; /// `true` if this function declaration is declared optional. bool isOptional; } // Should be an interface extern(C++) abstract class Objc { static void _init() { if (target.objc.supported) _objc = new Supported; else _objc = new Unsupported; } /** * Deinitializes the global state of the compiler. * * This can be used to restore the state set by `_init` to its original * state. */ static void deinitialize() { _objc = _objc.init; } abstract void setObjc(ClassDeclaration cd); abstract void setObjc(InterfaceDeclaration); /** * Returns a pretty textual representation of the given class declaration. * * Params: * classDeclaration = the class declaration to return the textual representation for * qualifyTypes = `true` if types should be qualified in the result * * Returns: the textual representation */ abstract const(char)* toPrettyChars(ClassDeclaration classDeclaration, bool qualifyTypes) 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; /** * Marks the given function declaration as optional. * * A function declaration is considered optional if it's annotated with the * UDA: `@(core.attribute.optional)`. Only function declarations inside * interface declarations and with Objective-C linkage can be declared as * optional. * * Params: * functionDeclaration = the function declaration to be set as optional * sc = the scope from the semantic phase */ abstract void setAsOptional(FuncDeclaration functionDeclaration, Scope* sc) const; /** * Validates function declarations declared optional. * * Params: * functionDeclaration = the function declaration to validate */ abstract void validateOptional(FuncDeclaration functionDeclaration) 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. * * This is only used for class/static methods. For instance methods, no * Objective-C specialization is necessary. * * Params: * funcDeclaration = the function declaration to get the `this` pointer for * * Returns: the `this` pointer of the given function declaration, or `null` * if the given function declaration is not an Objective-C method. */ 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. * * Will only be performed on regular Objective-C classes, not on metaclasses. * * Params: * classDeclaration = the class/interface declaration to set the metaclass on */ abstract void setMetaclass(InterfaceDeclaration interfaceDeclaration, Scope* sc) const; /// ditto abstract void setMetaclass(ClassDeclaration classDeclaration, Scope* sc) const; /** * Returns Objective-C runtime metaclass of the given class declaration. * * `ClassDeclaration.ObjcClassDeclaration.metaclass` contains the metaclass * from the semantic point of view. This function returns the metaclass from * the Objective-C runtime's point of view. Here, the metaclass of a * metaclass is the root metaclass, not `null`. The root metaclass's * metaclass is itself. * * Params: * classDeclaration = The class declaration to return the metaclass of * * Returns: the Objective-C runtime metaclass of the given class declaration */ 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. * * To solve the fragile base class problem in Objective-C, fields have a * dynamic offset instead of a static offset. The compiler outputs a * statically known offset which later the dynamic loader can update, if * necessary, when the application is loaded. Due to this behavior it * doesn't make sense to be able to get the offset of a field at compile * time, because this offset might not actually be the same at runtime. * * To get the offset of a field that is correct at runtime, functionality * from the Objective-C runtime can be used instead. * * Params: * expression = the `.offsetof`/`.tupleof` expression * aggregateDeclaration = the aggregate declaration the field of the * `.offsetof`/`.tupleof` expression belongs to * type = the type of the receiver of the `.tupleof` expression * * See_Also: * $(LINK2 https://en.wikipedia.org/wiki/Fragile_binary_interface_problem, * Fragile Binary Interface Problem) * * See_Also: * $(LINK2 https://developer.apple.com/documentation/objectivec/objective_c_runtime, * Objective-C Runtime) */ abstract void checkOffsetof(Expression expression, AggregateDeclaration aggregateDeclaration) const; /// ditto abstract void checkTupleof(Expression expression, TypeClass type) const; } extern(C++) private final class Unsupported : Objc { extern(D) final this() { ObjcGlue.initialize(); } override void setObjc(ClassDeclaration cd) { cd.error("Objective-C classes not supported"); } override void setObjc(InterfaceDeclaration id) { id.error("Objective-C interfaces not supported"); } override const(char)* toPrettyChars(ClassDeclaration, bool qualifyTypes) const { assert(0, "Should never be called when Objective-C is not supported"); } override void setSelector(FuncDeclaration, Scope*) { // noop } override void validateSelector(FuncDeclaration) { // noop } override void checkLinkage(FuncDeclaration) { // noop } override bool isVirtual(const FuncDeclaration) const { assert(0, "Should never be called when Objective-C is not supported"); } override void setAsOptional(FuncDeclaration, Scope*) const { // noop } override void validateOptional(FuncDeclaration) const { // noop } 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 VarDeclaration createSelectorParameter(FuncDeclaration, Scope*) const { return null; } override void setMetaclass(InterfaceDeclaration, Scope*) const { // noop } override void setMetaclass(ClassDeclaration, Scope*) const { // noop } override ClassDeclaration getRuntimeMetaclass(ClassDeclaration classDeclaration) const { 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 } override void checkTupleof(Expression expression, TypeClass type) const { // noop } } extern(C++) private final class Supported : Objc { extern(D) final this() { VersionCondition.addPredefinedGlobalIdent("D_ObjectiveC"); ObjcGlue.initialize(); ObjcSelector._init(); } override void setObjc(ClassDeclaration cd) { cd.classKind = ClassKind.objc; cd.objc.isExtern = (cd.storage_class & STC.extern_) > 0; } override void setObjc(InterfaceDeclaration id) { id.classKind = ClassKind.objc; id.objc.isExtern = true; } override const(char)* toPrettyChars(ClassDeclaration cd, bool qualifyTypes) const { return cd.parent.toPrettyChars(qualifyTypes); } override void setSelector(FuncDeclaration fd, Scope* sc) { foreachUda(fd, sc, (e) { if (!e.isStructLiteralExp()) return 0; auto literal = e.isStructLiteralExp(); assert(literal.sd); if (!isCoreUda(literal.sd, Id.udaSelector)) return 0; if (fd.objc.selector) { fd.error("can only have one Objective-C selector per method"); return 1; } assert(literal.elements.length == 1); auto se = (*literal.elements)[0].toStringExp(); assert(se); fd.objc.selector = ObjcSelector.lookup(se.toUTF8(sc).peekString().ptr); return 0; }); } override void validateSelector(FuncDeclaration fd) { if (!fd.objc.selector) return; TypeFunction tf = cast(TypeFunction)fd.type; if (fd.objc.selector.paramCount != tf.parameterList.parameters.length) fd.error("number of colons in Objective-C selector must match number of parameters"); if (fd.parent && fd.parent.isTemplateInstance()) fd.error("template cannot have an Objective-C selector attached"); } override void checkLinkage(FuncDeclaration fd) { if (fd._linkage != LINK.objc && fd.objc.selector) 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); } do { if (fd.toParent.isInterfaceDeclaration && fd.isFinal) return false; // * 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.visibility) return !(kind == Visibility.Kind.private_ || kind == Visibility.Kind.package_); } override void setAsOptional(FuncDeclaration fd, Scope* sc) const { const count = declaredAsOptionalCount(fd, sc); fd.objc.isOptional = count > 0; if (count > 1) fd.error("can only declare a function as optional once"); } /// Returns: the number of times `fd` has been declared as optional. private int declaredAsOptionalCount(FuncDeclaration fd , Scope* sc) const { int count; foreachUda(fd, sc, (e) { if (!e.isTypeExp()) return 0; auto typeExp = e.isTypeExp(); if (typeExp.type.ty != Tenum) return 0; auto typeEnum = cast(TypeEnum) typeExp.type; if (isCoreUda(typeEnum.sym, Id.udaOptional)) count++; return 0; }); return count; } override void validateOptional(FuncDeclaration fd) const { if (!fd.objc.isOptional) return; if (fd._linkage != LINK.objc) { fd.error("only functions with Objective-C linkage can be declared as optional"); const linkage = linkageToString(fd._linkage); errorSupplemental(fd.loc, "function is declared with %.*s linkage", cast(uint) linkage.length, linkage.ptr); } auto parent = fd.parent; if (parent && parent.isTemplateInstance()) { fd.error("template cannot be optional"); parent = parent.parent; assert(parent); } if (parent && !parent.isInterfaceDeclaration()) { fd.error("only functions declared inside interfaces can be optional"); errorSupplemental(fd.loc, "function is declared inside %s", fd.parent.kind); } } override ClassDeclaration getParent(FuncDeclaration fd, ClassDeclaration cd) const out(metaclass) { assert(metaclass); } do { 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); } do { if (cd.classKind != ClassKind.objc) return; if (!fd.objc.selector) return; assert(fd.isStatic ? cd.objc.isMeta : !cd.objc.isMeta); cd.objc.methodList ~= fd; } override inout(AggregateDeclaration) isThis(inout FuncDeclaration funcDeclaration) const { with(funcDeclaration) { if (!objc.selector) return null; // Use Objective-C class object as 'this' auto cd = isMember2().isClassDeclaration(); if (cd.classKind == ClassKind.objc) { if (!cd.objc.isMeta) return cd.objc.metaclass; } return null; } } override VarDeclaration createSelectorParameter(FuncDeclaration fd, Scope* sc) const in { assert(fd.selectorParameter is null); } do { if (!fd.objc.selector) return null; auto ident = Identifier.generateAnonymousId("_cmd"); auto var = new VarDeclaration(fd.loc, Type.tvoidptr, ident, 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 { auto newMetaclass(Loc loc, BaseClasses* metaBases) { auto ident = createMetaclassIdentifier(interfaceDeclaration); return new InterfaceDeclaration(loc, ident, metaBases); } .setMetaclass!newMetaclass(interfaceDeclaration, sc); } override void setMetaclass(ClassDeclaration classDeclaration, Scope* sc) const { auto newMetaclass(Loc loc, BaseClasses* metaBases) { auto ident = createMetaclassIdentifier(classDeclaration); return new ClassDeclaration(loc, ident, metaBases, new Dsymbols(), 0); } .setMetaclass!newMetaclass(classDeclaration, sc); } override ClassDeclaration getRuntimeMetaclass(ClassDeclaration classDeclaration) const { if (!classDeclaration.objc.metaclass && classDeclaration.objc.isMeta) { if (classDeclaration.baseClass) return getRuntimeMetaclass(classDeclaration.baseClass); else return classDeclaration; } else 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) return; enum errorMessage = "no property `offsetof` for member `%s` of type " ~ "`%s`"; enum supplementalMessage = "`offsetof` is not available for members " ~ "of Objective-C classes. Please use the Objective-C runtime instead"; expression.error(errorMessage, expression.toChars(), expression.type.toChars()); expression.errorSupplemental(supplementalMessage); } override void checkTupleof(Expression expression, TypeClass type) const { if (type.sym.classKind != ClassKind.objc) return; expression.error("no property `tupleof` for type `%s`", type.toChars()); expression.errorSupplemental("`tupleof` is not available for members " ~ "of Objective-C classes. Please use the Objective-C runtime instead"); } } /* * Creates and sets the metaclass on the given class/interface declaration. * * Will only be performed on regular Objective-C classes, not on metaclasses. * * Params: * newMetaclass = a function that returns the metaclass to set. This should * return the same type as `T`. * classDeclaration = the class/interface declaration to set the metaclass on */ private void setMetaclass(alias newMetaclass, T)(T classDeclaration, Scope* sc) if (is(T == ClassDeclaration) || is(T == InterfaceDeclaration)) { static if (is(T == ClassDeclaration)) enum errorType = "class"; else enum errorType = "interface"; with (classDeclaration) { if (classKind != ClassKind.objc || objc.isMeta || objc.metaclass) return; if (!objc.identifier) objc.identifier = classDeclaration.ident; auto metaBases = new BaseClasses(); foreach (base ; baseclasses.opSlice) { auto baseCd = base.sym; assert(baseCd); if (baseCd.classKind == ClassKind.objc) { assert(baseCd.objc.metaclass); assert(baseCd.objc.metaclass.objc.isMeta); assert(baseCd.objc.metaclass.type.ty == Tclass); auto metaBase = new BaseClass(baseCd.objc.metaclass.type); metaBase.sym = baseCd.objc.metaclass; metaBases.push(metaBase); } else { error("base " ~ errorType ~ " for an Objective-C " ~ errorType ~ " must be `extern (Objective-C)`"); } } objc.metaclass = newMetaclass(loc, metaBases); 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.members = new Dsymbols(); objc.metaclass.dsymbolSemantic(sc); } } private Identifier createMetaclassIdentifier(ClassDeclaration classDeclaration) { const name = "class_" ~ classDeclaration.ident.toString ~ "_Meta"; return Identifier.generateAnonymousId(name); }