Syntax sugar: Accessing members of returned objects (#808)

Conflicts:
	src/CBot/CBotInstr/CBotInstrMethode.h
dev-new-models
krzys-h 2016-08-09 20:28:37 +02:00
commit b9d4d57e33
9 changed files with 621 additions and 45 deletions

View File

@ -0,0 +1,206 @@
/*
* This file is part of the Colobot: Gold Edition source code
* Copyright (C) 2001-2016, Daniel Roux, EPSITEC SA & TerranovaTeam
* http://epsitec.ch; http://colobot.info; http://github.com/colobot
*
* 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 3 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, see http://gnu.org/licenses
*/
#include <sstream>
#include "CBot/CBotInstr/CBotExprRetVar.h"
#include "CBot/CBotInstr/CBotExpression.h"
#include "CBot/CBotInstr/CBotInstrMethode.h"
#include "CBot/CBotInstr/CBotIndexExpr.h"
#include "CBot/CBotInstr/CBotFieldExpr.h"
#include "CBot/CBotStack.h"
namespace CBot
{
////////////////////////////////////////////////////////////////////////////////
CBotExprRetVar::CBotExprRetVar()
{
}
////////////////////////////////////////////////////////////////////////////////
CBotExprRetVar::~CBotExprRetVar()
{
}
////////////////////////////////////////////////////////////////////////////////
CBotInstr* CBotExprRetVar::Compile(CBotToken*& p, CBotCStack* pStack)
{
if (p->GetType() == ID_DOT)
{
CBotVar* var = pStack->GetVar();
if (var == nullptr)
{
pStack->SetError(CBotErrNoTerminator, p->GetStart());
return nullptr;
}
CBotCStack* pStk = pStack->TokenStack();
CBotInstr* inst = new CBotExprRetVar();
while (true)
{
pStk->SetStartError(p->GetStart());
if (var->GetType() == CBotTypArrayPointer)
{
if (IsOfType( p, ID_OPBRK ))
{
CBotIndexExpr* i = new CBotIndexExpr();
i->m_expr = CBotExpression::Compile(p, pStk);
inst->AddNext3(i);
var = var->GetItem(0,true);
if (i->m_expr == nullptr || pStk->GetType() != CBotTypInt)
{
pStk->SetError(CBotErrBadIndex, p->GetStart());
goto err;
}
if (!pStk->IsOk() || !IsOfType( p, ID_CLBRK ))
{
pStk->SetError(CBotErrCloseIndex, p->GetStart());
goto err;
}
continue;
}
}
if (var->GetType(CBotVar::GetTypeMode::CLASS_AS_POINTER) == CBotTypPointer)
{
if (IsOfType(p, ID_DOT))
{
CBotToken* pp = p;
if (p->GetType() == TokenTypVar)
{
if (p->GetNext()->GetType() == ID_OPENPAR)
{
CBotInstr* i = CBotInstrMethode::Compile(p, pStk, var);
if (!pStk->IsOk()) goto err;
inst->AddNext3(i);
return pStack->Return(inst, pStk);
}
else
{
CBotFieldExpr* i = new CBotFieldExpr();
i->SetToken(pp);
inst->AddNext3(i);
var = var->GetItem(p->GetString());
if (var != nullptr)
{
i->SetUniqNum(var->GetUniqNum());
if ( var->IsPrivate() &&
!pStk->GetProgram()->m_bCompileClass)
{
pStk->SetError(CBotErrPrivate, pp);
goto err;
}
}
}
if (var != nullptr)
{
p = p->GetNext();
continue;
}
pStk->SetError(CBotErrUndefItem, p);
goto err;
}
pStk->SetError(CBotErrUndefClass, p);
goto err;
}
}
break;
}
pStk->SetCopyVar(var);
if (pStk->IsOk()) return pStack->Return(inst, pStk);
pStk->SetError(CBotErrUndefVar, p);
err:
delete inst;
return pStack->Return(nullptr, pStk);
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
bool CBotExprRetVar::Execute(CBotStack* &pj)
{
CBotStack* pile = pj->AddStack();
CBotStack* pile1 = pile;
CBotVar* pVar;
if (pile1->GetState() == 0)
{
pVar = pj->GetVar();
pVar->Update(pj->GetUserPtr());
if (pVar->GetType(CBotVar::GetTypeMode::CLASS_AS_POINTER) == CBotTypNullPointer)
{
pile1->SetError(CBotErrNull, &m_token);
return pj->Return(pile1);
}
if ( !m_next3->ExecuteVar(pVar, pile, &m_token, true, false) )
return false;
if (pVar)
pile1->SetCopyVar(pVar);
else
return pj->Return(pile1);
pile1->IncState();
}
pVar = pile1->GetVar();
if (pVar == nullptr)
{
return pj->Return(pile1);
}
if (pVar->IsUndefined())
{
pile1->SetError(CBotErrNotInit, &m_token);
return pj->Return(pile1);
}
return pj->Return(pile1);
}
////////////////////////////////////////////////////////////////////////////////
void CBotExprRetVar::RestoreState(CBotStack* &pj, bool bMain)
{
if (!bMain) return;
CBotStack* pile = pj->RestoreStack();
if ( pile == nullptr ) return;
if (pile->GetState() == 0)
m_next3->RestoreStateVar(pile, bMain);
}
std::string CBotExprRetVar::GetDebugData()
{
std::stringstream ss;
ss << m_token.GetString() << "func(...).something" << std::endl;
return ss.str();
}
} // namespace CBot

View File

@ -0,0 +1,63 @@
/*
* This file is part of the Colobot: Gold Edition source code
* Copyright (C) 2001-2016, Daniel Roux, EPSITEC SA & TerranovaTeam
* http://epsitec.ch; http://colobot.info; http://github.com/colobot
*
* 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 3 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, see http://gnu.org/licenses
*/
#pragma once
#include "CBot/CBotInstr/CBotInstr.h"
namespace CBot
{
/**
* \brief Access a member/element of the variable on the stack
*
*
*
*/
class CBotExprRetVar : public CBotInstr
{
public:
CBotExprRetVar();
~CBotExprRetVar();
static CBotInstr* Compile(CBotToken*& p, CBotCStack* pStack);
/*!
* \brief Execute
* \param pj
* \return
*/
bool Execute(CBotStack* &pj) override;
/*!
* \brief RestoreState
* \param pj
* \param bMain
*/
void RestoreState(CBotStack* &pj, bool bMain) override;
protected:
virtual const std::string GetDebugName() override { return "CBotExprRetVar"; }
virtual std::string GetDebugData() override;
private:
};
} // namespace CBot

View File

@ -69,6 +69,7 @@ private:
CBotInstr* m_expr; CBotInstr* m_expr;
friend class CBotLeftExpr; friend class CBotLeftExpr;
friend class CBotExprVar; friend class CBotExprVar;
friend class CBotExprRetVar;
}; };
} // namespace CBot } // namespace CBot

