/*************************************************************************** copyright : (C) 2006 by David Nolden email : david.nolden.tdevelop@art-master.de ***************************************************************************/ /*************************************************************************** * * * 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 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "simpletypefunction.h" #include "safetycounter.h" #include "simpletypenamespace.h" extern SafetyCounter safetyCounter; extern CppCodeCompletion* cppCompletionInstance; HashedStringSet getIncludeFiles( const ItemDom& item ) { if ( item ) { FileDom f = item->file(); if ( f ) { ParseResultPointer p = f->parseResult(); if ( p ) { ParsedFilePointer pp = dynamic_cast( p.data() ); if ( pp ) { return pp->includeFiles(); } } } } return HashedStringSet(); } //SimpleTypeFunctionInterface implementation TQString SimpleTypeFunctionInterface::signature() { TQString sig = "( "; SimpleTypeImpl* asType = dynamic_cast( this ); TQStringList argDefaults = getArgumentDefaults(); TQStringList argNames = getArgumentNames(); TQValueList argTypes = getArgumentTypes(); TQValueList argRealTypes; if ( asType ) { for ( TQValueList::iterator it = argTypes.begin(); it != argTypes.end(); ++it ) { argRealTypes << asType->locateDecType( *it ); } } TQStringList::iterator def = argDefaults.begin(); TQStringList::iterator name = argNames.begin(); TQValueList::iterator realType = argRealTypes.begin(); while ( realType != argRealTypes.end() ) { if ( sig != "( " ) sig += ", "; sig += ( *realType )->fullNameChain(); ++realType; if ( name != argNames.end() ) { if ( !( *name ).isEmpty() ) sig += " " + *name; ++name; } if ( def != argDefaults.end() && !( *def ).isEmpty() ) { sig += " = " + *def; ++def; } } sig += " )"; return sig; } bool SimpleTypeFunctionInterface::containsUndefinedTemplateParam( TypeDesc& desc, SimpleTypeImpl::TemplateParamInfo& paramInfo ) { TypeDesc::TemplateParams& pm = desc.templateParams(); SimpleTypeImpl::TemplateParamInfo::TemplateParam t; if ( pm.isEmpty() && paramInfo.getParam( t, desc.name() ) ) if ( !t.value ) return true; if ( desc.next() ) if ( containsUndefinedTemplateParam( *desc.next(), paramInfo ) ) return true; for ( TypeDesc::TemplateParams::iterator it = pm.begin(); it != pm.end(); ++it ) { if ( containsUndefinedTemplateParam( **it, paramInfo ) ) return true; } return false; } void SimpleTypeFunctionInterface::resolveImplicitTypes( TypeDesc& argType, TypeDesc& gottenArgType, SimpleTypeImpl::TemplateParamInfo& paramInfo ) { if ( argType.templateParams().isEmpty() ) { ///Template-types may not be templates. SimpleTypeImpl::TemplateParamInfo::TemplateParam p; if ( paramInfo.getParam( p, argType.name() ) && !p.value ) { ifVerbose( dbg() << "choosing \"" << gottenArgType.fullNameChain() << "\" as implicit template-parameter for \"" << argType.name() << "\"" << endl ); p.value = gottenArgType; p.value.makePrivate(); for ( int d = 0; d < argType.totalPointerDepth(); d++ ) p.value.setTotalPointerDepth( p.value.totalPointerDepth() - 1 ); paramInfo.addParam( p ); } } else { if ( argType.name() == gottenArgType.name() ) resolveImplicitTypes( argType.templateParams(), gottenArgType.templateParams(), paramInfo ); } } void SimpleTypeFunctionInterface::resolveImplicitTypes( TypeDesc::TemplateParams& argTypes, TypeDesc::TemplateParams& gottenArgTypes, SimpleTypeImpl::TemplateParamInfo& paramInfo ) { TypeDesc::TemplateParams::iterator it = argTypes.begin(); TypeDesc::TemplateParams::iterator it2 = gottenArgTypes.begin(); while ( it != argTypes.end() && it2 != gottenArgTypes.end() ) { resolveImplicitTypes( **it, **it2, paramInfo ); ++it; ++it2; } } void SimpleTypeFunctionInterface::resolveImplicitTypes( TQValueList& argTypes, TQValueList& gottenArgTypes, SimpleTypeImpl::TemplateParamInfo& paramInfo ) { TQValueList::iterator it = argTypes.begin(); TQValueList::iterator it2 = gottenArgTypes.begin(); while ( it != argTypes.end() && it2 != gottenArgTypes.end() ) { resolveImplicitTypes( *it, *it2, paramInfo ); ++it; ++it2; } } void SimpleTypeFunctionInterface::appendNextFunction( SimpleType func ) { Debug d( "#fapp#" ); if ( !func || !d ) return; if (( SimpleTypeImpl* ) func.get() == ( SimpleTypeImpl* ) this ) return; if ( m_nextFunction && m_nextFunction->asFunction() ) { m_nextFunction->asFunction()->appendNextFunction( func ); } else { m_nextFunction = func; } } //SimpleTypeCodeModel implementation void SimpleTypeCodeModel::addAliasesTo( SimpleTypeNamespace* ns ) { const NamespaceModel* m = dynamic_cast( m_item.data() ); if ( m ) { const NamespaceModel::NamespaceAliasModelList& namespaceAliases = m->namespaceAliases(); const NamespaceModel::NamespaceImportModelList& namespaceImports = m->namespaceImports(); for ( NamespaceModel::NamespaceAliasModelList::const_iterator it = namespaceAliases.begin(); it != namespaceAliases.end(); ++it ) { HashedStringSet searchFiles; FileDom d = m->codeModel()->fileByName( it->fileName().str() ); ParsedFilePointer p = dynamic_cast( d->parseResult().data() ); if ( p ) { searchFiles = p->includeFiles(); } else { searchFiles = HashedStringSet( HashedString( it->fileName() ) ); } TypeDesc ds( it->aliasName() ); ds.setIncludeFiles( searchFiles ); ns->addAliasMap( it->name(), ds, HashedString( it->fileName() ), true, false, bigContainer() ); } for ( NamespaceModel::NamespaceImportModelList::const_iterator it = namespaceImports.begin(); it != namespaceImports.end(); ++it ) { HashedStringSet searchFiles; FileDom d = m->codeModel()->fileByName( it->fileName().str() ); ParsedFilePointer p = dynamic_cast( d->parseResult().data() ); if ( p ) { searchFiles = p->includeFiles(); } else { searchFiles = HashedStringSet( HashedString( it->fileName() ) ); } TypeDesc ds( it->name() ); ds.setIncludeFiles( searchFiles ); ns->addAliasMap( TypeDesc(), ds, HashedString( it->fileName() ), true, false, bigContainer() ); } } } SimpleTypeCodeModel::SimpleTypeCodeModel( ItemDom& item ) : m_item( item ) { CodeModelItem* i = & ( *item ); FunctionModel* m = dynamic_cast( i ); ClassModel* c = dynamic_cast( i ); if ( m ) { TQStringList l = m->scope(); l << m->name(); setScope( l ); return; } if ( c ) { TQStringList l = c->scope(); l << c->name(); setScope( l ); return; } ifVerbose( dbg() << "code-model-item has an unsupported type: " << i->name() << endl ); } ItemDom SimpleTypeCodeModel::locateModelContainer( class CodeModel* m, TypeDesc t, ClassDom cnt ) { if ( !cnt ) { if ( m->globalNamespace() ) { cnt = model_cast ( m->globalNamespace() ); } else { return ItemDom(); } } if ( t ) { if ( cnt->hasClass( t.name() ) ) { ClassList l = cnt->classByName( t.name() ); if ( !l.isEmpty() ) { if ( t.next() ) return locateModelContainer( m, *t.next(), l.front() ); else return model_cast ( l.front() ); } } NamespaceModel* ns = dynamic_cast( & ( *cnt ) ); if ( ns ) { NamespaceDom n = ns->namespaceByName( t.name() ); if ( t.next() ) return locateModelContainer( m, *t.next(), model_cast ( n ) ); else return model_cast ( n ); } } return ItemDom(); } ///Until header-parsing is implemented, this tries to find the class that is most related to this item /*ClassDom SimpleTypeCodeModel::pickMostRelated( ClassList lst, TQString fn ) { if( lst.isEmpty() ) return ClassDom(); ClassDom best = lst.front(); uint bestMatch = 0; //kdDebug() << "searching most related to " << fn << endl; for( ClassList::iterator it = lst.begin(); it != lst.end(); ++it ) { if( !(*it)->getSpecializationDeclaration().isEmpty() ) continue; ///Don't consider specialized classes //kdDebug() << "comparing " << (*it)->fileName() << endl; TQString str = (*it)->fileName(); uint len = str.length(); if( fn.length() < len ) len = fn.length(); uint matchLen = 0; for( uint a = 0; a < len; a++ ) { if( str[a] == fn[a] ) matchLen++; else break; } if( matchLen > bestMatch ) { //kdDebug() << "picking " << str << endl; bestMatch = matchLen; best = *it; } } //kdDebug() << "picked " << best->fileName() << endl; if( !best->getSpecializationDeclaration().isEmpty() ) best = 0; ///only accept non-specialized classes return best; }*/ /*TQValueList SimpleTypeCodeModel::findSpecializations( const TQString& name ) { ClassModel* klass = dynamic_cast ( & (*m_item) ); if( !klass ) { ifVerbose( dbg() << "\"" << str() << "\": search for member " << name.name() << " unsuccessful because the own type is invalid" << endl ); return TQValueList(); } ClassList l = klass->classByName( name.name() ); if( !l.isEmpty() ) { ClassDom i = pickMostRelated( l, globalCurrentFile ); if( i ) { ret.setBuildInfo( new CodeModelBuildInfo( model_cast( i ), name, TypePointer( this ) ) ); ret.memberType = MemberInfo::NestedType; ret.type = name; } } return TQValueList(); }*/ TQValueList SimpleTypeCodeModel::getMemberClasses( const TypeDesc& name ) { TQValueList ret; if ( !m_item ) return ret; ClassModel* klass = dynamic_cast( & ( *m_item ) ); if ( !klass ) { ifVerbose( dbg() << "\"" << str() << "\": search for member " << name.name() << " unsuccessful because the own type is invalid" << endl ); return ret; } ClassList l = klass->classByName( name.name() ); if ( !l.isEmpty() ) { for ( ClassList::iterator it = l.begin(); it != l.end(); ++it ) { CodeModelBuildInfo b( model_cast ( *it ), name, TypePointer( this ) ); TypePointer r = b.build(); if ( r ) ret << r; } } return ret; } template Item pickMostRelated( const HashedStringSet& includeFiles, const TQValueList& list ) { if ( list.isEmpty() ) return Item(); for ( typename TQValueList::const_iterator it = list.begin(); it != list.end(); ++it ) { if ( includeFiles[( *it )->fileName()] ) return *it; } return list.front(); } template<> ClassDom pickMostRelated( const HashedStringSet& includeFiles, const TQValueList& list ) { if ( list.isEmpty() ) return ClassDom(); ///@todo the current file must be preferred for ( TQValueList::const_iterator it = list.begin(); it != list.end(); ++it ) { if ( !( *it )->getSpecializationDeclaration().isEmpty() ) continue; ///Don't consider specialized classes if ( includeFiles[( *it )->fileName()] ) return *it; } if ( !list.front()->getSpecializationDeclaration().isEmpty() ) return ClassDom(); ///Don't consider specialized classes return list.front(); } SimpleTypeImpl::MemberInfo SimpleTypeCodeModel::findMember( TypeDesc name , MemberInfo::MemberType type ) { MemberInfo ret; ret.name = name.name(); ret.memberType = MemberInfo::NotFound; if ( !name || !m_item ) return ret; ClassModel* klass = dynamic_cast( & ( *m_item ) ); if ( !klass ) { ifVerbose( dbg() << "\"" << str() << "\": search for member " << name.name() << " unsuccessful because the own type is invalid" << endl ); return ret; } NamespaceModel* ns = dynamic_cast( klass ); if ( klass->hasVariable( name.name() ) && ( type & MemberInfo::Variable ) ) { ret.memberType = MemberInfo::Variable; VariableDom d = klass->variableByName( name.name() ); if ( d ) { ret.type = d->type(); ret.type->setIncludeFiles( HashedString( d->fileName() ) ); ret.decl.name = d->name(); ret.decl.file = d->fileName(); ret.decl.comment = d->comment(); d->getStartPosition( &ret.decl.startLine, &ret.decl.startCol ); d->getEndPosition( &ret.decl.endLine, &ret.decl.endCol ); } } else if ( klass->hasTypeAlias( name.name() ) && ( type & MemberInfo::Typedef ) ) { ret.memberType = MemberInfo::Typedef; TypeAliasList li = klass->typeAliasByName( name.name() ); TypeAliasDom a = pickMostRelated( name.includeFiles(), li ); if ( a ) { ret.type = a->type(); ret.type->setIncludeFiles( getIncludeFiles( a.data() ) ); ret.decl.name = a->name(); ret.decl.file = a->fileName(); ret.decl.comment = a->comment(); a->getStartPosition( &ret.decl.startLine, &ret.decl.startCol ); a->getEndPosition( &ret.decl.endLine, &ret.decl.endCol ); } } else if ( klass->hasEnum( name.name() ) && ( type & MemberInfo::Typedef ) ) { ret.memberType = MemberInfo::Typedef; EnumDom e = klass->enumByName( name.name() ); ret.type = TypeDesc( "const int" ); ret.type->setIncludeFiles( HashedString( e->fileName() ) ); ret.decl.name = e->name(); ret.decl.file = e->fileName(); ret.decl.comment = e->comment(); e->getStartPosition( &ret.decl.startLine, &ret.decl.startCol ); e->getEndPosition( &ret.decl.endLine, &ret.decl.endCol ); } else if ( klass->hasClass( name.name() ) && ( type & MemberInfo::NestedType ) ) { ClassList l = klass->classByName( name.name() ); if ( !l.isEmpty() ) { ClassDom i = pickMostRelated( name.includeFiles(), l ); if ( i ) { ret.setBuildInfo( new CodeModelBuildInfo( model_cast ( i ), name, TypePointer( this ) ) ); ret.memberType = MemberInfo::NestedType; ret.type = name; ret.type->setIncludeFiles( HashedString( i->fileName() ) ); } } } else if ( klass->hasFunction( name.name() ) && ( type & MemberInfo::Function ) ) { ret.memberType = MemberInfo::Function; FunctionList l = klass->functionByName( name.name() ); if ( !l.isEmpty() && l.front() ) { ret.setBuildInfo( new SimpleTypeCodeModelFunction::CodeModelFunctionBuildInfo( l, name , TypePointer( this ) ) ); ret.type = l.front()->resultType(); ret.type->setIncludeFiles( HashedString( l.front()->fileName() ) ); ret.type->increaseFunctionDepth(); } } else if ( ns && ns->hasNamespace( name.name() ) && ( type & MemberInfo::Namespace ) ) { NamespaceDom n = ns->namespaceByName( name.name() ); ret.setBuildInfo( new CodeModelBuildInfo( model_cast ( n ), name, TypePointer( this ) ) ); ret.memberType = MemberInfo::Namespace; ret.type = name; //ret.type->setIncludeFiles( d->fileName() ); } else if ( klass->hasFunctionDefinition( name.name() ) && ( type & MemberInfo::Function ) ) { FunctionDefinitionList l = klass->functionDefinitionByName( name.name() ); for ( FunctionDefinitionList::iterator it = l.begin(); it != l.end(); ++it ) { if ( !( *it )->scope().isEmpty() && ( *it )->scope() != scope() ) continue; ///Only use definitions with empty scope or that are within this class ret.setBuildInfo( new SimpleTypeCodeModelFunction::CodeModelFunctionBuildInfo( l, name, TypePointer( this ) ) ); ret.type = l.front()->resultType(); ret.type->setIncludeFiles( HashedString(( *it )->fileName() ) ); ret.type->increaseFunctionDepth(); ret.memberType = MemberInfo::Function; break; } } if ( ret.memberType == MemberInfo::NotFound ) { if ( type & MemberInfo::Template ) { LocateResult s = findTemplateParam( name.name() ); if ( s ) { ret.memberType = MemberInfo::Template; ret.type = s; if ( m_item ) ret.type->setIncludeFiles( getIncludeFiles( m_item.data() ) ); ret.decl.name = name.name(); if ( m_item ) { ret.decl.file = m_item->fileName(); m_item->getStartPosition( &ret.decl.startLine, &ret.decl.startCol ); m_item->getEndPosition( &ret.decl.endLine, &ret.decl.endCol ); } } } } if ( ret.memberType == MemberInfo::Function || ret.memberType == MemberInfo::Variable || ret.memberType == MemberInfo::Template || ret.memberType == MemberInfo::Typedef || ret.memberType == MemberInfo::NestedType ) { //For redirected types it is necessary to add the include-files of the context they were searched in. //That is not quite correct, but it makes sure that at least the same namespace-aliases will be activated while the search for the type, //Which is necessary because the alias is parented by exactly this class. ret.type->addIncludeFiles( name.includeFiles() ); } chooseSpecialization( ret ); return ret; } bool SimpleTypeCodeModel::findItem() { TQString key = str(); m_item = locateModelContainer( cppCompletionInstance->m_pSupport->codeModel(), str() ); return ( bool ) m_item; } void SimpleTypeCodeModel::init() { if ( scope().isEmpty() ) { m_item = cppCompletionInstance->m_pSupport->codeModel() ->globalNamespace(); } else { findItem(); } } DeclarationInfo SimpleTypeCodeModel::getDeclarationInfo() { DeclarationInfo ret; ItemDom i = item(); ret.name = fullTypeResolved(); if ( i ) { ret.file = i->fileName(); i->getStartPosition( &ret.startLine, &ret.startCol ); i->getEndPosition( &ret.endLine, &ret.endCol ); ret.comment = i->comment(); } return ret; } TQString SimpleTypeCodeModel::specialization() const { const ClassModel* klass = dynamic_cast( m_item.data() ); if ( !klass ) return TQString(); return klass->getSpecializationDeclaration(); } SimpleTypeImpl::TemplateParamInfo SimpleTypeCodeModel::getTemplateParamInfo() { TemplateParamInfo ret; if ( m_item ) { TemplateModelItem* ti = dynamic_cast( & ( *m_item ) ); TypeDesc::TemplateParams& templateParams = m_desc.templateParams(); TemplateModelItem::ParamMap m = ti->getTemplateParams(); for ( uint a = 0; a < m.size(); a++ ) { TemplateParamInfo::TemplateParam t; t.number = a; t.name = m[a].first; t.def = m[a].second; if ( templateParams.count() > a ) t.value = *templateParams[a]; ret.addParam( t ); } } return ret; } const LocateResult SimpleTypeCodeModel::findTemplateParam( const TQString& name ) { if ( m_item ) { TemplateModelItem* ti = dynamic_cast( & ( *m_item ) ); TypeDesc::TemplateParams& templateParams = m_desc.templateParams(); int pi = ti->findTemplateParam( name ); if ( pi != -1 && ( int ) templateParams.count() > pi ) { return *templateParams[pi]; } else { if ( pi != -1 && !ti->getParam( pi ).second.isEmpty() ) { TQString def = ti->getParam( pi ).second; ifVerbose( dbg() << "\"" << str() << "\": using default-template-parameter \"" << def << "\" for " << name << endl ); return TypeDesc( def ); } else if ( pi != -1 ) { ifVerbose( dbg() << "\"" << str() << "\": template-type \"" << name << "\" has no pameter! " << endl ); } } } return LocateResult(); } TQStringList SimpleTypeCodeModel::getBaseStrings() { Debug d( "#getbases#" ); if ( !d || !safetyCounter ) { //ifVerbose( dbg() << "\"" << str() << "\": recursion to deep while getting bases" << endl ); return TQStringList(); } TQStringList ret; ClassModel* klass; if ( !m_item || ( klass = dynamic_cast( & ( *m_item ) ) ) == 0 ) return ret; TQStringList parents = klass->baseClassList(); for ( TQStringList::Iterator it = parents.begin(); it != parents.end(); ++it ) { ret << *it; } return ret; } TypePointer SimpleTypeCodeModel::CodeModelBuildInfo::build() { TypePointer tp = new SimpleTypeCachedCodeModel( m_item ); tp->parseParams( m_desc ); if ( m_parent ) tp->setParent( m_parent->bigContainer() ); return tp; } //SimpleTypeCodeModelFunction implementation TypeDesc SimpleTypeCodeModelFunction::getReturnType() { if ( item() ) { IncludeFiles files; if( parent() ) files = parent()->getFindIncludeFiles(); if ( FunctionModel* m = dynamic_cast( & ( *item() ) ) ) { TypeDesc d = m->resultType(); d.setIncludeFiles( files ); return d; } } return TypeDesc(); } bool SimpleTypeCodeModelFunction::isConst() { if ( asFunctionModel() ) return asFunctionModel()->isConstant(); return false; } TQValueList SimpleTypeCodeModelFunction::getArgumentTypes() { TQValueList ret; if ( item() ) { IncludeFiles files; if( parent() ) files = parent()->getFindIncludeFiles(); if ( FunctionModel* m = dynamic_cast( & ( *item() ) ) ) { ArgumentList l = m->argumentList(); for ( ArgumentList::iterator it = l.begin(); it != l.end(); ++it ) { ret << TypeDesc(( *it )->type() ); ret.back().setIncludeFiles( files ); } } } return ret; } TQStringList SimpleTypeCodeModelFunction::getArgumentNames() { TQStringList ret; if ( item() ) { if ( FunctionModel* m = dynamic_cast( & ( *item() ) ) ) { ArgumentList l = m->argumentList(); for ( ArgumentList::iterator it = l.begin(); it != l.end(); ++it ) ret << ( *it )->name(); } } return ret; } TQStringList SimpleTypeCodeModelFunction::getArgumentDefaults() { TQStringList ret; if ( item() ) { if ( FunctionModel* m = dynamic_cast( & ( *item() ) ) ) { ArgumentList l = m->argumentList(); for ( ArgumentList::iterator it = l.begin(); it != l.end(); ++it ) ret << ( *it )->defaultValue(); } } return ret; } //SimpleTypeCodeModelFunction::CodeModelFunctionBuildInfo implementation SimpleTypeCodeModelFunction::CodeModelFunctionBuildInfo::CodeModelFunctionBuildInfo( FunctionDefinitionList items, TypeDesc& desc, TypePointer parent ) : m_desc( desc ), m_parent( parent ) { for ( FunctionDefinitionList::iterator it = items.begin(); it != items.end(); ++it ) { m_items << model_cast ( *it ); } } TypePointer SimpleTypeCodeModelFunction::CodeModelFunctionBuildInfo::build() { TQValueList ret; TypePointer last; for ( FunctionList::iterator it = m_items.begin(); it != m_items.end(); ++it ) { TypePointer tp = new SimpleTypeCodeModelFunction( model_cast ( *it ) ); tp->takeTemplateParams( m_desc ); tp->descForEdit().increaseFunctionDepth(); tp->setParent( m_parent->bigContainer() ); if ( last && last->asFunction() ) last->asFunction()->appendNextFunction( SimpleType( tp ) ); last = tp; ret << tp; } if ( ret.isEmpty() ) { ifVerbose( dbg() << "error" << endl ); return TypePointer(); } else return ret.front(); } //SimpleTypeCatalogFunction implementation TypeDesc SimpleTypeCatalogFunction::getReturnType() { if ( tag() ) { return tagType( tag() ); } return TypeDesc(); } bool SimpleTypeCatalogFunction::isConst() { Tag t = tag(); CppFunction tagInfo( t ); return tagInfo.isConst(); } TQStringList SimpleTypeCatalogFunction::getArgumentNames() { TQStringList ret; Tag t = tag(); CppFunction tagInfo( t ); return tagInfo.argumentNames(); } TQValueList SimpleTypeCatalogFunction::getArgumentTypes() { TQValueList ret; Tag t = tag(); CppFunction tagInfo( t ); TQStringList arguments = tagInfo.arguments(); for ( TQStringList::iterator it = arguments.begin(); it != arguments.end(); ++it ) ret << TypeDesc( *it ); return ret; } //SimpleTypeCatalogFunction::CatalogFunctionBuildInfo implementation TypePointer SimpleTypeCatalogFunction::CatalogFunctionBuildInfo::build() { TQValueList ret; TypePointer last; for ( TQValueList::iterator it = m_tags.begin(); it != m_tags.end(); ++it ) { TypePointer tp = new SimpleTypeCatalogFunction( *it ); tp->takeTemplateParams( m_desc ); tp->descForEdit().increaseFunctionDepth(); if ( m_parent ) tp->setParent( m_parent->bigContainer() ); if ( last && last->asFunction() ) last->asFunction()->appendNextFunction( SimpleType( tp ) ); last = tp; ret << tp; } if ( ret.isEmpty() ) { ifVerbose( dbg() << "error" << endl ); return TypePointer(); } return ret.front(); } // kate: indent-mode csands; tab-width 4;