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 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; }
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; }