View File

@ -18,7 +18,8 @@
*/ */
#include "CBot/CBotInstr/CBotInstrCall.h" #include "CBot/CBotInstr/CBotInstrCall.h"
#include "CBot/CBotInstr/CBotExpression.h" #include "CBot/CBotInstr/CBotExprRetVar.h"
#include "CBot/CBotInstr/CBotInstrUtils.h"
#include "CBot/CBotStack.h" #include "CBot/CBotStack.h"
@ -47,62 +48,26 @@ CBotInstrCall::~CBotInstrCall()
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
CBotInstr* CBotInstrCall::Compile(CBotToken* &p, CBotCStack* pStack) CBotInstr* CBotInstrCall::Compile(CBotToken* &p, CBotCStack* pStack)
{ {
CBotVar* ppVars[1000];
int i = 0;
CBotToken* pp = p; CBotToken* pp = p;
p = p->GetNext(); p = p->GetNext();
pStack->SetStartError(p->GetStart()); if (p->GetType() == ID_OPENPAR)
CBotCStack* pile = pStack;
if ( IsOfType(p, ID_OPENPAR) )
{ {
int start, end;
CBotVar* ppVars[1000];
CBotInstrCall* inst = new CBotInstrCall(); CBotInstrCall* inst = new CBotInstrCall();
inst->SetToken(pp); inst->SetToken(pp);
// compile la list of parameters // compile la list of parameters
if (!IsOfType(p, ID_CLOSEPAR)) while (true) inst->m_parameters = CompileParams(p, pStack, ppVars);
if ( !pStack->IsOk() )
{ {
start = p->GetStart();
pile = pile->TokenStack(); // keeps the results on the stack
CBotInstr* param = CBotExpression::Compile(p, pile);
end = p->GetStart();
if (inst->m_parameters == nullptr ) inst->m_parameters = param;
else inst->m_parameters->AddNext(param); // constructs the list
if ( !pile->IsOk() )
{
delete inst;
return pStack->Return(nullptr, pile);
}
if ( param != nullptr )
{
if ( pile->GetTypResult().Eq(99) )
{
delete pStack->TokenStack();
pStack->SetError(CBotErrVoid, p->GetStart());
delete inst;
return nullptr;
}
ppVars[i] = pile->GetVar();
ppVars[i]->GetToken()->SetPos(start, end);
i++;
if (IsOfType(p, ID_COMMA)) continue; // skips the comma
if (IsOfType(p, ID_CLOSEPAR)) break;
}
pStack->SetError(CBotErrClosePar, p->GetStart());
delete pStack->TokenStack();
delete inst; delete inst;
return nullptr; return nullptr;
} }
ppVars[i] = nullptr;
// the routine is known? // the routine is known?
// CBotClass* pClass = nullptr; // CBotClass* pClass = nullptr;
@ -124,6 +89,17 @@ CBotInstr* CBotInstrCall::Compile(CBotToken* &p, CBotCStack* pStack)
} }
else pStack->SetVar(nullptr); // routine returns void else pStack->SetVar(nullptr); // routine returns void
if (nullptr != (inst->m_exprRetVar = CBotExprRetVar::Compile(p, pStack)))
{
inst->m_exprRetVar->SetToken(&inst->m_token);
delete pStack->TokenStack();
}
if ( !pStack->IsOk() )
{
delete inst;
return nullptr;
}
return inst; return inst;
} }
p = pp; p = pp;
@ -138,6 +114,17 @@ bool CBotInstrCall::Execute(CBotStack* &pj)
CBotStack* pile = pj->AddStack(this); CBotStack* pile = pj->AddStack(this);
if ( pile->StackOver() ) return pj->Return( pile ); if ( pile->StackOver() ) return pj->Return( pile );
CBotStack* pile3 = nullptr;
if (m_exprRetVar != nullptr) // func().member
{
pile3 = pile->AddStack2();
if (pile3->GetState() == 1) // function call is done?
{
if (!m_exprRetVar->Execute(pile3)) return false;
return pj->Return(pile3);
}
}
// CBotStack* pile1 = pile; // CBotStack* pile1 = pile;
int i = 0; int i = 0;
@ -165,6 +152,14 @@ bool CBotInstrCall::Execute(CBotStack* &pj)
if ( !pile2->ExecuteCall(m_nFuncIdent, GetToken(), ppVars, m_typRes)) return false; // interrupt if ( !pile2->ExecuteCall(m_nFuncIdent, GetToken(), ppVars, m_typRes)) return false; // interrupt
if (m_exprRetVar != nullptr) // func().member
{
pile3->SetCopyVar( pile2->GetVar() ); // copy the result
pile2->SetVar(nullptr);
pile3->SetState(1); // set call is done
return false; // go back to the top ^^^
}
return pj->Return(pile2); // release the entire stack return pj->Return(pile2); // release the entire stack
} }
@ -176,6 +171,16 @@ void CBotInstrCall::RestoreState(CBotStack* &pj, bool bMain)
CBotStack* pile = pj->RestoreStack(this); CBotStack* pile = pj->RestoreStack(this);
if ( pile == nullptr ) return; if ( pile == nullptr ) return;
if (m_exprRetVar != nullptr) // func().member
{
CBotStack* pile3 = pile->AddStack2();
if (pile3->GetState() == 1) // function call is done?
{
m_exprRetVar->RestoreState(pile3, bMain);
return;
}
}
// CBotStack* pile1 = pile; // CBotStack* pile1 = pile;
int i = 0; int i = 0;

