fpc/compiler/nobjc.pas
Jonas Maebe b76def10b2 + check whether selector names are valid when they are specified in a class
delcaration (instead of only for objcselector() nodes)
  * also print the wrong selector itself when it's wrong

git-svn-id: branches/objc@13689 -
2009-09-11 16:12:27 +00:00

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.