void ClassStatement::analyzeProgram(AnalysisResultPtr ar) { vector<string> bases; if (!m_parent.empty()) bases.push_back(m_parent); if (m_base) m_base->getStrings(bases); for (unsigned int i = 0; i < bases.size(); i++) { string className = bases[i]; addUserClass(ar, bases[i]); } checkVolatile(ar); if (m_stmt) { m_stmt->analyzeProgram(ar); } ClassScopePtr clsScope = getClassScope(); // Check that every trait stmt is either a method, class_var, or trait_use if (clsScope->isTrait()) { StatementListPtr stmts = getStmts(); if (stmts) { for (int s = 0; s < stmts->getCount(); s++) { StatementPtr stmt = (*stmts)[s]; if(!dynamic_pointer_cast<UseTraitStatement>(stmt) && !dynamic_pointer_cast<MethodStatement>(stmt) && !dynamic_pointer_cast<ClassVariable>(stmt)) { Compiler::Error(Compiler::InvalidTraitStatement, stmt); } } } } if (ar->getPhase() != AnalysisResult::AnalyzeAll) return; clsScope->importUsedTraits(ar); ar->recordClassSource(m_name, m_loc, getFileScope()->getName()); for (unsigned int i = 0; i < bases.size(); i++) { ClassScopePtr cls = ar->findClass(bases[i]); if (cls) { if ((!cls->isInterface() && (m_parent.empty() || i > 0 )) || (cls->isInterface() && (!m_parent.empty() && i == 0 )) || (cls->isTrait())) { Compiler::Error(Compiler::InvalidDerivation, shared_from_this(), cls->getOriginalName()); } if (cls->isUserClass()) { cls->addUse(getScope(), BlockScope::UseKindParentRef); } } } }
void ClassConstant::onParseRecur(AnalysisResultConstPtr ar, ClassScopePtr scope) { ConstantTablePtr constants = scope->getConstants(); if (scope->isTrait()) { parseTimeFatal(Compiler::InvalidTraitStatement, "Traits cannot have constants"); } for (int i = 0; i < m_exp->getCount(); i++) { AssignmentExpressionPtr assignment = dynamic_pointer_cast<AssignmentExpression>((*m_exp)[i]); ExpressionPtr var = assignment->getVariable(); const std::string &name = dynamic_pointer_cast<ConstantExpression>(var)->getName(); if (constants->isPresent(name)) { assignment->parseTimeFatal(Compiler::DeclaredConstantTwice, "Cannot redeclare %s::%s", scope->getOriginalName().c_str(), name.c_str()); } else { assignment->onParseRecur(ar, scope); } } }
void ClassStatement::analyzeProgram(AnalysisResultPtr ar) { vector<string> bases; if (!m_parent.empty()) bases.push_back(m_parent); if (m_base) m_base->getStrings(bases); for (unsigned int i = 0; i < bases.size(); i++) { string className = bases[i]; addUserClass(ar, bases[i]); } checkVolatile(ar); if (m_stmt) { m_stmt->analyzeProgram(ar); } if (ar->getPhase() != AnalysisResult::AnalyzeAll) return; for (unsigned int i = 0; i < bases.size(); i++) { ClassScopePtr cls = ar->findClass(bases[i]); if (cls) { if ((!cls->isInterface() && (m_parent.empty() || i > 0 )) || (cls->isInterface() && (!m_parent.empty() && i == 0 )) || (cls->isTrait())) { Compiler::Error(Compiler::InvalidDerivation, shared_from_this(), "You are extending " + cls->getOriginalName() + " which is an interface or a trait"); } if (cls->isUserClass()) { cls->addUse(getScope(), BlockScope::UseKindParentRef); } } } }
bool ClosureExpression::preOutputCPP(CodeGenerator &cg, AnalysisResultPtr ar, int state) { FunctionScopeRawPtr cfunc(m_func->getFunctionScope()); bool output = false; for (BlockScopePtr sc = cfunc->getOuterScope(); sc; sc = sc->getOuterScope()) { if (sc->is(BlockScope::ClassScope)) { ClassScopePtr cls = boost::static_pointer_cast<ClassScope>(sc); if (cls->isTrait()) { output = true; break; } } } if (!cg.inExpression()) { return output || Expression::preOutputCPP(cg, ar, state); } if (output) { cg.wrapExpressionBegin(); cfunc->outputCPPPreface(cg, ar); } return Expression::preOutputCPP(cg, ar, state) || output; }
void ClassConstant::onParseRecur(AnalysisResultConstPtr ar, ClassScopePtr scope) { ConstantTablePtr constants = scope->getConstants(); if (scope->isTrait()) { parseTimeFatal(Compiler::InvalidTraitStatement, "Traits cannot have constants"); } if (isAbstract()) { for (int i = 0; i < m_exp->getCount(); i++) { ConstantExpressionPtr exp = dynamic_pointer_cast<ConstantExpression>((*m_exp)[i]); const std::string &name = exp->getName(); if (constants->isPresent(name)) { exp->parseTimeFatal(Compiler::DeclaredConstantTwice, "Cannot redeclare %s::%s", scope->getOriginalName().c_str(), name.c_str()); } // HACK: break attempts to write global constants here; // see ConstantExpression::preOptimize exp->setContext(Expression::LValue); // Unlike with assignment expression below, nothing needs to be added // to the scope's constant table } } else { for (int i = 0; i < m_exp->getCount(); i++) { AssignmentExpressionPtr assignment = dynamic_pointer_cast<AssignmentExpression>((*m_exp)[i]); ExpressionPtr var = assignment->getVariable(); const std::string &name = dynamic_pointer_cast<ConstantExpression>(var)->getName(); if (constants->isPresent(name)) { assignment->parseTimeFatal(Compiler::DeclaredConstantTwice, "Cannot redeclare %s::%s", scope->getOriginalName().c_str(), name.c_str()); } else { if (isTypeconst()) { // We do not want type constants to be available at run time. // To ensure this we do not want them to be added to the constants // table. The constants table is used to inline values for expressions // See ClassConstantExpression::preOptimize. // AssignmentExpression::onParseRecur essentially adds constants to // the constant table so we skip it. continue; } assignment->onParseRecur(ar, scope); } } } }
void ParameterExpression::parseHandler(ClassScopePtr cls) { // Trait has not been 'inlined' into using class so context is not available if (!m_type.empty() && !cls->isTrait()) { fixupSelfAndParentTypehints(cls); if (m_defaultValue) { compatibleDefault(); } } }
void TraitRequireStatement::onParseRecur(AnalysisResultConstPtr ar, ClassScopePtr scope) { if (!scope->isTrait()) { parseTimeFatal(Compiler::InvalidTraitStatement, "Only traits can require in class scope"); } ar->parseOnDemandByClass(toLower(m_required)); scope->addTraitRequirement(m_required, m_extends); }
void ClassScope::applyTraitAliasRule(AnalysisResultPtr ar, TraitAliasStatementPtr stmt) { assert(Option::WholeProgram); const string traitName = toLower(stmt->getTraitName()); const string origMethName = toLower(stmt->getMethodName()); const string newMethName = toLower(stmt->getNewMethodName()); // Get the trait's "class" ClassScopePtr traitCls; if (traitName.empty()) { traitCls = findSingleTraitWithMethod(ar, origMethName); } else { traitCls = ar->findClass(traitName); } if (!traitCls || !(traitCls->isTrait())) { stmt->analysisTimeFatal( Compiler::UnknownTrait, Strings::TRAITS_UNKNOWN_TRAIT, traitName.empty() ? origMethName.c_str() : traitName.c_str() ); } // Keep record of alias rule addTraitAlias(stmt); // Get the method std::set<ClassScopePtr> visitedTraits; MethodStatementPtr methStmt = findTraitMethod(ar, traitCls, origMethName, visitedTraits); if (!methStmt) { stmt->analysisTimeFatal( Compiler::UnknownTraitMethod, Strings::TRAITS_UNKNOWN_TRAIT_METHOD, origMethName.c_str() ); } if (origMethName == newMethName) { setImportTraitMethodModifiers(origMethName, traitCls, stmt->getModifiers()); } else { // Insert renamed entry into the set of methods to be imported TraitMethod traitMethod(traitCls, methStmt, stmt->getModifiers(), stmt, stmt->getNewMethodName()); addImportTraitMethod(traitMethod, newMethName); } }
void ClassRequireStatement::onParseRecur(AnalysisResultConstPtr ar, FileScopeRawPtr fs, ClassScopePtr scope) { if (!scope->isTrait() && !scope->isInterface()) { parseTimeFatal(fs, Compiler::InvalidTraitStatement, "Only traits and interfaces may use 'require' in class scope"); } if (scope->isInterface() && !m_extends) { parseTimeFatal( fs, Compiler::InvalidTraitStatement, "'require implements' may not be used in interface scope" "; instead, use interface inheritance"); } ar->parseOnDemandByClass(toLower(m_required)); scope->addClassRequirement(m_required, m_extends); }
void ClassScope::importUsedTraits(AnalysisResultPtr ar) { // Trait flattening is supposed to happen only when we have awareness of the // whole program. assert(Option::WholeProgram); if (m_traitStatus == FLATTENED) return; if (m_traitStatus == BEING_FLATTENED) { getStmt()->analysisTimeFatal( Compiler::CyclicDependentTraits, "Cyclic dependency between traits involving %s", getScopeName().c_str() ); return; } if (m_usedTraitNames.size() == 0) { m_traitStatus = FLATTENED; return; } m_traitStatus = BEING_FLATTENED; m_numDeclMethods = m_functionsVec.size(); // First, make sure that parent classes have their traits imported. if (!m_parent.empty()) { ClassScopePtr parent = ar->findClass(m_parent); if (parent) { parent->importUsedTraits(ar); } } TMIData tmid; if (isTrait()) { for (auto const& req : getClassRequiredExtends()) { ClassScopePtr rCls = ar->findClass(req); if (!rCls || rCls->isFinal() || rCls->isInterface()) { getStmt()->analysisTimeFatal( Compiler::InvalidDerivation, Strings::TRAIT_BAD_REQ_EXTENDS, m_scopeName.c_str(), req.c_str(), req.c_str() ); } } for (auto const& req : getClassRequiredImplements()) { ClassScopePtr rCls = ar->findClass(req); if (!rCls || !(rCls->isInterface())) { getStmt()->analysisTimeFatal( Compiler::InvalidDerivation, Strings::TRAIT_BAD_REQ_IMPLEMENTS, m_scopeName.c_str(), req.c_str(), req.c_str() ); } } } // Find trait methods to be imported. for (unsigned i = 0; i < m_usedTraitNames.size(); i++) { ClassScopePtr tCls = ar->findClass(m_usedTraitNames[i]); if (!tCls || !(tCls->isTrait())) { setAttribute(UsesUnknownTrait); // XXX: is this useful ... for anything? getStmt()->analysisTimeFatal( Compiler::UnknownTrait, Strings::TRAITS_UNKNOWN_TRAIT, m_usedTraitNames[i].c_str() ); } // First, make sure the used trait is flattened. tCls->importUsedTraits(ar); findTraitMethodsToImport(ar, tCls, tmid); // Import any interfaces implemented. tCls->getInterfaces(ar, m_bases, /* recursive */ false); importClassRequirements(ar, tCls); } // Apply rules. applyTraitRules(tmid); // Remove methods declared on the current class from the trait import list; // the class methods take precedence. for (auto const& methName : tmid.methodNames()) { if (findFunction(ar, methName, false /* recursive */, false /* exclIntfBase */)) { // This does not affect the methodNames() vector. tmid.erase(methName); } } auto traitMethods = tmid.finish(this); std::map<string, MethodStatementPtr, stdltistr> importedTraitMethods; std::vector<std::pair<string,const TraitMethod*>> importedTraitsWithOrigName; // Actually import the methods. for (auto const& mdata : traitMethods) { if ((mdata.tm.modifiers ? mdata.tm.modifiers : mdata.tm.method->getModifiers() )->isAbstract()) { // Skip abstract methods, if the method already exists in the class. if (findFunction(ar, mdata.name, true) || importedTraitMethods.count(mdata.name)) { continue; } } auto sourceName = mdata.tm.ruleStmt ? ((TraitAliasStatement*)mdata.tm.ruleStmt.get())->getMethodName() : mdata.name; importedTraitMethods[sourceName] = MethodStatementPtr(); importedTraitsWithOrigName.push_back( std::make_pair(sourceName, &mdata.tm)); } // Make sure there won't be 2 constructors after importing auto traitConstruct = importedTraitMethods.count("__construct"); auto traitName = importedTraitMethods.count(getScopeName()); auto classConstruct = m_functions.count("__construct"); auto className = m_functions.count(getScopeName()); if ((traitConstruct && traitName) || (traitConstruct && className) || (classConstruct && traitName)) { getStmt()->analysisTimeFatal( Compiler::InvalidDerivation, "%s has colliding constructor definitions coming from traits", getScopeName().c_str() ); } for (auto const& traitPair : importedTraitsWithOrigName) { auto traitMethod = traitPair.second; MethodStatementPtr newMeth = importTraitMethod( *traitMethod, ar, traitMethod->originalName ); } // Import trait properties importTraitProperties(ar); m_traitStatus = FLATTENED; }
TypePtr NewObjectExpression::inferTypes(AnalysisResultPtr ar, TypePtr type, bool coerce) { reset(); m_classScope.reset(); FunctionScopePtr prev = m_funcScope; m_funcScope.reset(); ConstructPtr self = shared_from_this(); if (!m_name.empty() && !isStatic()) { ClassScopePtr cls = resolveClassWithChecks(); m_name = m_className; if (!cls) { if (m_params) m_params->inferAndCheck(ar, Type::Any, false); return Type::Object; } if (getScope()->isFirstPass() && (cls->isTrait() ? !isSelf() && !isParent() : cls->isInterface() || cls->isAbstract())) { Compiler::Error(Compiler::InvalidInstantiation, self); } if (cls->isVolatile() && !isPresent()) { getScope()->getVariables()-> setAttribute(VariableTable::NeedGlobalPointer); } m_dynamic = cls->derivesFromRedeclaring(); bool valid = true; FunctionScopePtr func = cls->findConstructor(ar, true); if (!func) { if (m_params) { if (!m_dynamic && m_params->getCount()) { if (getScope()->isFirstPass()) { Compiler::Error(Compiler::BadConstructorCall, self); } } m_params->inferAndCheck(ar, Type::Some, false); } } else { if (func != prev) func->addNewObjCaller(getScope()); m_extraArg = func->inferParamTypes(ar, self, m_params, valid); m_variableArgument = func->isVariableArgument(); } if (valid) { m_classScope = cls; m_funcScope = func; } if (!valid || m_dynamic) { m_implementedType = Type::Object; } else { m_implementedType.reset(); } return Type::CreateObjectType(m_name); } else { if (m_params) { m_params->markParams(canInvokeFewArgs()); } } m_implementedType.reset(); m_nameExp->inferAndCheck(ar, Type::String, false); if (m_params) m_params->inferAndCheck(ar, Type::Any, false); return Type::Object; }
TypePtr ObjectMethodExpression::inferAndCheck(AnalysisResultPtr ar, TypePtr type, bool coerce) { assert(type); IMPLEMENT_INFER_AND_CHECK_ASSERT(getScope()); resetTypes(); reset(); ConstructPtr self = shared_from_this(); TypePtr objectType = m_object->inferAndCheck(ar, Type::Some, false); m_valid = true; m_bindClass = true; if (m_name.empty()) { m_nameExp->inferAndCheck(ar, Type::Some, false); setInvokeParams(ar); // we have to use a variant to hold dynamic value return checkTypesImpl(ar, type, Type::Variant, coerce); } ClassScopePtr cls; if (objectType && !objectType->getName().empty()) { if (m_classScope && !strcasecmp(objectType->getName().c_str(), m_classScope->getName().c_str())) { cls = m_classScope; } else { cls = ar->findExactClass(shared_from_this(), objectType->getName()); } } if (!cls) { m_classScope.reset(); m_funcScope.reset(); m_valid = false; setInvokeParams(ar); return checkTypesImpl(ar, type, Type::Variant, coerce); } if (m_classScope != cls) { m_classScope = cls; m_funcScope.reset(); } FunctionScopePtr func = m_funcScope; if (!func) { func = cls->findFunction(ar, m_name, true, true); if (!func) { if (!cls->isTrait() && !cls->getAttribute(ClassScope::MayHaveUnknownMethodHandler) && !cls->getAttribute(ClassScope::HasUnknownMethodHandler) && !cls->getAttribute(ClassScope::InheritsUnknownMethodHandler)) { if (ar->classMemberExists(m_name, AnalysisResult::MethodName)) { if (!Option::AllDynamic) { setDynamicByIdentifier(ar, m_name); } } else { Compiler::Error(Compiler::UnknownObjectMethod, self); } } m_valid = false; setInvokeParams(ar); return checkTypesImpl(ar, type, Type::Variant, coerce); } m_funcScope = func; func->addCaller(getScope(), !type->is(Type::KindOfAny)); } bool valid = true; m_bindClass = func->isStatic(); // use $this inside a static function if (m_object->isThis()) { FunctionScopePtr localfunc = getFunctionScope(); if (localfunc->isStatic()) { if (getScope()->isFirstPass()) { Compiler::Error(Compiler::MissingObjectContext, self); } valid = false; } } // invoke() will return Variant if (cls->isInterface() || (func->isVirtual() && (!Option::WholeProgram || func->isAbstract() || (func->hasOverride() && cls->getAttribute(ClassScope::NotFinal))) && !func->isPerfectVirtual())) { valid = false; } if (!valid) { setInvokeParams(ar); checkTypesImpl(ar, type, Type::Variant, coerce); m_valid = false; // so we use invoke() syntax if (!Option::AllDynamic) { func->setDynamic(); } assert(m_actualType); return m_actualType; } assert(func); return checkParamsAndReturn(ar, type, coerce, func, false); }
void ClassScope::importUsedTraits(AnalysisResultPtr ar) { // Trait flattening is supposed to happen only when we have awareness of // the whole program. assert(Option::WholeProgram); if (m_traitStatus == FLATTENED) return; if (m_traitStatus == BEING_FLATTENED) { getStmt()->analysisTimeFatal( Compiler::CyclicDependentTraits, "Cyclic dependency between traits involving %s", getOriginalName().c_str() ); return; } if (m_usedTraitNames.size() == 0) { m_traitStatus = FLATTENED; return; } m_traitStatus = BEING_FLATTENED; // First, make sure that parent classes have their traits imported if (!m_parent.empty()) { ClassScopePtr parent = ar->findClass(m_parent); if (parent) { parent->importUsedTraits(ar); } } if (isTrait()) { for (auto const& req : getTraitRequiredExtends()) { ClassScopePtr rCls = ar->findClass(req); if (!rCls || rCls->isFinal() || rCls->isInterface()) { getStmt()->analysisTimeFatal( Compiler::InvalidDerivation, Strings::TRAIT_BAD_REQ_EXTENDS, m_originalName.c_str(), req.c_str(), req.c_str() ); } } for (auto const& req : getTraitRequiredImplements()) { ClassScopePtr rCls = ar->findClass(req); if (!rCls || !(rCls->isInterface())) { getStmt()->analysisTimeFatal( Compiler::InvalidDerivation, Strings::TRAIT_BAD_REQ_IMPLEMENTS, m_originalName.c_str(), req.c_str(), req.c_str() ); } } } // Find trait methods to be imported for (unsigned i = 0; i < m_usedTraitNames.size(); i++) { ClassScopePtr tCls = ar->findClass(m_usedTraitNames[i]); if (!tCls || !(tCls->isTrait())) { setAttribute(UsesUnknownTrait); // XXX: is this useful ... for anything? getStmt()->analysisTimeFatal( Compiler::UnknownTrait, Strings::TRAITS_UNKNOWN_TRAIT, m_usedTraitNames[i].c_str() ); } // First, make sure the used trait is flattened tCls->importUsedTraits(ar); findTraitMethodsToImport(ar, tCls); // Import any interfaces implemented tCls->getInterfaces(ar, m_bases, false); } for (unsigned i = 0; i < m_usedTraitNames.size(); i++) { // Requirements must be checked in a separate loop because the // interfaces required by one trait may be implemented by another trait // whose "use" appears later in the class' scope ClassScopePtr tCls = ar->findClass(m_usedTraitNames[i]); importTraitRequirements(ar, tCls); } // Apply rules applyTraitRules(ar); // Remove trait abstract methods provided by other traits and duplicates removeSpareTraitAbstractMethods(ar); // Apply precedence of current class over used traits for (MethodToTraitListMap::iterator iter = m_importMethToTraitMap.begin(); iter != m_importMethToTraitMap.end(); ) { MethodToTraitListMap::iterator thisiter = iter; iter++; if (findFunction(ar, thisiter->first, 0, 0) != FunctionScopePtr()) { m_importMethToTraitMap.erase(thisiter); } } std::map<string, MethodStatementPtr> importedTraitMethods; std::vector<std::pair<string,const TraitMethod*>> importedTraitsWithOrigName; // Actually import the methods for (MethodToTraitListMap::const_iterator iter = m_importMethToTraitMap.begin(); iter != m_importMethToTraitMap.end(); iter++) { // The rules may rule out a method from all traits. // In this case, simply don't import the method. if (iter->second.size() == 0) { continue; } // Consistency checking: each name must only refer to one imported method if (iter->second.size() > 1) { getStmt()->analysisTimeFatal( Compiler::MethodInMultipleTraits, Strings::METHOD_IN_MULTIPLE_TRAITS, iter->first.c_str() ); } else { TraitMethodList::const_iterator traitMethIter = iter->second.begin(); if ((traitMethIter->m_modifiers ? traitMethIter->m_modifiers : traitMethIter->m_method->getModifiers())->isAbstract()) { // Skip abstract methods, if method already exists in the class if (findFunction(ar, iter->first, true) || importedTraitMethods.count(iter->first)) { continue; } } string sourceName = traitMethIter->m_ruleStmt ? toLower(((TraitAliasStatement*)traitMethIter->m_ruleStmt.get())-> getMethodName()) : iter->first; importedTraitMethods[sourceName] = MethodStatementPtr(); importedTraitsWithOrigName.push_back( make_pair(sourceName, &*traitMethIter)); } } // Make sure there won't be 2 constructors after importing auto traitConstruct = importedTraitMethods.count("__construct"); auto traitName = importedTraitMethods.count(getName()); auto classConstruct = m_functions.count("__construct"); auto className = m_functions.count(getName()); if ((traitConstruct && traitName) || (traitConstruct && className) || (classConstruct && traitName)) { getStmt()->analysisTimeFatal( Compiler::InvalidDerivation, "%s has colliding constructor definitions coming from traits", getOriginalName().c_str() ); } for (unsigned i = 0; i < importedTraitsWithOrigName.size(); i++) { const string &sourceName = importedTraitsWithOrigName[i].first; const TraitMethod *traitMethod = importedTraitsWithOrigName[i].second; MethodStatementPtr newMeth = importTraitMethod( *traitMethod, ar, toLower(traitMethod->m_originalName), importedTraitMethods); if (newMeth) { importedTraitMethods[sourceName] = newMeth; } } // Import trait properties importTraitProperties(ar); m_traitStatus = FLATTENED; }