View File

@ -69,6 +69,10 @@ private:
CBotTypResult m_typRes; CBotTypResult m_typRes;
//! Id of a function. //! Id of a function.
long m_nFuncIdent; long m_nFuncIdent;
//! Instruction to return a member of the returned object.
CBotInstr* m_exprRetVar;
friend class CBotDebug; friend class CBotDebug;
}; };

View File

@ -20,6 +20,7 @@
#include <sstream> #include <sstream>
#include "CBot/CBotInstr/CBotInstrMethode.h" #include "CBot/CBotInstr/CBotInstrMethode.h"
#include "CBot/CBotInstr/CBotExprRetVar.h"
#include "CBot/CBotInstr/CBotInstrUtils.h" #include "CBot/CBotInstr/CBotInstrUtils.h"
#include "CBot/CBotStack.h" #include "CBot/CBotStack.h"
@ -87,7 +88,16 @@ CBotInstr* CBotInstrMethode::Compile(CBotToken* &p, CBotCStack* pStack, CBotVar*
} }
pStack->SetVar(pResult); pStack->SetVar(pResult);
} }
return inst; else pStack->SetVar(nullptr);
if (nullptr != (inst->m_exprRetVar = CBotExprRetVar::Compile(p, pStack)))
{
inst->m_exprRetVar->SetToken(&inst->m_token);
delete pStack->TokenStack();
}
if ( pStack->IsOk() )
return inst;
} }
delete inst; delete inst;
return nullptr; return nullptr;
@ -107,6 +117,18 @@ bool CBotInstrMethode::ExecuteVar(CBotVar* &pVar, CBotStack* &pj, CBotToken* pre
return pj->Return(pile1); return pj->Return(pile1);
} }
CBotStack* pile3 = nullptr;
if (m_exprRetVar != nullptr) // .func().member
{
pile3 = pile1->AddStack2();
if (pile3->GetState() == 1)
{
if (!m_exprRetVar->Execute(pile3)) return false;
pVar = nullptr;
return pj->Return(pile3);
}
}
if (pile1->IfStep()) return false; if (pile1->IfStep()) return false;
CBotStack* pile2 = pile1->AddStack(); // for the next parameters CBotStack* pile2 = pile1->AddStack(); // for the next parameters
@ -166,6 +188,15 @@ bool CBotInstrMethode::ExecuteVar(CBotVar* &pVar, CBotStack* &pj, CBotToken* pre
pResult, pile2, GetToken())) return false; pResult, pile2, GetToken())) return false;
if (pRes != pResult) delete pRes; if (pRes != pResult) delete pRes;
if (m_exprRetVar != nullptr) // .func().member
{
pile3->SetCopyVar( pile2->GetVar() );
pile2->SetVar(nullptr);
pile3->SetState(1); // set call is done
pVar = nullptr;
return false; // go back to the top ^^^
}
pVar = nullptr; // does not return value for this pVar = nullptr; // does not return value for this
return pj->Return(pile2); // release the entire stack return pj->Return(pile2); // release the entire stack
} }
@ -179,6 +210,16 @@ void CBotInstrMethode::RestoreStateVar(CBotStack* &pile, bool bMain)
CBotStack* pile1 = pile->RestoreStack(this); // place for the copy of This CBotStack* pile1 = pile->RestoreStack(this); // place for the copy of This
if (pile1 == nullptr) return; if (pile1 == nullptr) return;
if (m_exprRetVar != nullptr) // .func().member
{
CBotStack* pile3 = pile1->AddStack2();
if (pile3->GetState() == 1) // function call is done?
{
m_exprRetVar->RestoreState(pile3, bMain);
return;
}
}
CBotStack* pile2 = pile1->RestoreStack(); // and for the parameters coming CBotStack* pile2 = pile1->RestoreStack(); // and for the parameters coming
if (pile2 == nullptr) return; if (pile2 == nullptr) return;

