mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-16 19:59:26 +02:00

delcaration (instead of only for objcselector() nodes) * also print the wrong selector itself when it's wrong git-svn-id: branches/objc@13689 -
357 lines
12 KiB
ObjectPascal
357 lines
12 KiB
ObjectPascal
{
|
|
Copyright (c) 2009 by Jonas Maebe
|
|
|
|
This unit implements Objective-C nodes
|
|
|
|
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.
|
|
|
|
****************************************************************************
|
|
}
|
|
{ @abstract(This unit implements Objective-C nodes)
|
|
This unit contains various nodes to implement Objective-Pascal and to
|
|
interface with the Objective-C runtime.
|
|
}
|
|
|
|
unit nobjc;
|
|
|
|
{$i fpcdefs.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
node;
|
|
|
|
type
|
|
tobjcselectornode = class(tunarynode)
|
|
public
|
|
constructor create(formethod: tnode);
|
|
function pass_typecheck: tnode;override;
|
|
function pass_1: tnode;override;
|
|
end;
|
|
tobjcselectornodeclass = class of tobjcselectornode;
|
|
|
|
tobjcprotocolnode = class(tunarynode)
|
|
public
|
|
constructor create(forprotocol: tnode);
|
|
function pass_typecheck: tnode;override;
|
|
function pass_1: tnode;override;
|
|
end;
|
|
tobjcprotocolnodeclass = class of tobjcprotocolnode;
|
|
|
|
tobjcmessagesendnode = class(tunarynode)
|
|
public
|
|
constructor create(forcall: tnode);
|
|
function pass_typecheck: tnode;override;
|
|
function pass_1: tnode;override;
|
|
end;
|
|
tobjcmessagesendnodeclass = class of tobjcmessagesendnode;
|
|
|
|
var
|
|
cobjcselectornode : tobjcselectornodeclass;
|
|
cobjcmessagesendnode : tobjcmessagesendnodeclass;
|
|
cobjcprotocolnode : tobjcprotocolnodeclass;
|
|
|
|
implementation
|
|
|
|
uses
|
|
sysutils,
|
|
globtype,cclasses,
|
|
verbose,pass_1,
|
|
defutil,
|
|
symtype,symtable,symdef,symconst,symsym,
|
|
paramgr,
|
|
nutils,
|
|
nbas,nld,ncnv,ncon,ncal,nmem,
|
|
objcutil,
|
|
cgbase;
|
|
|
|
|
|
{*****************************************************************************
|
|
TOBJCSELECTORNODE
|
|
*****************************************************************************}
|
|
|
|
constructor tobjcselectornode.create(formethod: tnode);
|
|
begin
|
|
inherited create(objcselectorn,formethod);
|
|
end;
|
|
|
|
|
|
function tobjcselectornode.pass_typecheck: tnode;
|
|
var
|
|
len: longint;
|
|
s: shortstring;
|
|
begin
|
|
result:=nil;
|
|
typecheckpass(left);
|
|
{ argument can be
|
|
a) an objc method
|
|
b) a pchar, zero-based chararray or ansistring
|
|
}
|
|
case left.nodetype of
|
|
loadn:
|
|
begin
|
|
if (left.resultdef.typ=procdef) and
|
|
(po_objc in tprocdef(left.resultdef).procoptions) then
|
|
begin
|
|
{ ok }
|
|
end
|
|
else
|
|
CGMessage1(type_e_expected_objc_method_but_got,left.resultdef.typename);
|
|
end;
|
|
stringconstn:
|
|
begin
|
|
if not objcvalidselectorname(tstringconstnode(left).value_str,
|
|
tstringconstnode(left).len) then
|
|
begin
|
|
len:=tstringconstnode(left).len;
|
|
if (len>255) then
|
|
len:=255;
|
|
setlength(s,len);
|
|
move(tstringconstnode(left).value_str^,s[1],len);
|
|
CGMessage1(type_e_invalid_objc_selector_name,s);
|
|
exit;
|
|
end;
|
|
end
|
|
else
|
|
CGMessage(type_e_expected_objc_method);
|
|
end;
|
|
resultdef:=objc_seltype;
|
|
end;
|
|
|
|
|
|
function tobjcselectornode.pass_1: tnode;
|
|
begin
|
|
result:=nil;
|
|
expectloc:=LOC_CREFERENCE;
|
|
end;
|
|
|
|
|
|
{*****************************************************************************
|
|
TOBJPROTOCOLNODE
|
|
*****************************************************************************}
|
|
|
|
constructor tobjcprotocolnode.create(forprotocol: tnode);
|
|
begin
|
|
inherited create(objcprotocoln,forprotocol);
|
|
end;
|
|
|
|
|
|
function tobjcprotocolnode.pass_typecheck: tnode;
|
|
begin
|
|
result:=nil;
|
|
typecheckpass(left);
|
|
if (left.nodetype<>typen) then
|
|
MessagePos(left.fileinfo,type_e_type_id_expected)
|
|
else if not is_objcprotocol(left.resultdef) then
|
|
MessagePos2(left.fileinfo,type_e_incompatible_types,left.resultdef.typename,'ObjCProtocol');
|
|
resultdef:=objc_protocoltype;
|
|
end;
|
|
|
|
|
|
function tobjcprotocolnode.pass_1: tnode;
|
|
begin
|
|
result:=nil;
|
|
expectloc:=LOC_CREFERENCE;
|
|
end;
|
|
|
|
|
|
{*****************************************************************************
|
|
TOBJCMESSAGESENDNODE
|
|
*****************************************************************************}
|
|
|
|
constructor tobjcmessagesendnode.create(forcall: tnode);
|
|
begin
|
|
if (forcall.nodetype<>calln) then
|
|
internalerror(2009032502);
|
|
{ typecheck pass (and pass1) must already have run on the call node,
|
|
because pass1 of the callnode creates this node
|
|
}
|
|
inherited create(objcmessagesendn,forcall);
|
|
end;
|
|
|
|
|
|
function tobjcmessagesendnode.pass_typecheck: tnode;
|
|
begin
|
|
{ typecheckpass of left has already run, see constructor }
|
|
resultdef:=left.resultdef;
|
|
result:=nil;
|
|
expectloc:=left.expectloc;
|
|
end;
|
|
|
|
|
|
function tobjcmessagesendnode.pass_1: tnode;
|
|
var
|
|
msgsendname: string;
|
|
newparas,
|
|
para: tcallparanode;
|
|
block,
|
|
selftree : tnode;
|
|
statements: tstatementnode;
|
|
temp: ttempcreatenode;
|
|
objcsupertype: tdef;
|
|
field: tfieldvarsym;
|
|
selfpara,
|
|
msgselpara: tcallparanode;
|
|
begin
|
|
{ pass1 of left has already run, see constructor }
|
|
|
|
{ default behaviour: call objc_msgSend and friends;
|
|
ppc64 and x86_64 for Mac OS X have to override this as they
|
|
call messages via an indirect function call similar to
|
|
dynamically linked functions, ARM maybe as well (not checked)
|
|
|
|
Which variant of objc_msgSend is used depends on the
|
|
result type, and on whether or not it's an inherited call.
|
|
}
|
|
|
|
{ record returned via implicit pointer }
|
|
if paramanager.ret_in_param(left.resultdef,tcallnode(left).procdefinition.proccalloption) then
|
|
if not(cnf_inherited in tcallnode(left).callnodeflags) then
|
|
msgsendname:='OBJC_MSGSEND_STRET'
|
|
else
|
|
msgsendname:='OBJC_MSGSENDSUPER_STRET'
|
|
{$ifdef i386}
|
|
{ special case for fpu results on i386 for non-inherited calls }
|
|
else if (left.resultdef.typ=floatdef) and
|
|
not(cnf_inherited in tcallnode(left).callnodeflags) then
|
|
msgsendname:='OBJC_MSGSEND_FPRET'
|
|
{$endif}
|
|
{ default }
|
|
else if not(cnf_inherited in tcallnode(left).callnodeflags) then
|
|
msgsendname:='OBJC_MSGSEND'
|
|
else
|
|
msgsendname:='OBJC_MSGSENDSUPER';
|
|
|
|
newparas:=tcallparanode(tcallnode(left).left);
|
|
{ Find the self and msgsel parameters. }
|
|
para:=newparas;
|
|
selfpara:=nil;
|
|
msgselpara:=nil;
|
|
while assigned(para) do
|
|
begin
|
|
if (vo_is_self in para.parasym.varoptions) then
|
|
selfpara:=para
|
|
else if (vo_is_msgsel in para.parasym.varoptions) then
|
|
msgselpara:=para;
|
|
para:=tcallparanode(para.right);
|
|
end;
|
|
if not assigned(selfpara) then
|
|
internalerror(2009051801);
|
|
if not assigned(msgselpara) then
|
|
internalerror(2009051802);
|
|
{ Handle self }
|
|
{ 1) in case of sending a message to a superclass, self is a pointer to
|
|
an objc_super record
|
|
}
|
|
if (cnf_inherited in tcallnode(left).callnodeflags) then
|
|
begin
|
|
block:=internalstatements(statements);
|
|
objcsupertype:=search_named_unit_globaltype('OBJC1','OBJC_SUPER').typedef;
|
|
if (objcsupertype.typ<>recorddef) then
|
|
internalerror(2009032901);
|
|
{ temp for the for the objc_super record }
|
|
temp:=ctempcreatenode.create(objcsupertype,objcsupertype.size,tt_persistent,false);
|
|
addstatement(statements,temp);
|
|
{ initialize objc_super record }
|
|
selftree:=load_self_node;
|
|
|
|
{ we can call an inherited class static/method from a regular method
|
|
-> self node must change from instance pointer to vmt pointer)
|
|
}
|
|
if (po_classmethod in tcallnode(left).procdefinition.procoptions) and
|
|
(selftree.resultdef.typ<>classrefdef) then
|
|
begin
|
|
selftree:=cloadvmtaddrnode.create(selftree);
|
|
typecheckpass(selftree);
|
|
end;
|
|
field:=tfieldvarsym(trecorddef(objcsupertype).symtable.find('RECEIVER'));
|
|
if not assigned(field) then
|
|
internalerror(2009032902);
|
|
{ first the destination object/class instance }
|
|
addstatement(statements,
|
|
cassignmentnode.create(
|
|
csubscriptnode.create(field,ctemprefnode.create(temp)),
|
|
selftree
|
|
)
|
|
);
|
|
{ and secondly, the class type in which the selector must be looked
|
|
up (the parent class in case of an instance method, the parent's
|
|
metaclass in case of a class method) }
|
|
field:=tfieldvarsym(trecorddef(objcsupertype).symtable.find('_CLASS'));
|
|
if not assigned(field) then
|
|
internalerror(2009032903);
|
|
addstatement(statements,
|
|
cassignmentnode.create(
|
|
csubscriptnode.create(field,ctemprefnode.create(temp)),
|
|
objcsuperclassnode(selftree.resultdef)
|
|
)
|
|
);
|
|
{ result of this block is the address of this temp }
|
|
addstatement(statements,caddrnode.create_internal(ctemprefnode.create(temp)));
|
|
{ replace the method pointer with the address of this temp }
|
|
tcallnode(left).methodpointer.free;
|
|
tcallnode(left).methodpointer:=block;
|
|
typecheckpass(block);
|
|
end
|
|
else
|
|
{ 2) regular call (not inherited) }
|
|
begin
|
|
{ a) If we're calling a class method, use a class ref. }
|
|
if (po_classmethod in tcallnode(left).procdefinition.procoptions) and
|
|
((tcallnode(left).methodpointer.nodetype=typen) or
|
|
(tcallnode(left).methodpointer.resultdef.typ<>classrefdef)) then
|
|
begin
|
|
tcallnode(left).methodpointer:=cloadvmtaddrnode.create(tcallnode(left).methodpointer);
|
|
firstpass(tcallnode(left).methodpointer);
|
|
end;
|
|
{ b) convert methodpointer parameter to match objc_MsgSend* signatures }
|
|
inserttypeconv_internal(tcallnode(left).methodpointer,objc_idtype);
|
|
end;
|
|
{ replace self parameter }
|
|
selfpara.left.free;
|
|
selfpara.left:=tcallnode(left).methodpointer;
|
|
{ replace selector parameter }
|
|
msgselpara.left.Free;
|
|
msgselpara.left:=
|
|
cobjcselectornode.create(
|
|
cstringconstnode.createstr(tprocdef(tcallnode(left).procdefinition).messageinf.str^)
|
|
);
|
|
{ parameters are reused -> make sure they don't get freed }
|
|
tcallnode(left).left:=nil;
|
|
{ methodpointer is also reused }
|
|
tcallnode(left).methodpointer:=nil;
|
|
{ and now the call to the Objective-C rtl }
|
|
result:=ccallnode.createinternresfromunit('OBJC1',msgsendname,newparas,left.resultdef);
|
|
|
|
if (cnf_inherited in tcallnode(left).callnodeflags) then
|
|
begin
|
|
{ free the objc_super temp after the call. We cannout use
|
|
ctempdeletenode.create_normal_temp before the call, because then
|
|
the temp will be released while evaluating the parameters, and thus
|
|
may be reused while evaluating another parameter
|
|
}
|
|
block:=internalstatements(statements);
|
|
addstatement(statements,result);
|
|
addstatement(statements,ctempdeletenode.create(temp));
|
|
typecheckpass(block);
|
|
result:=block;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
cobjcmessagesendnode:=tobjcmessagesendnode;
|
|
end.
|
|
|