{ Copyright (c) 2010 by Jonas Maebe This unit implements some Objective-C type helper routines (minimal unit dependencies, usable in symdef). This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. **************************************************************************** } {$i fpcdefs.inc} unit objcdef; interface uses node, symtype; { The internals of Objective-C's @encode() functionality: encode a type into the internal format used by the run time. Returns false if a type is not representable by the Objective-C run time, and in that case also the failing definition. } function objctryencodetype(def: tdef; out encodedtype: ansistring; out founderror: tdef): boolean; { Check whether a type can be used in an Objective-C method signature or field declaration. } function objcchecktype(def: tdef; out founderror: tdef): boolean; { add type info for def at the end of encodedstr. recordinfostate influences whether a record-style type will be fully encoded, or just using its type name. bpacked indicates whether a record/array is bitpacked. On error, founderror contains the type that triggered the error. } type trecordinfostate = (ris_initial, ris_afterpointer, ris_dontprint); function objcaddencodedtype(def: tdef; recordinfostate: trecordinfostate; bpacked: boolean; var encodedstr: ansistring; out founderror: tdef): boolean; implementation uses globtype, cutils,cclasses, verbose,systems, symtable,symconst,symsym,symdef, defutil,paramgr; {****************************************************************** Type encoding *******************************************************************} function encoderecst(const recname: ansistring; recst: tabstractrecordsymtable; var encodedstr: ansistring; out founderror: tdef): boolean; var variantstarts: tfplist; i, varindex: longint; field, firstfield: tfieldvarsym; firstfieldvariant, bpacked: boolean; begin result:=false; bpacked:=recst.fieldalignment=bit_alignment; { Is the first field already the start of a variant? } firstfield:=nil; firstfieldvariant:=false; for i:=0 to recst.symlist.count-1 do begin if (tsym(recst.symlist[i]).typ<>fieldvarsym) then continue; field:=tfieldvarsym(recst.symlist[i]); if not assigned(firstfield) then firstfield:=field else if (vo_is_first_field in field.varoptions) then begin if (field.fieldoffset=firstfield.fieldoffset) then firstfieldvariant:=true; end; end; variantstarts:=tfplist.create; encodedstr:=encodedstr+'{'+recname+'='; for i:=0 to recst.symlist.count-1 do begin if (tsym(recst.symlist[i]).typ<>fieldvarsym) then continue; field:=tfieldvarsym(recst.symlist[i]); { start of a variant part? } if ((field=firstfield) and firstfieldvariant) or ((field<>firstfield) and (vo_is_first_field in field.varoptions)) then begin varindex:=variantstarts.count-1; if (varindex=-1) or (tfieldvarsym(variantstarts[varindex]).fieldoffset=0) and (tfieldvarsym(variantstarts[varindex]).fieldoffset>field.fieldoffset) do begin { close more deeply nested variants } encodedstr:=encodedstr+'})'; dec(varindex); end; if (varindex<0) then internalerror(2009081805); if (tfieldvarsym(variantstarts[varindex]).fieldoffset<>field.fieldoffset) then internalerror(2009081804); { variant at the same level as a previous one } variantstarts.count:=varindex+1; { No need to add this field, it has the same offset as the previous one at this position. } if tfieldvarsym(variantstarts[varindex]).fieldoffset<>field.fieldoffset then internalerror(2009081601); { close previous variant sub-part and start new one } encodedstr:=encodedstr+'}{?='; end end; if not objcaddencodedtype(field.vardef,ris_afterpointer,bpacked,encodedstr,founderror) then exit; end; for i:=0 to variantstarts.count-1 do encodedstr:=encodedstr+'})'; variantstarts.free; encodedstr:=encodedstr+'}'; result:=true end; function objcaddencodedtype(def: tdef; recordinfostate: trecordinfostate; bpacked: boolean; var encodedstr: ansistring; out founderror: tdef): boolean; var recname: ansistring; recdef: trecorddef; objdef: tobjectdef; len: aint; c: char; newstate: trecordinfostate; addrpara: boolean; begin result:=true; case def.typ of stringdef : begin case tstringdef(def).stringtype of st_shortstring: { include length byte } encodedstr:=encodedstr+'['+tostr(tstringdef(def).len+1)+'C]'; else { While we could handle refcounted Pascal strings correctly when such methods are called from Pascal code, things would completely break down if they were called from Objective-C code/reflection since the necessary refcount helper calls would be missing on the caller side (unless we'd automatically generate wrappers). } result:=false; end; end; enumdef, orddef : begin if bpacked and not is_void(def) then encodedstr:=encodedstr+'b'+tostr(def.packedbitsize) else begin if is_void(def) then c:='v' { in gcc, sizeof(_Bool) = sizeof(char) } else if is_boolean(def) and (def.size=1) then c:='B' else begin case def.size of 1: c:='c'; 2: c:='s'; 4: c:='i'; 8: c:='q'; else internalerror(2009081502); end; if not is_signed(def) then c:=upcase(c); end; encodedstr:=encodedstr+c; end; end; pointerdef : begin if is_pchar(def) then encodedstr:=encodedstr+'*' else if (def=objc_idtype) then encodedstr:=encodedstr+'@' else if (def=objc_seltype) then encodedstr:=encodedstr+':' else if (def=objc_metaclasstype) then encodedstr:=encodedstr+'#' else begin encodedstr:=encodedstr+'^'; newstate:=recordinfostate; if (recordinfostateris_dontprint) then begin if not encoderecst(recname,tabstractrecordsymtable(trecorddef(def).symtable),encodedstr,founderror) then begin result:=false; { report the exact (nested) error defintion } exit; end end else encodedstr:=encodedstr+'{'+recname+'}' end; variantdef : begin recdef:=trecorddef(search_system_type('TVARDATA').typedef); if (recordinfostate<>ris_dontprint) then begin if not encoderecst(recdef.typename,tabstractrecordsymtable(recdef.symtable),encodedstr,founderror) then begin result:=false; { report the exact (nested) error defintion } exit; end end else encodedstr:=encodedstr+'{'+recdef.typename+'}'; end; classrefdef : begin encodedstr:=encodedstr+'^'; newstate:=recordinfostate; if (recordinfostate<>ris_dontprint) then newstate:=succ(newstate); if is_objcclassref(def) then begin objdef:=tobjectdef(tclassrefdef(def).pointeddef); if (newstate<>ris_dontprint) then { anonymous (objc)class definitions do not exist } begin if not encoderecst(objdef.objextname^,tabstractrecordsymtable(objdef.symtable),encodedstr,founderror) then { The fields of an Objective-C class should always be encodeable. } internalerror(2009081702); end else encodedstr:=encodedstr+'{'+objdef.objextname^+'}' end { Object Pascal classrefdefs point to a vmt, not really useful to completely write those here. I'm not even sure what the Objective-C run time uses this information for, since in C you can have forward struct definitions so not all structs passed to functions can be written out here either -> treat classrefdefs the same as such forward-defined structs. } else begin if assigned(def.typesym) then recname:=def.typename else recname:='?'; encodedstr:=encodedstr+'{'+recname; if (newstate<>ris_dontprint) then encodedstr:=encodedstr+'='; encodedstr:=encodedstr+'}' end; end; setdef : begin addrpara:=paramanager.push_addr_param(vs_value,def,pocall_cdecl); if not addrpara then { encode as an record, they are always passed by value in C. } encodedstr:=encodedstr+'{?='; { Encode the set itself as an array. Without an encompassing record, these are always passed by reference in C. } encodedstr:=encodedstr+'['+tostr(def.size)+'C]'; if not addrpara then encodedstr:=encodedstr+'}'; end; formaldef : begin encodedstr:=encodedstr+'^v'; end; arraydef : begin if is_array_of_const(def) then { do nothing, varargs are ignored in signatures } else if is_special_array(def) then result:=false else begin len:=tarraydef(def).highrange-tarraydef(def).lowrange+1; if is_packed_array(def) then begin { convert from bits to bytes for bitpacked arrays } len:=(len+7) div 8; { and encode as plain array of bytes } encodedstr:=encodedstr+'['+tostr(len)+'C]'; end else begin encodedstr:=encodedstr+'['+tostr(len); { Embedded structured types in the array are printed in full regardless of the current recordinfostate. } if not objcaddencodedtype(tarraydef(def).elementdef,ris_initial,false,encodedstr,founderror) then begin result:=false; { report the exact (nested) error defintion } exit; end; encodedstr:=encodedstr+']'; end; end; end; procvardef : encodedstr:=encodedstr+'^?'; objectdef : case tobjectdef(def).objecttype of odt_helper, odt_class, odt_object, odt_cppclass: begin newstate:=recordinfostate; { implicit pointer for classes } if (tobjectdef(def).objecttype in [odt_class,odt_helper]) then begin encodedstr:=encodedstr+'^'; { make all classes opaque, so even if they contain a reference-counted field there is no problem. Since a "dereferenced class" object does not exist, this should not cause problems } newstate:=ris_dontprint; end; if newstate<>ris_dontprint then begin if not encoderecst(def.typename,tabstractrecordsymtable(tobjectdef(def).symtable),encodedstr,founderror) then begin result:=false; { report the exact (nested) error defintion } exit; end end else encodedstr:=encodedstr+'{'+def.typename+'}' end; odt_interfacecom, odt_interfacecom_property, odt_interfacecom_function, odt_dispinterface: result:=false; odt_interfacecorba: encodedstr:=encodedstr+'^{'+def.typename+'=}'; { In Objective-C, the actual types of class instances are NSObject* etc, and those are encoded as "@". In FPC, to keep the similarity with Delphi-style Object Pascal, the type is NSObject and the pointer is implicit. Objective-C's "NSObject" has "class of NSObject" as equivalent here. } odt_objcclass, odt_objcprotocol: encodedstr:=encodedstr+'@'; else internalerror(2009081509); end; undefineddef, errordef : result:=false; procdef : { must be done via objcencodemethod() } internalerror(2009081511); else internalerror(2009150812); end; if not result then founderror:=def; end; function objctryencodetype(def: tdef; out encodedtype: ansistring; out founderror: tdef): boolean; begin result:=objcaddencodedtype(def,ris_initial,false,encodedtype,founderror); end; {****************************************************************** ObjC type validity checking *******************************************************************} function objcdochecktype(def: tdef; recordinfostate: trecordinfostate; out founderror: tdef): boolean; forward; function checkrecsttype(recst: tabstractrecordsymtable; recordinfostate: trecordinfostate; out founderror: tdef): boolean; var i: longint; field: tfieldvarsym; newstate: trecordinfostate; begin result:=false; newstate:=recordinfostate; { Although we never have to print the type info for nested records, check them anyway in case we're not after a pointer since if such records contain refcounted types then they can cause just as much trouble as if they were a simple refcounted field. } if (newstate=ris_afterpointer) then newstate:=ris_dontprint; for i:=0 to recst.symlist.count-1 do begin if (tsym(recst.symlist[i]).typ<>fieldvarsym) then continue; field:=tfieldvarsym(recst.symlist[i]); if not objcdochecktype(field.vardef,newstate,founderror) then exit; end; result:=true end; function objcdochecktype(def: tdef; recordinfostate: trecordinfostate; out founderror: tdef): boolean; var recdef: trecorddef; objdef: tobjectdef; newstate: trecordinfostate; begin result:=true; case def.typ of stringdef : begin case tstringdef(def).stringtype of st_shortstring: ; else { While we could handle refcounted Pascal strings correctly when such methods are called from Pascal code, things would completely break down if they were called from Objective-C code/reflection since the necessary refcount helper calls would be missing on the caller side (unless we'd automatically generate wrappers). } result:=false; end; end; enumdef, orddef : ; pointerdef : begin newstate:=recordinfostate; if (recordinfostateris_dontprint) then begin if not checkrecsttype(tabstractrecordsymtable(trecorddef(def).symtable),recordinfostate,founderror) then begin result:=false; { report the exact (nested) error defintion } exit; end end end; variantdef : begin recdef:=trecorddef(search_system_type('TVARDATA').typedef); if (recordinfostate<>ris_dontprint) then begin if not checkrecsttype(tabstractrecordsymtable(recdef.symtable),recordinfostate,founderror) then begin result:=false; { report the exact (nested) error defintion } exit; end end; end; classrefdef: begin if is_objcclassref(def) then begin objdef:=tobjectdef(tclassrefdef(def).pointeddef); newstate:=recordinfostate; if (recordinfostateris_dontprint) then begin if not checkrecsttype(tabstractrecordsymtable(objdef.symtable),recordinfostate,founderror) then begin result:=false; { report the exact (nested) error defintion } exit; end end end end; setdef, formaldef : ; arraydef : begin if is_array_of_const(def) then { ok, varargs are ignored in signatures } else if is_special_array(def) then result:=false else begin if not is_packed_array(def) then begin if not objcdochecktype(tarraydef(def).elementdef,ris_initial,founderror) then begin result:=false; { report the exact (nested) error defintion } exit; end; end; end; end; procvardef : ; objectdef : case tobjectdef(def).objecttype of odt_helper, odt_class, odt_object, odt_cppclass: begin newstate:=recordinfostate; { implicit pointer for classes } if (tobjectdef(def).objecttype in [odt_class,odt_helper]) then begin { make all classes opaque, so even if they contain a reference-counted field there is no problem. Since a "dereferenced class" object does not exist, this should not cause problems } newstate:=ris_dontprint; end; if newstate<>ris_dontprint then begin if not checkrecsttype(tabstractrecordsymtable(tobjectdef(def).symtable),newstate,founderror) then begin result:=false; { report the exact (nested) error defintion } exit; end end end; odt_interfacecom, odt_interfacecom_property, odt_interfacecom_function, odt_dispinterface: result:=false; odt_interfacecorba, odt_objcclass, odt_objcprotocol: ; else internalerror(2009081709); end; undefineddef, errordef : result:=false; procdef : result:=false; else internalerror(2009170812); end; if not result then founderror:=def; end; function objcchecktype(def: tdef; out founderror: tdef): boolean; begin result:=objcdochecktype(def,ris_initial,founderror); end; end.