View File

@ -86,6 +86,9 @@ private:
//! Variable ID //! Variable ID
long m_thisIdent; long m_thisIdent;
//! Instruction to return a member of the returned object.
CBotInstr* m_exprRetVar;
}; };
} // namespace CBot } // namespace CBot

View File

@ -54,6 +54,8 @@ set(SOURCES
CBotInstr/CBotExprLitNum.h CBotInstr/CBotExprLitNum.h
CBotInstr/CBotExprLitString.cpp CBotInstr/CBotExprLitString.cpp
CBotInstr/CBotExprLitString.h CBotInstr/CBotExprLitString.h
CBotInstr/CBotExprRetVar.cpp
CBotInstr/CBotExprRetVar.h
CBotInstr/CBotExprUnaire.cpp CBotInstr/CBotExprUnaire.cpp
CBotInstr/CBotExprUnaire.h CBotInstr/CBotExprUnaire.h
CBotInstr/CBotExprVar.cpp CBotInstr/CBotExprVar.cpp

View File

@ -1465,3 +1465,254 @@ TEST_F(CBotUT, AccessMembersInParameters_Issue256)
"}\n" "}\n"
); );
} }
TEST_F(CBotUT, InstrCallAccessMemberVoid)
{
ExecuteTest(
"void Test() {}\n"
"extern void TestAccessMemberVoid() {\n"
" Test().x;\n"
"}\n",
CBotErrNoTerminator
);
}
TEST_F(CBotUT, InstrCallAccessMemberNonObject)
{
ExecuteTest(
"int GetInt() {\n"
" return 1;\n"
"}\n"
"extern void TestAccessMemberNonObject() {\n"
" GetInt().x;\n"
"}\n",
CBotErrNoTerminator
);
}
TEST_F(CBotUT, InstrCallAccessMemberObjectNull)
{
ExecuteTest(
"public class TestClass { int x = 1; }\n"
"TestClass GetObjectNull() {\n"
" TestClass t = null;"
" return t;\n"
"}\n"
"extern void TestAccessMemberObjectNull() {\n"
" GetObjectNull().x;\n"
"}\n",
CBotErrNull
);
}
TEST_F(CBotUT, InstrCallAccessMemberReturnNull)
{
ExecuteTest(
"public class TestClass { int x = 1; }\n"
"TestClass GetReturnNull() {\n"
" return null;\n"
"}\n"
"extern void TestAccessMemberReturnNull() {\n"
" GetReturnNull().x;\n"
"}\n",
CBotErrNull
);
}
TEST_F(CBotUT, InstrCallAccessMemberNotVar)
{
ExecuteTest(
"public class TestClass {}\n"
"TestClass GetObject(TestClass t) {\n"
" return t;\n"
"}\n"
"extern void TestAccessMemberNotVar() {\n"
" TestClass tc();\n"
" GetObject(tc).123;\n"
"}\n",
CBotErrUndefClass
);
}
TEST_F(CBotUT, InstrCallAccessMemberVarNonMember)
{
ExecuteTest(
"public class TestClass { int x = 1; }\n"
"TestClass GetObject(TestClass t) {\n"
" return t;\n"
"}\n"
"extern void TestAccessMemberVarNonMember() {\n"
" TestClass tc();\n"
" GetObject(tc).y;\n"
"}\n",
CBotErrUndefItem
);
}
TEST_F(CBotUT, InstrCallAccessMemberVarUndefined)
{
ExecuteTest(
"public class TestClass { int x; }\n"
"TestClass GetObject(TestClass t) {\n"
" return t;\n"
"}\n"
"extern void TestAccessMemberVarUndefined() {\n"
" TestClass tc();\n"
" GetObject(tc).x;\n"
"}\n",
CBotErrNotInit
);
}
TEST_F(CBotUT, InstrCallAccessMemberVarPrivate)
{
ExecuteTest(
"public class TestClass { private int x = 123; }\n"
"TestClass GetObject(TestClass t) {\n"
" return t;\n"
"}\n"
"extern void TestAccessMemberVarPrivate() {\n"
" TestClass tc();\n"
" ASSERT(123 == GetObject(tc).x);\n"
"}\n",
CBotErrPrivate
);
}
TEST_F(CBotUT, InstrCallAccessMemberVar)
{
ExecuteTest(
"public class TestClass { int x = 123; }\n"
"TestClass GetObject(TestClass t) {\n"
" return t;\n"
"}\n"
"extern void TestAccessMemberVar() {\n"
" TestClass tc();\n"
" ASSERT(123 == GetObject(tc).x);\n"
"}\n"
);
}
TEST_F(CBotUT, InstrCallAccessMemberVarArrayBadIndex)
{
ExecuteTest(
"public class TestClass { int[] a; }\n"
"TestClass GetObject(TestClass t) {\n"
" return t;\n"
"}\n"
"extern void TestAccessMemberVarArrayEmpty() {\n"
" TestClass tc();\n"
" int i = GetObject(tc).a[4.7];\n"
"}\n",
CBotErrBadIndex
);
}
TEST_F(CBotUT, InstrCallAccessMemberVarArrayCloseIndex)
{
ExecuteTest(
"public class TestClass { int[] a = {123}; }\n"
"TestClass GetObject(TestClass t) {\n"
" return t;\n"
"}\n"
"extern void TestAccessMemberVarArrayEmpty() {\n"
" TestClass tc();\n"
" int i = GetObject(tc).a[0;\n"
"}\n",
CBotErrCloseIndex
);
}
TEST_F(CBotUT, InstrCallAccessMemberVarArrayEmpty)
{
ExecuteTest(
"public class TestClass { int[] a; }\n"
"TestClass GetObject(TestClass t) {\n"
" return t;\n"
"}\n"
"extern void TestAccessMemberVarArrayEmpty() {\n"
" TestClass tc();\n"
" int i = GetObject(tc).a[0];\n"
"}\n",
CBotErrOutArray
);
}
TEST_F(CBotUT, InstrCallAccessMemberVarArrayOutOfRange)
{
ExecuteTest(
"public class TestClass { int a[] = {123}; }\n"
"TestClass GetObject(TestClass t) {\n"
" return t;\n"
"}\n"
"extern void TestAccessMemberVarArrayOut() {\n"
" TestClass tc();\n"
" int i = GetObject(tc).a[1];\n"
"}\n",
CBotErrOutArray
);
}
TEST_F(CBotUT, InstrCallAccessMemberVarArray)
{
ExecuteTest(
"public class TestClass { int a[] = {123}; }\n"
"TestClass GetObject(TestClass t) {\n"
" return t;\n"
"}\n"
"extern void TestAccessMemberVarArray() {\n"
" TestClass tc();\n"
" ASSERT(123 == GetObject(tc).a[0]);\n"
"}\n"
);
}
TEST_F(CBotUT, InstrCallAccessMemberMethod)
{
ExecuteTest(
"public class TestClass {\n"
" int x = 123;\n"
" int testGetX() { return x; }\n"
"}\n"
"TestClass GetObject(TestClass t) {\n"
" return t;\n"
"}\n"
"extern void TestAccessMemberMethod() {\n"
" TestClass tc();\n"
" ASSERT(123 == GetObject(tc).testGetX());\n"
"}\n"
);
}
TEST_F(CBotUT, InstrCallAccessMemberMethodChain)
{
ExecuteTest(
"public class TestClass {\n"
" int x = 123;\n"
" TestClass testGetThis() { return this; }\n"
" int testGetX() { return x; }\n"
"}\n"
"TestClass GetObject(TestClass t) {\n"
" return t;\n"
"}\n"
"extern void TestAccessMemberMethodChain() {\n"
" TestClass tc();\n"
" ASSERT(123 == GetObject(tc).testGetThis().testGetX());\n"
"}\n"
);
}
TEST_F(CBotUT, InstrCallAccessMemberNewObjectDestructor)
{
ExecuteTest(
"public class TestClass {\n"
" int x = 123;\n"
" static bool b = false;\n"
" void ~TestClass() { b = true; }\n"
"}\n"
"TestClass GetNewObject() { return new TestClass(); }\n"
"extern void TestAccessMemberNewObject() {\n"
" TestClass tc();\n"
" ASSERT(123 == GetNewObject().x);\n"
" ASSERT(tc.b == true);\n"
"}\n"
);
}