2add534031
Let's move the type inference for generators from function scope to function call, so that the original infered type is kept in func scope and can be used later (will be useful especially for async functions lates). Differential Revision: D913970
593 linhas
20 KiB
C++
593 linhas
20 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "hphp/compiler/expression/function_call.h"
|
|
#include "hphp/util/util.h"
|
|
#include "hphp/util/logger.h"
|
|
#include "hphp/compiler/expression/scalar_expression.h"
|
|
#include "hphp/compiler/analysis/code_error.h"
|
|
#include "hphp/compiler/analysis/function_scope.h"
|
|
#include "hphp/compiler/analysis/file_scope.h"
|
|
#include "hphp/compiler/analysis/variable_table.h"
|
|
#include "hphp/compiler/statement/statement.h"
|
|
#include "hphp/compiler/statement/method_statement.h"
|
|
#include "hphp/compiler/statement/exp_statement.h"
|
|
#include "hphp/compiler/statement/return_statement.h"
|
|
#include "hphp/compiler/statement/statement_list.h"
|
|
#include "hphp/compiler/analysis/class_scope.h"
|
|
#include "hphp/compiler/expression/expression_list.h"
|
|
#include "hphp/compiler/expression/array_pair_expression.h"
|
|
#include "hphp/compiler/expression/simple_variable.h"
|
|
#include "hphp/compiler/expression/simple_function_call.h"
|
|
#include "hphp/compiler/expression/parameter_expression.h"
|
|
#include "hphp/compiler/expression/assignment_expression.h"
|
|
#include "hphp/compiler/expression/unary_op_expression.h"
|
|
#include "hphp/util/parser/hphp.tab.hpp"
|
|
|
|
using namespace HPHP;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// constructors/destructors
|
|
|
|
FunctionCall::FunctionCall
|
|
(EXPRESSION_CONSTRUCTOR_BASE_PARAMETERS,
|
|
ExpressionPtr nameExp, const std::string &name, bool hadBackslash,
|
|
ExpressionListPtr params, ExpressionPtr classExp)
|
|
: Expression(EXPRESSION_CONSTRUCTOR_BASE_PARAMETER_VALUES),
|
|
StaticClassName(classExp), m_nameExp(nameExp),
|
|
m_ciTemp(-1), m_params(params), m_valid(false),
|
|
m_extraArg(0), m_variableArgument(false), m_voidReturn(false),
|
|
m_voidWrapper(false), m_redeclared(false),
|
|
m_noStatic(false), m_noInline(false), m_invokeFewArgsDecision(true),
|
|
m_arrayParams(false), m_hadBackslash(hadBackslash),
|
|
m_argArrayId(-1), m_argArrayHash(-1), m_argArrayIndex(-1) {
|
|
|
|
if (m_nameExp &&
|
|
m_nameExp->getKindOf() == Expression::KindOfScalarExpression) {
|
|
assert(m_name.empty());
|
|
ScalarExpressionPtr c = dynamic_pointer_cast<ScalarExpression>(m_nameExp);
|
|
m_origName = c->getOriginalLiteralString();
|
|
c->toLower(true /* func call*/);
|
|
m_name = c->getLiteralString();
|
|
} else {
|
|
m_origName = name;
|
|
m_name = Util::toLower(name);
|
|
}
|
|
m_clsNameTemp = -1;
|
|
}
|
|
|
|
void FunctionCall::reset() {
|
|
m_valid = false;
|
|
m_extraArg = 0;
|
|
m_variableArgument = false;
|
|
m_voidWrapper = false;
|
|
}
|
|
|
|
bool FunctionCall::isTemporary() const {
|
|
return m_funcScope && !m_funcScope->isRefReturn();
|
|
}
|
|
|
|
void FunctionCall::deepCopy(FunctionCallPtr exp) {
|
|
Expression::deepCopy(exp);
|
|
exp->m_class = Clone(m_class);
|
|
exp->m_params = Clone(m_params);
|
|
exp->m_nameExp = Clone(m_nameExp);
|
|
}
|
|
|
|
bool FunctionCall::canInvokeFewArgs() {
|
|
// We can always change out minds about saying yes, but once we say
|
|
// no, it sticks.
|
|
if (m_invokeFewArgsDecision &&
|
|
((m_params && m_params->getCount() > Option::InvokeFewArgsCount) ||
|
|
m_arrayParams)) {
|
|
m_invokeFewArgsDecision = false;
|
|
}
|
|
return m_invokeFewArgsDecision;
|
|
}
|
|
|
|
ConstructPtr FunctionCall::getNthKid(int n) const {
|
|
switch (n) {
|
|
case 0:
|
|
return m_class;
|
|
case 1:
|
|
return m_nameExp;
|
|
case 2:
|
|
return m_params;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
return ConstructPtr();
|
|
}
|
|
|
|
int FunctionCall::getKidCount() const {
|
|
return 3;
|
|
}
|
|
|
|
void FunctionCall::setNthKid(int n, ConstructPtr cp) {
|
|
switch (n) {
|
|
case 0:
|
|
m_class = boost::dynamic_pointer_cast<Expression>(cp);
|
|
break;
|
|
case 1:
|
|
m_nameExp = boost::dynamic_pointer_cast<Expression>(cp);
|
|
break;
|
|
case 2:
|
|
m_params = boost::dynamic_pointer_cast<ExpressionList>(cp);
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FunctionCall::markRefParams(FunctionScopePtr func,
|
|
const std::string &name,
|
|
bool canInvokeFewArgs) {
|
|
ExpressionList ¶ms = *m_params;
|
|
if (func) {
|
|
int mpc = func->getMaxParamCount();
|
|
for (int i = params.getCount(); i--; ) {
|
|
ExpressionPtr p = params[i];
|
|
if (i < mpc ? func->isRefParam(i) :
|
|
func->isReferenceVariableArgument()) {
|
|
p->setContext(Expression::RefValue);
|
|
} else if (i < mpc && p->hasContext(RefParameter)) {
|
|
Symbol *sym = func->getVariables()->addSymbol(func->getParamName(i));
|
|
sym->setLvalParam();
|
|
sym->setCallTimeRef();
|
|
}
|
|
}
|
|
} else if (Option::WholeProgram && !m_name.empty()) {
|
|
FunctionScope::FunctionInfoPtr info =
|
|
FunctionScope::GetFunctionInfo(m_name);
|
|
if (info) {
|
|
for (int i = params.getCount(); i--; ) {
|
|
if (info->isRefParam(i)) {
|
|
m_params->markParam(i, canInvokeFewArgs);
|
|
}
|
|
}
|
|
}
|
|
// If we cannot find information of the so-named function, it might not
|
|
// exist, or it might go through __call(), either of which cannot have
|
|
// reference parameters.
|
|
} else {
|
|
for (int i = params.getCount(); i--; ) {
|
|
m_params->markParam(i, canInvokeFewArgs);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FunctionCall::analyzeProgram(AnalysisResultPtr ar) {
|
|
if (m_class) m_class->analyzeProgram(ar);
|
|
if (m_nameExp) m_nameExp->analyzeProgram(ar);
|
|
if (m_params) m_params->analyzeProgram(ar);
|
|
if (ar->getPhase() == AnalysisResult::AnalyzeFinal) {
|
|
if (m_funcScope && !m_arrayParams) {
|
|
for (int i = 0, n = m_funcScope->getMaxParamCount(); i < n; ++i) {
|
|
if (TypePtr specType = m_funcScope->getParamTypeSpec(i)) {
|
|
const char *fmt = 0;
|
|
string ptype;
|
|
if (!m_params || m_params->getCount() <= i) {
|
|
if (i >= m_funcScope->getMinParamCount()) break;
|
|
fmt = "parameter %d of %s() requires %s, none given";
|
|
} else {
|
|
ExpressionPtr param = (*m_params)[i];
|
|
if (!Type::Inferred(ar, param->getType(), specType)) {
|
|
fmt = "parameter %d of %s() requires %s, called with %s";
|
|
}
|
|
ptype = param->getType()->toString();
|
|
}
|
|
if (fmt) {
|
|
string msg;
|
|
Util::string_printf
|
|
(msg, fmt,
|
|
i + 1,
|
|
Util::escapeStringForCPP(m_funcScope->getOriginalName()).c_str(),
|
|
specType->toString().c_str(), ptype.c_str());
|
|
Compiler::Error(Compiler::BadArgumentType,
|
|
shared_from_this(), msg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct InlineCloneInfo {
|
|
explicit InlineCloneInfo(FunctionScopePtr fs)
|
|
: func(fs)
|
|
, callWithThis(false)
|
|
{}
|
|
|
|
FunctionScopePtr func;
|
|
StringToExpressionPtrMap sepm;
|
|
ExpressionListPtr elist;
|
|
bool callWithThis;
|
|
string localThis;
|
|
string staticClass;
|
|
};
|
|
|
|
//typedef std::map<std::string, ExpressionPtr> StringToExpressionPtrMap;
|
|
|
|
static ExpressionPtr cloneForInlineRecur(InlineCloneInfo &info,
|
|
ExpressionPtr exp,
|
|
const std::string &prefix,
|
|
AnalysisResultConstPtr ar,
|
|
FunctionScopePtr scope) {
|
|
exp->getOriginalScope(); // make sure to cache the original scope
|
|
exp->setBlockScope(scope);
|
|
for (int i = 0, n = exp->getKidCount(); i < n; i++) {
|
|
if (ExpressionPtr k = exp->getNthExpr(i)) {
|
|
exp->setNthKid(i, cloneForInlineRecur(info, k, prefix, ar, scope));
|
|
}
|
|
}
|
|
StaticClassName *scn = dynamic_cast<StaticClassName*>(exp.get());
|
|
if (scn && scn->isStatic() && !info.staticClass.empty()) {
|
|
scn->resolveStatic(info.staticClass);
|
|
}
|
|
switch (exp->getKindOf()) {
|
|
case Expression::KindOfSimpleVariable:
|
|
{
|
|
SimpleVariablePtr sv(dynamic_pointer_cast<SimpleVariable>(exp));
|
|
if (sv->isSuperGlobal()) break;
|
|
string name;
|
|
if (sv->isThis()) {
|
|
if (!info.callWithThis) {
|
|
if (!sv->hasContext(Expression::ObjectContext)) {
|
|
exp = sv->makeConstant(ar, "null");
|
|
} else {
|
|
// This will produce the wrong error
|
|
// we really want a "throw_fatal" ast node.
|
|
exp = sv->makeConstant(ar, "null");
|
|
}
|
|
break;
|
|
}
|
|
if (info.localThis.empty()) break;
|
|
name = info.localThis;
|
|
} else {
|
|
name = prefix + sv->getName();
|
|
}
|
|
SimpleVariablePtr rep(new SimpleVariable(
|
|
exp->getScope(), exp->getLocation(), name));
|
|
rep->copyContext(sv);
|
|
rep->updateSymbol(SimpleVariablePtr());
|
|
rep->getSymbol()->setHidden();
|
|
// Conservatively set flags to prevent
|
|
// the alias manager from getting confused.
|
|
// On the next pass, it will correct the values,
|
|
// and optimize appropriately.
|
|
rep->getSymbol()->setUsed();
|
|
rep->getSymbol()->setReferenced();
|
|
if (exp->getContext() & (Expression::LValue|
|
|
Expression::RefValue|
|
|
Expression::RefParameter)) {
|
|
info.sepm[name] = rep;
|
|
}
|
|
exp = rep;
|
|
}
|
|
break;
|
|
case Expression::KindOfObjectMethodExpression:
|
|
{
|
|
FunctionCallPtr call(
|
|
static_pointer_cast<FunctionCall>(exp));
|
|
if (call->getFuncScope() == info.func) {
|
|
call->setNoInline();
|
|
}
|
|
break;
|
|
}
|
|
case Expression::KindOfSimpleFunctionCall:
|
|
{
|
|
SimpleFunctionCallPtr call(static_pointer_cast<SimpleFunctionCall>(exp));
|
|
call->addLateDependencies(ar);
|
|
call->setLocalThis(info.localThis);
|
|
if (call->getFuncScope() == info.func) {
|
|
call->setNoInline();
|
|
}
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return exp;
|
|
}
|
|
|
|
static ExpressionPtr cloneForInline(InlineCloneInfo &info,
|
|
ExpressionPtr exp,
|
|
const std::string &prefix,
|
|
AnalysisResultConstPtr ar,
|
|
FunctionScopePtr scope) {
|
|
return cloneForInlineRecur(info, exp->clone(), prefix, ar, scope);
|
|
}
|
|
|
|
static int cloneStmtsForInline(InlineCloneInfo &info, StatementPtr s,
|
|
const std::string &prefix,
|
|
AnalysisResultConstPtr ar,
|
|
FunctionScopePtr scope) {
|
|
switch (s->getKindOf()) {
|
|
case Statement::KindOfStatementList:
|
|
{
|
|
for (int i = 0, n = s->getKidCount(); i < n; ++i) {
|
|
if (int ret = cloneStmtsForInline(info, s->getNthStmt(i),
|
|
prefix, ar, scope)) {
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
case Statement::KindOfExpStatement:
|
|
info.elist->addElement(cloneForInline(
|
|
info, dynamic_pointer_cast<ExpStatement>(s)->
|
|
getExpression(), prefix, ar, scope));
|
|
return 0;
|
|
case Statement::KindOfReturnStatement:
|
|
{
|
|
ExpressionPtr exp =
|
|
dynamic_pointer_cast<ReturnStatement>(s)->getRetExp();
|
|
|
|
if (exp) {
|
|
exp = cloneForInline(info, exp, prefix, ar, scope);
|
|
if (exp->hasContext(Expression::RefValue)) {
|
|
exp->clearContext(Expression::RefValue);
|
|
if (exp->isRefable()) exp->setContext(Expression::LValue);
|
|
}
|
|
info.elist->addElement(exp);
|
|
return 1;
|
|
}
|
|
return -1;
|
|
}
|
|
default:
|
|
not_reached();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
ExpressionPtr FunctionCall::inliner(AnalysisResultConstPtr ar,
|
|
ExpressionPtr obj, std::string localThis) {
|
|
FunctionScopePtr fs = getFunctionScope();
|
|
if (m_noInline || !fs || fs == m_funcScope || !m_funcScope->getStmt()) {
|
|
return ExpressionPtr();
|
|
}
|
|
|
|
BlockScope::s_jobStateMutex.lock();
|
|
if (m_funcScope->getMark() == BlockScope::MarkProcessing) {
|
|
fs->setForceRerun(true);
|
|
BlockScope::s_jobStateMutex.unlock();
|
|
return ExpressionPtr();
|
|
}
|
|
ReadLock lock(m_funcScope->getInlineMutex());
|
|
BlockScope::s_jobStateMutex.unlock();
|
|
|
|
if (!m_funcScope->getInlineAsExpr()) {
|
|
return ExpressionPtr();
|
|
}
|
|
|
|
if (m_funcScope->getInlineSameContext() &&
|
|
m_funcScope->getContainingClass() &&
|
|
m_funcScope->getContainingClass() != getClassScope()) {
|
|
/*
|
|
The function contains a context sensitive construct such as
|
|
call_user_func (context sensitive because it could call
|
|
array('parent', 'foo')) so its not safe to inline it
|
|
into a different context.
|
|
*/
|
|
return ExpressionPtr();
|
|
}
|
|
|
|
MethodStatementPtr m
|
|
(dynamic_pointer_cast<MethodStatement>(m_funcScope->getStmt()));
|
|
|
|
VariableTablePtr vt = fs->getVariables();
|
|
int nAct = m_params ? m_params->getCount() : 0;
|
|
int nMax = m_funcScope->getMaxParamCount();
|
|
if (nAct < m_funcScope->getMinParamCount() || !m->getStmts()) {
|
|
return ExpressionPtr();
|
|
}
|
|
|
|
InlineCloneInfo info(m_funcScope);
|
|
info.elist = ExpressionListPtr(new ExpressionList(
|
|
getScope(), getLocation(),
|
|
ExpressionList::ListKindWrapped));
|
|
std::ostringstream oss;
|
|
oss << fs->nextInlineIndex() << "_" << m_name << "_";
|
|
std::string prefix = oss.str();
|
|
|
|
if (obj) {
|
|
info.callWithThis = true;
|
|
if (!obj->isThis()) {
|
|
SimpleVariablePtr var
|
|
(new SimpleVariable(getScope(),
|
|
obj->getLocation(),
|
|
prefix + "this"));
|
|
var->updateSymbol(SimpleVariablePtr());
|
|
var->getSymbol()->setHidden();
|
|
var->getSymbol()->setUsed();
|
|
var->getSymbol()->setReferenced();
|
|
AssignmentExpressionPtr ae
|
|
(new AssignmentExpression(getScope(),
|
|
obj->getLocation(),
|
|
var, obj, false));
|
|
info.elist->addElement(ae);
|
|
info.sepm[var->getName()] = var;
|
|
info.localThis = var->getName();
|
|
}
|
|
} else {
|
|
if (m_classScope) {
|
|
if (!m_funcScope->isStatic()) {
|
|
ClassScopeRawPtr oCls = getOriginalClass();
|
|
FunctionScopeRawPtr oFunc = getOriginalFunction();
|
|
if (oCls && !oFunc->isStatic() &&
|
|
(oCls == m_classScope ||
|
|
oCls->derivesFrom(ar, m_className, true, false))) {
|
|
info.callWithThis = true;
|
|
info.localThis = localThis;
|
|
}
|
|
}
|
|
if (!isSelf() && !isParent() && !isStatic()) {
|
|
info.staticClass = m_className;
|
|
}
|
|
}
|
|
}
|
|
|
|
ExpressionListPtr plist = m->getParams();
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < nMax || i < nAct; i++) {
|
|
ParameterExpressionPtr param
|
|
(i < nMax ?
|
|
dynamic_pointer_cast<ParameterExpression>((*plist)[i]) :
|
|
ParameterExpressionPtr());
|
|
ExpressionPtr arg = i < nAct ? (*m_params)[i] :
|
|
Clone(param->defaultValue(), getScope());
|
|
SimpleVariablePtr var
|
|
(new SimpleVariable(getScope(),
|
|
(i < nAct ? arg.get() : this)->getLocation(),
|
|
prefix + (param ?
|
|
param->getName() :
|
|
lexical_cast<string>(i))));
|
|
var->updateSymbol(SimpleVariablePtr());
|
|
var->getSymbol()->setHidden();
|
|
var->getSymbol()->setUsed();
|
|
var->getSymbol()->setReferenced();
|
|
bool ref =
|
|
(i < nMax && m_funcScope->isRefParam(i)) ||
|
|
arg->hasContext(RefParameter);
|
|
arg->clearContext(RefParameter);
|
|
AssignmentExpressionPtr ae
|
|
(new AssignmentExpression(getScope(),
|
|
arg->getLocation(),
|
|
var, arg, ref));
|
|
info.elist->addElement(ae);
|
|
if (i < nAct && (ref || !arg->isScalar())) {
|
|
info.sepm[var->getName()] = var;
|
|
}
|
|
}
|
|
|
|
if (cloneStmtsForInline(info, m->getStmts(), prefix, ar,
|
|
getFunctionScope()) <= 0) {
|
|
info.elist->addElement(makeConstant(ar, "null"));
|
|
}
|
|
|
|
if (info.sepm.size()) {
|
|
ExpressionListPtr unset_list
|
|
(new ExpressionList(getScope(), getLocation()));
|
|
|
|
for (StringToExpressionPtrMap::iterator it = info.sepm.begin(),
|
|
end = info.sepm.end(); it != end; ++it) {
|
|
ExpressionPtr var = it->second->clone();
|
|
var->clearContext((Context)(unsigned)-1);
|
|
unset_list->addElement(var);
|
|
}
|
|
|
|
ExpressionPtr unset(
|
|
new UnaryOpExpression(getScope(), getLocation(),
|
|
unset_list, T_UNSET, true));
|
|
i = info.elist->getCount();
|
|
ExpressionPtr ret = (*info.elist)[--i];
|
|
if (ret->isScalar()) {
|
|
info.elist->insertElement(unset, i);
|
|
} else {
|
|
ExpressionListPtr result_list
|
|
(new ExpressionList(getScope(), getLocation(),
|
|
ExpressionList::ListKindLeft));
|
|
if (ret->hasContext(LValue)) {
|
|
result_list->setContext(LValue);
|
|
result_list->setContext(ReturnContext);
|
|
}
|
|
result_list->addElement(ret);
|
|
result_list->addElement(unset);
|
|
(*info.elist)[i] = result_list;
|
|
}
|
|
}
|
|
|
|
recomputeEffects();
|
|
|
|
return replaceValue(info.elist);
|
|
}
|
|
|
|
ExpressionPtr FunctionCall::preOptimize(AnalysisResultConstPtr ar) {
|
|
if (m_class) updateClassName();
|
|
return ExpressionPtr();
|
|
}
|
|
|
|
ExpressionPtr FunctionCall::postOptimize(AnalysisResultConstPtr ar) {
|
|
if (m_class) updateClassName();
|
|
return ExpressionPtr();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
TypePtr FunctionCall::checkParamsAndReturn(AnalysisResultPtr ar,
|
|
TypePtr type, bool coerce,
|
|
FunctionScopePtr func,
|
|
bool arrayParams) {
|
|
#ifdef HPHP_DETAILED_TYPE_INF_ASSERT
|
|
assert(func->hasUser(getScope(), BlockScope::UseKindCaller));
|
|
#endif /* HPHP_DETAILED_TYPE_INF_ASSERT */
|
|
ConstructPtr self = shared_from_this();
|
|
TypePtr frt;
|
|
{
|
|
TRY_LOCK(func);
|
|
func->getInferTypesMutex().assertOwnedBySelf();
|
|
assert(!func->inVisitScopes() || getScope() == func);
|
|
frt = func->getReturnType();
|
|
}
|
|
|
|
// fix return type for generators here, keep the infered return type
|
|
// in function scope
|
|
if (func->isGenerator()) {
|
|
frt = Type::GetType(Type::KindOfObject, "Continuation");
|
|
}
|
|
|
|
if (!frt) {
|
|
m_voidReturn = true;
|
|
setActualType(TypePtr());
|
|
if (!isUnused() && !type->is(Type::KindOfAny)) {
|
|
if (!hasContext(ReturnContext) &&
|
|
!func->isFirstPass() && !func->isAbstract()) {
|
|
if (Option::WholeProgram || !func->getContainingClass() ||
|
|
func->isStatic() || func->isFinal() || func->isPrivate()) {
|
|
Compiler::Error(Compiler::UseVoidReturn, self);
|
|
}
|
|
}
|
|
if (!Type::IsMappedToVariant(type)) {
|
|
setExpectedType(type);
|
|
}
|
|
m_voidWrapper = true;
|
|
}
|
|
} else {
|
|
m_voidReturn = false;
|
|
m_voidWrapper = false;
|
|
type = checkTypesImpl(ar, type, frt, coerce);
|
|
assert(m_actualType);
|
|
}
|
|
if (arrayParams) {
|
|
m_extraArg = 0;
|
|
(*m_params)[0]->inferAndCheck(ar, Type::Array, false);
|
|
} else {
|
|
m_extraArg = func->inferParamTypes(ar, self, m_params, m_valid);
|
|
}
|
|
m_variableArgument = func->isVariableArgument();
|
|
if (m_valid) {
|
|
m_implementedType.reset();
|
|
} else {
|
|
m_implementedType = Type::Variant;
|
|
}
|
|
assert(type);
|
|
|
|
return type;
|
|
}
|