Fixes and improvements for type aliases

Replace NameDef with a new struct of runtime-resolved typedef
information.  This needs to include more than Class* or Typedef*,
because we might have nullable type aliases, or a non-nullable alias
to a nullable typedef, or vice versa.  Switch to the new
TypeAnnotation stuff in TypedefStatement instead of just strings so
support for this isn't weird (shapes are outside of this for now
though---see the hack in parser.cpp).  Also fixes support for type
aliases to mixed.
Esse commit está contido em:
Jordan DeLong
2013-06-20 17:08:48 -07:00
commit de Sara Golemon
commit 67bf707768
31 arquivos alterados com 285 adições e 177 exclusões
+19 -14
Ver Arquivo
@@ -6143,28 +6143,33 @@ void EmitterVisitor::emitClassUseTrait(PreClassEmitter* pce,
}
void EmitterVisitor::emitTypedef(Emitter& e, TypedefStatementPtr td) {
auto kind = !strcasecmp(td->value.c_str(), "array") ? KindOfArray :
!strcasecmp(td->value.c_str(), "int") ? KindOfInt64 :
!strcasecmp(td->value.c_str(), "integer") ? KindOfInt64 :
!strcasecmp(td->value.c_str(), "bool") ? KindOfBoolean :
!strcasecmp(td->value.c_str(), "boolean") ? KindOfBoolean :
!strcasecmp(td->value.c_str(), "string") ? KindOfString :
!strcasecmp(td->value.c_str(), "real") ? KindOfDouble :
!strcasecmp(td->value.c_str(), "float") ? KindOfDouble :
!strcasecmp(td->value.c_str(), "double") ? KindOfDouble :
KindOfObject;
auto const nullable = td->annot->isNullable();
auto const valueStr = td->annot->stripNullable().vanillaName();
auto const kind =
td->annot->stripNullable().isMixed() ? KindOfAny :
!strcasecmp(valueStr.c_str(), "array") ? KindOfArray :
!strcasecmp(valueStr.c_str(), "int") ? KindOfInt64 :
!strcasecmp(valueStr.c_str(), "integer") ? KindOfInt64 :
!strcasecmp(valueStr.c_str(), "bool") ? KindOfBoolean :
!strcasecmp(valueStr.c_str(), "boolean") ? KindOfBoolean :
!strcasecmp(valueStr.c_str(), "string") ? KindOfString :
!strcasecmp(valueStr.c_str(), "real") ? KindOfDouble :
!strcasecmp(valueStr.c_str(), "float") ? KindOfDouble :
!strcasecmp(valueStr.c_str(), "double") ? KindOfDouble :
KindOfObject;
// We have to merge the strings as litstrs to ensure namedentity
// creation.
auto const name = StringData::GetStaticString(td->name);
auto const value = StringData::GetStaticString(td->value);
auto const value = StringData::GetStaticString(valueStr);
m_ue.mergeLitstr(name);
m_ue.mergeLitstr(value);
Typedef record;
record.m_name = name;
record.m_value = value;
record.m_kind = kind;
record.name = name;
record.value = value;
record.kind = kind;
record.nullable = nullable;
Id id = m_ue.addTypedef(record);
e.DefTypedef(id);
}
+10 -2
Ver Arquivo
@@ -13,8 +13,10 @@
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/compiler/parser/parser.h"
#include <boost/make_shared.hpp>
#include "hphp/compiler/type_annotation.h"
#include "hphp/util/parser/hphp.tab.hpp"
#include "hphp/compiler/analysis/file_scope.h"
@@ -1635,7 +1637,13 @@ void Parser::onGoto(Token &out, Token &label, bool limited) {
}
void Parser::onTypedef(Token& out, const Token& name, const Token& type) {
auto td_stmt = NEW_STMT(TypedefStatement, name.text(), type.text());
// Note: we don't always get TypeAnnotations (e.g. for shape types
// currently).
auto const annot = type.typeAnnotation
? type.typeAnnotation
: boost::make_shared<TypeAnnotation>(type.text(), TypeAnnotationPtr());
auto td_stmt = NEW_STMT(TypedefStatement, name.text(), annot);
td_stmt->onParse(m_ar, m_file);
out->stmt = td_stmt;
}
+2 -2
Ver Arquivo
@@ -24,10 +24,10 @@ namespace HPHP {
TypedefStatement::TypedefStatement(
STATEMENT_CONSTRUCTOR_PARAMETERS,
const std::string& name,
const std::string& value)
const TypeAnnotationPtr& annot)
: Statement(STATEMENT_CONSTRUCTOR_PARAMETER_VALUES(TypedefStatement))
, name(name)
, value(value)
, annot(annot)
{}
TypedefStatement::~TypedefStatement() {}
+3 -2
Ver Arquivo
@@ -21,6 +21,7 @@
#include <string>
#include "hphp/compiler/statement/statement.h"
#include "hphp/compiler/type_annotation.h"
namespace HPHP {
//////////////////////////////////////////////////////////////////////
@@ -28,7 +29,7 @@ namespace HPHP {
struct TypedefStatement : Statement, IParseHandler {
explicit TypedefStatement(STATEMENT_CONSTRUCTOR_PARAMETERS,
const std::string& name,
const std::string& value);
const TypeAnnotationPtr& typeAnnotation);
~TypedefStatement();
DECLARE_STATEMENT_VIRTUAL_FUNCTIONS;
@@ -38,7 +39,7 @@ public: // IParseHandler
public:
const std::string name;
const std::string value;
const TypeAnnotationPtr annot;
};
typedef boost::shared_ptr<TypedefStatement> TypedefStatementPtr;
+5
Ver Arquivo
@@ -90,6 +90,11 @@ public:
return ret;
}
/*
* Return whether this TypeAnnotation is equal to the "mixed" type.
*/
bool isMixed() const { return !m_name.compare("mixed"); }
/*
* Returns whether this TypeAnnotation is "simple"---as described
* above, this implies it has only one level of depth. Both the
+1 -1
Ver Arquivo
@@ -1095,7 +1095,7 @@ bool AutoloadHandler::autoloadType(CStrRef name) {
return !m_map.isNull() &&
loadFromMap(name, s_type, true,
[] (CStrRef name) {
return !!Unit::GetNamedEntity(name.get())->getCachedNameDef();
return Unit::GetNamedEntity(name.get())->getCachedTypedef() != nullptr;
}
) != Failure;
}
+11 -5
Ver Arquivo
@@ -731,12 +731,18 @@ CacheHandle allocFixedFunction(const StringData* name) {
return allocFixedFunction(Unit::GetNamedEntity(name), false);
}
CacheHandle allocNameDef(const NamedEntity* ne) {
if (ne->m_cachedNameDefOffset) {
return ne->m_cachedNameDefOffset;
CacheHandle allocTypedef(const NamedEntity* ne) {
if (ne->m_cachedTypedefOffset) {
return ne->m_cachedTypedefOffset;
}
return allocFuncOrClass(&ne->m_cachedNameDefOffset,
false /* persistent */); // TODO(#2103214): support persistent
Lock l(s_handleMutex);
// TODO(#2103214): support persistent
const_cast<NamedEntity*>(ne)->m_cachedTypedefOffset =
allocLocked(false /* persistent */,
sizeof(TypedefReq),
alignof(TypedefReq));
return ne->m_cachedTypedefOffset;
}
template<bool checkOnly>
+5 -6
Ver Arquivo
@@ -342,14 +342,13 @@ CacheHandle allocFixedFunction(const NamedEntity* ne, bool persistent);
CacheHandle allocFixedFunction(const StringData* name);
/*
* NameDefs.
* Type aliases.
*
* Request-private values for typedef names. When a typedef is
* defined, a NameDef for it is cached here. If it is a typedef for a
* class, the NameDef will be for the target class. Otherwise it's a
* NameDef pointing to the Typedef*.
* Request-private values for type aliases (typedefs). When a typedef
* is defined, the entry for it is cached. This reserves enough space
* for a TypedefReq struct.
*/
CacheHandle allocNameDef(const NamedEntity* name);
CacheHandle allocTypedef(const NamedEntity* name);
/*
* Constants.
+10 -4
Ver Arquivo
@@ -285,16 +285,22 @@ void VerifyParamTypeSlow(const Class* cls,
// object, so if it's a typedef for something non-objecty we're
// failing anyway.
if (auto namedEntity = expected->namedEntity()) {
NameDef def = namedEntity->getCachedNameDef();
auto def = namedEntity->getCachedTypedef();
if (UNLIKELY(!def)) {
VMRegAnchor _;
String nameStr(const_cast<StringData*>(expected->typeName()));
if (AutoloadHandler::s_instance->autoloadType(nameStr)) {
def = namedEntity->getCachedNameDef();
def = namedEntity->getCachedTypedef();
}
}
if (def && (constraint = def.asClass()) && cls->classof(constraint)) {
return;
if (def) {
// There's no need to handle nullable typedefs specially here:
// we already know we're checking a non-null object with the
// class `cls'.
if (def->kind == KindOfObject) {
constraint = def->klass;
if (constraint && cls->classof(constraint)) return;
}
}
}
+22 -43
Ver Arquivo
@@ -28,60 +28,29 @@
namespace HPHP {
class Class;
class Typedef;
struct Typedef;
struct TypedefReq;
class Func;
//////////////////////////////////////////////////////////////////////
/*
* A NameDef is a definition of a type for a given name. This
* currently means it is either a Class*, a Typedef*, or null.
*/
struct NameDef : private boost::equality_comparable<NameDef> {
explicit NameDef() : m_p(0) {}
explicit NameDef(Class* c) : m_p(reinterpret_cast<uintptr_t>(c)) {
assert(!(m_p & 0x1));
}
explicit NameDef(Typedef* t)
: m_p(reinterpret_cast<uintptr_t>(t) | 0x1)
{}
Class* asClass() const {
return m_p & 0x1 ? nullptr : reinterpret_cast<Class*>(m_p);
}
Typedef* asTypedef() const {
return m_p & 0x1 ? reinterpret_cast<Typedef*>(m_p - 1) : nullptr;
}
explicit operator bool() const { return !!m_p; }
bool operator==(NameDef other) const {
return m_p == other.m_p;
}
private:
uintptr_t m_p;
};
/*
* NamedEntity represents a user-defined name that may map to
* different objects (NameDefs) in different requests. Classes and
* functions are in separate namespaces, so we have a targetcache
* offset for resolving each.
* different objects in different requests. Classes and functions are
* in separate namespaces, so we have a targetcache offset for
* resolving each.
*
* Classes and typedefs are in the same namespace when we're naming
* types, but different namespaces at sites that allocate classes. If
* a typedef is defined for a given name, we'll cache a NameDef in
* each request at m_cachedNameDefOffset. Classes are always cached
* at m_cachedClassOffset. See TargetCache::allocNameDef.
* a typedef is defined for a given name, we'll cache it in each
* request at m_cachedTypedefOffset. Classes are always cached at
* m_cachedClassOffset.
*/
struct NamedEntity {
explicit NamedEntity()
: m_cachedClassOffset(0)
, m_cachedFuncOffset(0)
, m_cachedNameDefOffset(0)
, m_cachedTypedefOffset(0)
, m_clsList(nullptr)
{}
@@ -89,7 +58,7 @@ struct NamedEntity {
// read them without locks.
unsigned m_cachedClassOffset;
unsigned m_cachedFuncOffset;
unsigned m_cachedNameDefOffset;
unsigned m_cachedTypedefOffset;
void setCachedFunc(Func *f);
Func* getCachedFunc() const;
@@ -97,8 +66,8 @@ struct NamedEntity {
void setCachedClass(Class* c);
Class* getCachedClass() const;
NameDef getCachedNameDef() const;
void setCachedNameDef(NameDef);
const TypedefReq* getCachedTypedef() const;
void setCachedTypedef(const TypedefReq&);
Class* clsList() const {
return m_clsList;
@@ -115,6 +84,16 @@ private:
//////////////////////////////////////////////////////////////////////
/*
* Lookup a Typedef* for the supplied NamedEntity (which must be the
* NamedEntity for `name'), if necessary invoking autoload for types
* but not classes.
*/
const TypedefReq* getTypedefWithAutoload(const NamedEntity* ne,
const StringData* name);
//////////////////////////////////////////////////////////////////////
struct ahm_string_data_isame {
bool operator()(const StringData *s1, const StringData *s2) const {
// ahm uses -1, -2, -3 as magic values
+14 -13
Ver Arquivo
@@ -128,15 +128,15 @@ void TypeConstraint::init() {
* autoload typedefs because they can affect whether the
* VerifyParamType would succeed.
*/
static NameDef getNameDefWithAutoload(const NamedEntity* ne,
const StringData* name) {
NameDef def = ne->getCachedNameDef();
const TypedefReq* getTypedefWithAutoload(const NamedEntity* ne,
const StringData* name) {
auto def = ne->getCachedTypedef();
if (!def) {
String nameStr(const_cast<StringData*>(name));
if (!AutoloadHandler::s_instance->autoloadType(nameStr)) {
return NameDef();
return nullptr;
}
def = ne->getCachedNameDef();
def = ne->getCachedTypedef();
}
return def;
}
@@ -145,20 +145,21 @@ bool TypeConstraint::checkTypedefNonObj(const TypedValue* tv) const {
assert(tv->m_type != KindOfObject); // this checks when tv is not an object
assert(!isSelf() && !isParent());
NameDef def = getNameDefWithAutoload(m_namedEntity, m_typeName);
if (!def) return false;
Typedef* td = def.asTypedef();
return td && equivDataTypes(td->m_kind, tv->m_type);
auto const td = getTypedefWithAutoload(m_namedEntity, m_typeName);
if (!td) return false;
if (td->nullable && IS_NULL_TYPE(tv->m_type)) return true;
return td->kind == KindOfAny || equivDataTypes(td->kind, tv->m_type);
}
bool TypeConstraint::checkTypedefObj(const TypedValue* tv) const {
assert(tv->m_type == KindOfObject); // this checks when tv is an object
assert(!isSelf() && !isParent() && !isCallable());
NameDef def = getNameDefWithAutoload(m_namedEntity, m_typeName);
if (!def) return false;
Class* c = def.asClass();
return c && tv->m_data.pobj->instanceof(c);
auto const td = getTypedefWithAutoload(m_namedEntity, m_typeName);
if (!td) return false;
if (td->nullable && IS_NULL_TYPE(tv->m_type)) return true;
if (td->kind != KindOfObject) return false;
return td->klass && tv->m_data.pobj->instanceof(td->klass);
}
bool
+63 -74
Ver Arquivo
@@ -145,16 +145,22 @@ Class* NamedEntity::getCachedClass() const {
return nullptr;
}
void NamedEntity::setCachedNameDef(NameDef nd) {
assert(m_cachedNameDefOffset);
Transl::TargetCache::handleToRef<NameDef>(m_cachedNameDefOffset) = nd;
void NamedEntity::setCachedTypedef(const TypedefReq& td) {
assert(m_cachedTypedefOffset);
auto& tdReq = Transl::TargetCache::handleToRef<TypedefReq>(
m_cachedTypedefOffset
);
tdReq = td;
}
NameDef NamedEntity::getCachedNameDef() const {
if (LIKELY(m_cachedNameDefOffset != 0)) {
return Transl::TargetCache::handleToRef<NameDef>(m_cachedNameDefOffset);
const TypedefReq* NamedEntity::getCachedTypedef() const {
if (LIKELY(m_cachedTypedefOffset != 0)) {
auto ret = &Transl::TargetCache::handleToRef<const TypedefReq>(
m_cachedTypedefOffset
);
return ret->name ? ret : nullptr;
}
return NameDef();
return nullptr;
}
void NamedEntity::pushClass(Class* cls) {
@@ -527,20 +533,15 @@ Class* Unit::defClass(const PreClass* preClass,
* Raise a fatal unless the existing class definition is identical to the
* one this invocation would create.
*/
if (NameDef current = nameList->getCachedNameDef()) {
auto name = current.asTypedef()
? current.asTypedef()->m_name
: current.asClass()->name();
if (auto current = nameList->getCachedTypedef()) {
FrameRestore fr(preClass);
raise_error("Cannot declare class with the same name (%s) as an "
"existing type", name->data());
"existing type", current->name->data());
return nullptr;
}
// If it's compatible, the class must have been declared as a
// DefClass, not a typedef. So we don't need to check the NameDef
// for a class, only the cached class offset.
// If there was already a class declared with DefClass, check if
// it's compatible.
if (Class* cls = nameList->getCachedClass()) {
if (cls->preClass() != preClass) {
if (failIsFatal) {
@@ -651,33 +652,19 @@ bool Unit::aliasClass(Class* original, const StringData* alias) {
void Unit::defTypedef(Id id) {
assert(id < m_typedefs.size());
auto thisType = &m_typedefs[id];
auto nameList = GetNamedEntity(thisType->m_name);
const StringData* typeName = thisType->m_value;
auto checkExistingClass = [&] (Class* cls) {
if (thisType->m_kind != KindOfObject ||
!cls->name()->isame(typeName)) {
raise_error("The type %s is already defined to a different class (%s)",
thisType->m_name->data(),
cls->name()->data());
}
};
auto nameList = GetNamedEntity(thisType->name);
const StringData* typeName = thisType->value;
/*
* Check if this name already has a NameDef, and if so make sure it
* is compatible.
* Check if this name already was defined as a type alias, and if so
* make sure it is compatible.
*/
if (NameDef current = nameList->getCachedNameDef()) {
if (Class* cls = current.asClass()) {
checkExistingClass(cls);
return;
}
Typedef* td = current.asTypedef();
assert(td);
if (thisType->m_kind != td->m_kind ||
!td->m_value->isame(typeName)) {
if (auto current = nameList->getCachedTypedef()) {
if (thisType->kind != current->kind ||
thisType->nullable != current->nullable ||
Unit::lookupClass(typeName) != current->klass) {
raise_error("The type %s is already defined to an incompatible type",
thisType->m_name->data());
thisType->name->data());
}
return;
}
@@ -685,56 +672,58 @@ void Unit::defTypedef(Id id) {
// There might also be a class with this name already.
if (Class* cls = nameList->getCachedClass()) {
raise_error("The name %s is already defined as a class",
thisType->m_name->data());
thisType->name->data());
return;
}
if (!nameList->m_cachedNameDefOffset) {
nameList->m_cachedNameDefOffset =
Transl::TargetCache::allocNameDef(nameList);
if (!nameList->m_cachedTypedefOffset) {
nameList->m_cachedTypedefOffset =
Transl::TargetCache::allocTypedef(nameList);
}
/*
* The cached NameDef for this typedef will be the actual Class* if
* it is a typedef for a class type, otherwise it is a pointer to a
* Typedef structure.
*
* If this typedef is a KindOfObject and the name on the right hand
* side was another typedef, we will bind the name to the other side
* for this request. We need to inspect the right hand side and
* figure out what it was first.
* for this request (i.e. resolve that typedef now).
*
* We need to inspect the right hand side and figure out what it was
* first.
*
* If the right hand side was a class, we need to autoload and
* ensure it exists at this point.
*/
if (thisType->m_kind != KindOfObject) {
nameList->setCachedNameDef(NameDef(thisType));
return;
}
if (auto klass = Unit::loadClass(typeName)) {
nameList->setCachedNameDef(NameDef(klass));
if (thisType->kind != KindOfObject) {
nameList->setCachedTypedef(
TypedefReq { thisType->kind,
thisType->nullable,
nullptr,
thisType->name }
);
return;
}
auto targetNameList = GetNamedEntity(typeName);
NameDef target = targetNameList->getCachedNameDef();
if (!target) {
String normName = normalizeNS(typeName);
if (normName) {
typeName = normName.get();
targetNameList = GetNamedEntity(typeName);
target = targetNameList->getCachedNameDef();
}
if (!target) {
AutoloadHandler::s_instance->autoloadType(typeName->data());
target = targetNameList->getCachedNameDef();
if (!target) {
raise_error("Unknown type or class %s", typeName->data());
return;
}
}
if (auto targetTd = getTypedefWithAutoload(targetNameList, typeName)) {
nameList->setCachedTypedef(
TypedefReq { targetTd->kind,
thisType->nullable || targetTd->nullable,
targetTd->klass,
thisType->name }
);
return;
}
assert(target);
nameList->setCachedNameDef(target);
if (auto klass = Unit::loadClass(typeName)) {
nameList->setCachedTypedef(
TypedefReq { KindOfObject,
thisType->nullable,
klass,
thisType->name }
);
return;
}
raise_error("Unknown type or class %s", typeName->data());
}
void Unit::renameFunc(const StringData* oldName, const StringData* newName) {
+22 -6
Ver Arquivo
@@ -246,20 +246,36 @@ typedef std::vector<FuncEntry> FuncTable;
* just a name. At runtime we still might resolve this name to
* another typedef, becoming a typedef for KindOfArray or something in
* that request.
*
* For the per-request struct, see TypedefReq below.
*/
struct Typedef {
const StringData* m_name;
const StringData* m_value;
DataType m_kind;
const StringData* name;
const StringData* value;
DataType kind;
bool nullable; // Null is allowed; for ?Foo typedefs
template<class SerDe> void serde(SerDe& sd) {
sd(m_name)
(m_value)
(m_kind)
sd(name)
(value)
(kind)
(nullable)
;
}
};
/*
* In a given request, a defined typedef is turned into a TypedefReq
* struct. This contains the information needed to validate parameter
* type hints for a typedef at runtime.
*/
struct TypedefReq {
DataType kind; // may be KindOfAny for "mixed"
bool nullable; // for option types, like ?Foo
Class* klass; // nullptr if kind != KindOfObject
const StringData* name; // needed for error messages; nullptr if not defined
};
//==============================================================================
// (const StringData*) versus (StringData*)
//
@@ -0,0 +1,4 @@
<?hh
// non newtypes can't have constraints
type Foo as int = int; // error
@@ -1 +1 @@
HipHop Fatal error: syntax error, unexpected T_AS, expecting '=' in %s on line 3
HipHop Fatal error: syntax error, unexpected T_AS, expecting '=' in %s on line 4
-3
Ver Arquivo
@@ -1,3 +0,0 @@
<?hh
type Foo as int = int;
+1 -1
Ver Arquivo
@@ -1 +1 @@
HipHop Fatal error: The type MyType is already defined to a different class (MyClass) in %s on line 10
HipHop Fatal error: The type MyType is already defined to an incompatible type in %s on line 10
+10
Ver Arquivo
@@ -0,0 +1,10 @@
<?php
type Foo = mixed;
function foo(Foo $x) {
echo $x;
echo "\n";
}
foo(12);
+1
Ver Arquivo
@@ -0,0 +1 @@
12
+14
Ver Arquivo
@@ -0,0 +1,14 @@
<?hh
type foo = ?int;
function bar(foo $k) { var_dump($k); }
bar(12);
bar(null);
bar(42);
class something {}
type blah = ?something;
function bar2(blah $k) { var_dump($k); }
bar2(new something());
bar2(null);
bar2(new something());
+8
Ver Arquivo
@@ -0,0 +1,8 @@
int(12)
NULL
int(42)
object(something)#1 (0) {
}
NULL
object(something)#1 (0) {
}
+12
Ver Arquivo
@@ -0,0 +1,12 @@
<?hh
type foo = int;
type bar = ?foo;
function wat(bar $x) {
var_dump($x);
}
wat(null);
wat(2);
wat("fail"); // ends test
@@ -0,0 +1,3 @@
NULL
int(2)
HipHop Fatal error: Argument 1 passed to wat() must be an instance of bar, string given in %s on line 8
+12
Ver Arquivo
@@ -0,0 +1,12 @@
<?hh
type foo = ?int;
type bar = foo;
function wat(bar $x) {
var_dump($x);
}
wat(null);
wat(2);
wat("fail"); // ends test
@@ -0,0 +1,3 @@
NULL
int(2)
HipHop Fatal error: Argument 1 passed to wat() must be an instance of bar, string given in %s on line 8
+12
Ver Arquivo
@@ -0,0 +1,12 @@
<?hh
type one = ?int;
type two = ?int;
function yeah(two $x) {
var_dump($x);
}
yeah(12);
yeah(null);
yeah("fail"); // ends test
@@ -0,0 +1,3 @@
int(12)
NULL
HipHop Fatal error: Argument 1 passed to yeah() must be an instance of two, string given in %s on line 8
+6
Ver Arquivo
@@ -0,0 +1,6 @@
<?hh
type foo = ?int;
function bar(foo $k) {}
bar("fail");
@@ -0,0 +1 @@
HipHop Fatal error: Argument 1 passed to bar() must be an instance of foo, string given in %s on line 4
+6
Ver Arquivo
@@ -0,0 +1,6 @@
<?hh
class something {}
type blah = ?something;
function bar2(blah $k) {}
bar2("fail");
@@ -0,0 +1 @@
HipHop Fatal error: Argument 1 passed to bar2() must be an instance of blah, string given in %s on line 5